add vim mode lines to avoid evil tab characters :-)
[opensuse:osc.git] / osc / commandline.py
1 # Copyright (C) 2006 Novell Inc.  All rights reserved.
2 # This program is free software; it may be used, copied, modified
3 # and distributed under the terms of the GNU General Public Licence,
4 # either version 2, or version 3 (at your option).
5
6
7 from core import *
8 import cmdln
9 import conf
10 import oscerr
11 import urlgrabber.progress
12 from optparse import SUPPRESS_HELP
13
14 MAN_HEADER = r""".TH %(ucname)s "1" "%(date)s" "%(name)s %(version)s" "User Commands"
15 .SH NAME
16 %(name)s \- openSUSE build service command-line tool.
17 .SH SYNOPSIS
18 .B %(name)s
19 [\fIGLOBALOPTS\fR] \fISUBCOMMAND \fR[\fIOPTS\fR] [\fIARGS\fR...]
20 .br
21 .B %(name)s
22 \fIhelp SUBCOMMAND\fR
23 .SH DESCRIPTION
24 openSUSE build service command-line tool.
25 """
26 MAN_FOOTER = r"""
27 .SH "SEE ALSO"
28 Type 'osc help <subcommand>' for more detailed help on a specific subcommand.
29 .PP
30 For additional information, see
31  * http://www.opensuse.org/Build_Service_Tutorial
32  * http://www.opensuse.org/Build_Service/CLI
33 .PP
34 You can modify osc commands, or roll you own, via the plugin API:
35  * http://www.opensuse.org/Build_Service/osc_plugins
36 .SH AUTHOR
37 osc was written by several authors. This man page is automatically generated.
38 """
39
40 class Osc(cmdln.Cmdln):
41     """Usage: osc [GLOBALOPTS] SUBCOMMAND [OPTS] [ARGS...]
42     or: osc help SUBCOMMAND
43
44     openSUSE build service command-line tool.
45     Type 'osc help <subcommand>' for help on a specific subcommand.
46
47     ${command_list}
48     ${help_list}
49     global ${option_list}
50     For additional information, see
51     * http://www.opensuse.org/Build_Service_Tutorial
52     * http://www.opensuse.org/Build_Service/CLI
53
54     You can modify osc commands, or roll you own, via the plugin API:
55     * http://www.opensuse.org/Build_Service/osc_plugins
56     """
57     name = 'osc'
58     conf = None
59
60     man_header = MAN_HEADER
61     man_footer = MAN_FOOTER
62
63     def __init__(self, *args, **kwargs):
64         cmdln.Cmdln.__init__(self, *args, **kwargs)
65         cmdln.Cmdln.do_help.aliases.append('h')
66
67     def get_version(self):
68         return get_osc_version()
69
70     def get_optparser(self):
71         """this is the parser for "global" options (not specific to subcommand)"""
72
73         optparser = cmdln.CmdlnOptionParser(self, version=get_osc_version())
74         optparser.add_option('--debugger', action='store_true',
75                       help='jump into the debugger before executing anything')
76         optparser.add_option('--post-mortem', action='store_true',
77                       help='jump into the debugger in case of errors')
78         optparser.add_option('-t', '--traceback', action='store_true',
79                       help='print call trace in case of errors')
80         optparser.add_option('-H', '--http-debug', action='store_true',
81                       help='debug HTTP traffic')
82         optparser.add_option('-d', '--debug', action='store_true',
83                       help='print info useful for debugging')
84         optparser.add_option('-A', '--apiurl', dest='apiurl',
85                       metavar='URL/alias',
86                       help='specify URL to access API server at or an alias')
87         optparser.add_option('-c', '--config', dest='conffile',
88                       metavar='FILE',
89                       help='specify alternate configuration file')
90         optparser.add_option('--no-keyring', action='store_true',
91                       help='disable usage of desktop keyring system')
92         optparser.add_option('--no-gnome-keyring', action='store_true',
93                       help='disable usage of GNOME Keyring')
94         optparser.add_option('-v', '--verbose', dest='verbose', action='count', default=0,
95                       help='increase verbosity')
96         optparser.add_option('-q', '--quiet',   dest='verbose', action='store_const', const=-1,
97                       help='be quiet, not verbose')
98         return optparser
99
100
101     def postoptparse(self, try_again = True):
102         """merge commandline options into the config"""
103         try:
104             conf.get_config(override_conffile = self.options.conffile,
105                             override_apiurl = self.options.apiurl,
106                             override_debug = self.options.debug,
107                             override_http_debug = self.options.http_debug,
108                             override_traceback = self.options.traceback,
109                             override_post_mortem = self.options.post_mortem,
110                             override_no_keyring = self.options.no_keyring,
111                             override_no_gnome_keyring = self.options.no_gnome_keyring,
112                             override_verbose = self.options.verbose)
113         except oscerr.NoConfigfile, e:
114             print >>sys.stderr, e.msg
115             print >>sys.stderr, 'Creating osc configuration file %s ...' % e.file
116             import getpass
117             config = {}
118             config['user'] = raw_input('Username: ')
119             config['pass'] = getpass.getpass()
120             if self.options.apiurl:
121                 config['apiurl'] = self.options.apiurl
122
123             conf.write_initial_config(e.file, config)
124             print >>sys.stderr, 'done'
125             if try_again: self.postoptparse(try_again = False)
126         except oscerr.ConfigMissingApiurl, e:
127             print >>sys.stderr, e.msg
128             import getpass
129             user = raw_input('Username: ')
130             passwd = getpass.getpass()
131             conf.add_section(e.file, e.url, user, passwd)
132             if try_again: self.postoptparse(try_again = False)
133
134         self.options.verbose = conf.config['verbose']
135         self.download_progress = None
136         if conf.config.get('show_download_progress', False):
137             from meter import TextMeter
138             self.download_progress = TextMeter()
139
140
141     def get_cmd_help(self, cmdname):
142         doc = self._get_cmd_handler(cmdname).__doc__
143         doc = self._help_reindent(doc)
144         doc = self._help_preprocess(doc, cmdname)
145         doc = doc.rstrip() + '\n' # trim down trailing space
146         return self._str(doc)
147
148
149     # overridden from class Cmdln() to use config variables in help texts
150     def _help_preprocess(self, help, cmdname):
151         help = cmdln.Cmdln._help_preprocess(self, help, cmdname)
152         return help % conf.config
153
154
155     def do_init(self, subcmd, opts, project, package=None):
156         """${cmd_name}: Initialize a directory as working copy
157
158         Initialize an existing directory to be a working copy of an
159         (already existing) buildservice project/package.
160
161         (This is the same as checking out a package and then copying sources
162         into the directory. It does NOT create a new package. To create a
163         package, use 'osc meta pkg ... ...')
164
165         You wouldn't normally use this command.
166
167         To get a working copy of a package (e.g. for building it or working on
168         it, you would normally use the checkout command. Use "osc help
169         checkout" to get help for it.
170
171         usage:
172             osc init PRJ
173             osc init PRJ PAC
174         ${cmd_option_list}
175         """
176
177         if not package:
178             init_project_dir(conf.config['apiurl'], os.curdir, project)
179             print 'Initializing %s (Project: %s)' % (os.curdir, project)
180         else:
181             init_package_dir(conf.config['apiurl'], project, package, os.path.curdir)
182             print 'Initializing %s (Project: %s, Package: %s)' % (os.curdir, project, package)
183
184     @cmdln.alias('ls')
185     @cmdln.alias('ll')
186     @cmdln.alias('lL')
187     @cmdln.alias('LL')
188     @cmdln.option('-a', '--arch', metavar='ARCH',
189                         help='specify architecture (only for binaries)')
190     @cmdln.option('-r', '--repo', metavar='REPO',
191                         help='specify repository (only for binaries)')
192     @cmdln.option('-b', '--binaries', action='store_true',
193                         help='list built binaries instead of sources')
194     @cmdln.option('-R', '--revision', metavar='REVISION',
195                         help='specify revision (only for sources)')
196     @cmdln.option('-e', '--expand', action='store_true',
197                         help='expand linked package (only for sources)')
198     @cmdln.option('-v', '--verbose', action='store_true',
199                         help='print extra information')
200     @cmdln.option('-l', '--long', action='store_true', dest='verbose',
201                         help='print extra information')
202     def do_list(self, subcmd, opts, *args):
203         """${cmd_name}: List sources or binaries on the server
204
205         Examples for listing sources:
206            ls                         # list all projects
207            ls PROJECT                  # list packages in a project
208            ls PROJECT PACKAGE          # list source files of package of a project
209            ls PROJECT PACKAGE <file>   # list <file> if this file exists
210            ls -v PROJECT PACKAGE       # verbosely list source files of package
211            ls -l PROJECT PACKAGE       # verbosely list source files of package
212            ll PROJECT PACKAGE          # verbosely list source files of package
213            LL PROJECT PACKAGE          # verbosely list source files of expanded link
214
215         With --verbose, the following fields will be shown for each item:
216            MD5 hash of file
217            Revision number of the last commit
218            Size (in bytes)
219            Date and time of the last commit
220
221         Examples for listing binaries:
222            ls -b PROJECT               # list all binaries of a project
223            ls -b PROJECT -a ARCH       # list ARCH binaries of a project
224            ls -b PROJECT -r REPO       # list binaries in REPO
225            ls -b PROJECT PACKAGE REPO ARCH
226
227         Usage:
228            ${cmd_name} [PROJECT [PACKAGE]]
229            ${cmd_name} -b [PROJECT [PACKAGE [REPO [ARCH]]]]
230         ${cmd_option_list}
231         """
232
233         apiurl = conf.config['apiurl']
234         args = slash_split(args)
235         if subcmd == 'll':
236             opts.verbose = True
237         if subcmd == 'lL' or subcmd == 'LL':
238             opts.verbose = True
239             opts.expand = True
240
241         project = None
242         package = None
243         fname = None
244         if len(args) > 0:
245             project = args[0]
246         if len(args) > 1:
247             package = args[1]
248         if len(args) > 2:
249             if opts.binaries:
250                 if opts.repo:
251                     if opts.repo != args[2]:
252                         raise oscerr.WrongArgs("conflicting repos specified ('%s' vs '%s')"%(opts.repo, args[2]))
253                 else:
254                     opts.repo = args[2]
255             else:
256                 fname = args[2]
257
258         if len(args) > 3:
259             if not opts.binaries:
260                 raise oscerr.WrongArgs('Too many arguments')
261             if opts.arch:
262                 if opts.arch != args[3]:
263                     raise oscerr.WrongArgs("conflicting archs specified ('%s' vs '%s')"%(opts.arch, args[3]))
264             else:
265                 opts.arch = args[3]
266
267
268         if opts.binaries and opts.expand:
269             raise oscerr.WrongOptions('Sorry, --binaries and --expand are mutual exclusive.')
270
271         # list binaries
272         if opts.binaries:
273             # ls -b toplevel doesn't make sense, so use info from
274             # current dir if available
275             if len(args) == 0:
276                 dir = os.getcwd()
277                 if is_project_dir(dir):
278                     project = store_read_project(dir)
279                     apiurl = store_read_apiurl(dir)
280                 elif is_package_dir(dir):
281                     project = store_read_project(dir)
282                     package = store_read_package(dir)
283                     apiurl = store_read_apiurl(dir)
284
285             if not project:
286                 raise oscerr.WrongArgs('There are no binaries to list above project level.')
287             if opts.revision:
288                 raise oscerr.WrongOptions('Sorry, the --revision option is not supported for binaries.')
289
290             repos = []
291
292             if opts.repo and opts.arch:
293                 repos.append(Repo(opts.repo, opts.arch))
294             elif opts.repo and not opts.arch:
295                 for repo in get_repos_of_project(apiurl, project):
296                     if repo.name == opts.repo:
297                         repos.append(repo)
298             elif opts.arch and not opts.repo:
299                 for repo in get_repos_of_project(apiurl, project):
300                     if repo.arch == opts.arch:
301                         repos.append(repo)
302             else:
303                 repos = get_repos_of_project(apiurl, project)
304
305             results = []
306             for repo in repos:
307                 results.append((repo, get_binarylist(apiurl, project, repo.name, repo.arch, package=package, verbose=opts.verbose)))
308
309             for result in results:
310                 indent = ''
311                 if len(results) > 1:
312                     print '%s/%s' % (result[0].name, result[0].arch)
313                     indent = ' '
314
315                 if opts.verbose:
316                     for f in result[1]:
317                         print "%9d %s %-40s" % (f.size, shorttime(f.mtime), f.name)
318                 else:
319                     for f in result[1]:
320                         print indent+f
321
322         # list sources
323         elif not opts.binaries:
324             if not args:
325                 print '\n'.join(meta_get_project_list(conf.config['apiurl']))
326
327             elif len(args) == 1:
328                 if opts.verbose:
329                     if self.options.verbose:
330                         print >>sys.stderr, 'Sorry, the --verbose option is not implemented for projects.'
331                 if opts.expand:
332                     raise oscerr.WrongOptions('Sorry, the --expand option is not implemented for projects.')
333
334                 print '\n'.join(meta_get_packagelist(conf.config['apiurl'], project))
335
336             elif len(args) == 2 or len(args) == 3:
337                 l = meta_get_filelist(conf.config['apiurl'],
338                                       project,
339                                       package,
340                                       verbose=opts.verbose,
341                                       expand=opts.expand,
342                                       revision=opts.revision)
343                 if opts.verbose:
344                     out = [ '%s %7s %9d %s %s' % (i.md5, i.rev, i.size, shorttime(i.mtime), i.name) \
345                             for i in l if not fname or fname == i.name ]
346                     if len(out) == 0:
347                         if fname:
348                             print 'file \'%s\' does not exist' % fname
349                     else:
350                         print '\n'.join(out)
351                 else:
352                     if fname:
353                         if fname in l:
354                             print fname
355                         else:
356                             print 'file \'%s\' does not exist' % fname
357                     else:
358                         print '\n'.join(l)
359
360
361     @cmdln.option('-f', '--force', action='store_true',
362                         help='force generation of new patchinfo file')
363     @cmdln.option('--force-update', action='store_true',
364                         help='drops away collected packages from an already built patch and let it collect again')
365     def do_patchinfo(self, subcmd, opts, *args):
366         """${cmd_name}: Generate and edit a patchinfo file.
367
368         A patchinfo file describes the packages for an update and the kind of
369         problem it solves.
370
371         Examples:
372             osc patchinfo
373             osc patchinfo PATCH_NAME
374         ${cmd_option_list}
375         """
376
377         project_dir = localdir = os.getcwd()
378         if is_project_dir(localdir):
379             project = store_read_project(localdir)
380             apiurl = store_read_apiurl(localdir)
381         else:
382             sys.exit('This command must be called in a checked out project.')
383         patchinfo = None
384         for p in meta_get_packagelist(apiurl, project):
385             if p.startswith("_patchinfo:"):
386                 patchinfo = p
387         
388         if opts.force or not patchinfo:
389             print "Creating initial patchinfo..."
390             query='cmd=createpatchinfo'
391             if args and args[0]:
392                query += "&name=" + args[0]
393             url = makeurl(apiurl, ['source', project], query=query)
394             f = http_POST(url)
395             for p in meta_get_packagelist(apiurl, project):
396                 if p.startswith("_patchinfo:"):
397                     patchinfo = p
398
399         if not os.path.exists(project_dir + "/" + patchinfo):
400             checkout_package(apiurl, project, patchinfo, prj_dir=project_dir)
401
402         if sys.platform[:3] != 'win':
403             editor = os.getenv('EDITOR', default='vim')
404         else:
405             editor = os.getenv('EDITOR', default='notepad')
406         subprocess.call('%s %s' % (editor, project_dir + "/" + patchinfo + "/_patchinfo"), shell=True)
407
408
409     @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
410                         help='affect only a given attribute')
411     @cmdln.option('--attribute-defaults', action='store_true',
412                         help='include defined attribute defaults')
413     @cmdln.option('--attribute-project', action='store_true',
414                         help='include project values, if missing in packages ')
415     @cmdln.option('-F', '--file', metavar='FILE',
416                         help='read metadata from FILE, instead of opening an editor. '
417                         '\'-\' denotes standard input. ')
418     @cmdln.option('-e', '--edit', action='store_true',
419                         help='edit metadata')
420     @cmdln.option('-c', '--create', action='store_true',
421                         help='create attribute without values')
422     @cmdln.option('-s', '--set', metavar='ATTRIBUTE_VALUES',
423                         help='set attribute values')
424     @cmdln.option('--delete', action='store_true',
425                         help='delete a pattern or attribute')
426     def do_meta(self, subcmd, opts, *args):
427         """${cmd_name}: Show meta information, or edit it
428
429         Show or edit build service metadata of type <prj|pkg|prjconf|user|pattern>.
430
431         This command displays metadata on buildservice objects like projects,
432         packages, or users. The type of metadata is specified by the word after
433         "meta", like e.g. "meta prj".
434
435         prj denotes metadata of a buildservice project.
436         prjconf denotes the (build) configuration of a project.
437         pkg denotes metadata of a buildservice package.
438         user denotes the metadata of a user.
439         pattern denotes installation patterns defined for a project.
440
441         To list patterns, use 'osc meta pattern PRJ'. An additional argument
442         will be the pattern file to view or edit.
443
444         With the --edit switch, the metadata can be edited. Per default, osc
445         opens the program specified by the environmental variable EDITOR with a
446         temporary file. Alternatively, content to be saved can be supplied via
447         the --file switch. If the argument is '-', input is taken from stdin:
448         osc meta prjconf home:user | sed ... | osc meta prjconf home:user -F -
449
450         When trying to edit a non-existing resource, it is created implicitly.
451
452
453         Examples:
454             osc meta prj PRJ
455             osc meta pkg PRJ PKG
456             osc meta pkg PRJ PKG -e
457             osc meta attribute PRJ [PKG [SUBPACKAGE]] [--attribute ATTRIBUTE] [--create|--delete|--set [value_list]]
458
459         Usage:
460             osc meta <prj|pkg|prjconf|user|pattern|attribute> ARGS...
461             osc meta <prj|pkg|prjconf|user|pattern|attribute> -e|--edit ARGS...
462             osc meta <prj|pkg|prjconf|user|pattern|attribute> -F|--file ARGS...
463             osc meta pattern --delete PRJ PATTERN
464         ${cmd_option_list}
465         """
466
467         args = slash_split(args)
468
469         if not args or args[0] not in metatypes.keys():
470             raise oscerr.WrongArgs('Unknown meta type. Choose one of %s.' \
471                                                % ', '.join(metatypes))
472
473         cmd = args[0]
474         del args[0]
475
476         if cmd in ['pkg']:
477             min_args, max_args = 2, 2
478         elif cmd in ['pattern']:
479             min_args, max_args = 1, 2
480         elif cmd in ['attribute']:
481             min_args, max_args = 1, 3
482         else:
483             min_args, max_args = 1, 1
484         if len(args) < min_args:
485             raise oscerr.WrongArgs('Too few arguments.')
486         if len(args) > max_args:
487             raise oscerr.WrongArgs('Too many arguments.')
488
489         # specific arguments
490         attributepath = []
491         if cmd == 'prj':
492             project = args[0]
493         elif cmd == 'pkg':
494             project, package = args[0:2]
495         elif cmd == 'attribute':
496             project = args[0]
497             if len(args) > 1:
498                 package = args[1]
499             else:
500                 package = None
501                 if opts.attribute_project:
502                     raise oscerr.WrongOptions('--attribute-project works only when also a package is given')
503             if len(args) > 2:
504                 subpackage = args[2]
505             else:
506                 subpackage = None
507             attributepath.append('source')
508             attributepath.append(project)
509             if package:
510                 attributepath.append(package)
511             if subpackage:
512                 attributepath.append(subpackage)
513             attributepath.append('_attribute')
514         elif cmd == 'prjconf':
515             project = args[0]
516         elif cmd == 'user':
517             user = args[0]
518         elif cmd == 'pattern':
519             project = args[0]
520             if len(args) > 1:
521                 pattern = args[1]
522             else:
523                 pattern = None
524                 # enforce pattern argument if needed
525                 if opts.edit or opts.file:
526                     raise oscerr.WrongArgs('A pattern file argument is required.')
527
528         # show
529         if not opts.edit and not opts.file and not opts.delete and not opts.create and not opts.set:
530             if cmd == 'prj':
531                 sys.stdout.write(''.join(show_project_meta(conf.config['apiurl'], project)))
532             elif cmd == 'pkg':
533                 sys.stdout.write(''.join(show_package_meta(conf.config['apiurl'], project, package)))
534             elif cmd == 'attribute':
535                 sys.stdout.write(''.join(show_attribute_meta(conf.config['apiurl'], project, package, subpackage, opts.attribute, opts.attribute_defaults, opts.attribute_project)))
536             elif cmd == 'prjconf':
537                 sys.stdout.write(''.join(show_project_conf(conf.config['apiurl'], project)))
538             elif cmd == 'user':
539                 r = get_user_meta(conf.config['apiurl'], user)
540                 if r:
541                     sys.stdout.write(''.join(r))
542             elif cmd == 'pattern':
543                 if pattern:
544                     r = show_pattern_meta(conf.config['apiurl'], project, pattern)
545                     if r:
546                         sys.stdout.write(''.join(r))
547                 else:
548                     r = show_pattern_metalist(conf.config['apiurl'], project)
549                     if r:
550                         sys.stdout.write('\n'.join(r) + '\n')
551
552         # edit
553         if opts.edit and not opts.file:
554             if cmd == 'prj':
555                 edit_meta(metatype='prj',
556                           edit=True,
557                           path_args=quote_plus(project),
558                           template_args=({
559                                   'name': project,
560                                   'user': conf.config['user']}))
561             elif cmd == 'pkg':
562                 edit_meta(metatype='pkg',
563                           edit=True,
564                           path_args=(quote_plus(project), quote_plus(package)),
565                           template_args=({
566                                   'name': package,
567                                   'user': conf.config['user']}))
568             elif cmd == 'prjconf':
569                 edit_meta(metatype='prjconf',
570                           edit=True,
571                           path_args=quote_plus(project),
572                           template_args=None)
573             elif cmd == 'user':
574                 edit_meta(metatype='user',
575                           edit=True,
576                           path_args=(quote_plus(user)),
577                           template_args=({'user': user}))
578             elif cmd == 'pattern':
579                 edit_meta(metatype='pattern',
580                           edit=True,
581                           path_args=(project, pattern),
582                           template_args=None)
583
584         # create attribute entry
585         if (opts.create or opts.set) and cmd == 'attribute':
586             if not opts.attribute:
587                 raise oscerr.WrongOptions('no attribute given to create')
588             values = ''
589             if opts.set:
590                 opts.set = opts.set.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
591                 for i in opts.set.split(','):
592                     values += '<value>%s</value>' % i
593             aname = opts.attribute.split(":")
594             d = '<attributes><attribute namespace=\'%s\' name=\'%s\' >%s</attribute></attributes>' % (aname[0], aname[1], values)
595             url = makeurl(conf.config['apiurl'], attributepath)
596             for data in streamfile(url, http_POST, data=d):
597                 sys.stdout.write(data)
598
599         # upload file
600         if opts.file:
601
602             if opts.file == '-':
603                 f = sys.stdin.read()
604             else:
605                 try:
606                     f = open(opts.file).read()
607                 except:
608                     sys.exit('could not open file \'%s\'.' % opts.file)
609
610             if cmd == 'prj':
611                 edit_meta(metatype='prj',
612                           data=f,
613                           edit=opts.edit,
614                           path_args=quote_plus(project))
615             elif cmd == 'pkg':
616                 edit_meta(metatype='pkg',
617                           data=f,
618                           edit=opts.edit,
619                           path_args=(quote_plus(project), quote_plus(package)))
620             elif cmd == 'prjconf':
621                 edit_meta(metatype='prjconf',
622                           data=f,
623                           edit=opts.edit,
624                           path_args=quote_plus(project))
625             elif cmd == 'user':
626                 edit_meta(metatype='user',
627                           data=f,
628                           edit=opts.edit,
629                           path_args=(quote_plus(user)))
630             elif cmd == 'pattern':
631                 edit_meta(metatype='pattern',
632                           data=f,
633                           edit=opts.edit,
634                           path_args=(project, pattern))
635
636
637         # delete
638         if opts.delete:
639             path = metatypes[cmd]['path']
640             if cmd == 'pattern':
641                 path = path % (project, pattern)
642                 u = makeurl(conf.config['apiurl'], [path])
643                 http_DELETE(u)
644             elif cmd == 'attribute':
645                 if not opts.attribute:
646                     raise oscerr.WrongOptions('no attribute given to create')
647                 attributepath.append(opts.attribute)
648                 u = makeurl(conf.config['apiurl'], attributepath)
649                 for data in streamfile(u, http_DELETE):
650                     sys.stdout.write(data)
651             else:
652                 raise oscerr.WrongOptions('The --delete switch is only for pattern metadata or attributes.')
653
654
655     @cmdln.option('-m', '--message', metavar='TEXT',
656                   help='specify message TEXT')
657     @cmdln.option('-r', '--revision', metavar='REV',
658                   help='for "create", specify a certain source revision ID (the md5 sum)')
659     @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
660                   help='Superseding another request by this one')
661     @cmdln.option('--nodevelproject', action='store_true',
662                   help='do not follow a defined devel project ' \
663                        '(primary project where a package is developed)')
664     @cmdln.option('--cleanup', action='store_true',
665                   help='remove package if submission gets accepted (default for home:<id>:branch projects)')
666     @cmdln.option('--no-cleanup', action='store_true',
667                   help='never remove source package on accept, but update its content')
668     @cmdln.option('--no-update', action='store_true',
669                   help='never touch source package on accept (will break source links)')
670     @cmdln.option('-d', '--diff', action='store_true',
671                   help='show diff only instead of creating the actual request')
672     @cmdln.option('--yes', action='store_true',
673                   help='proceed without asking.')
674     @cmdln.alias("sr")
675     @cmdln.alias("submitreq")
676     @cmdln.alias("submitpac")
677     def do_submitrequest(self, subcmd, opts, *args):
678         """${cmd_name}: Create request to submit source into another Project
679
680         [See http://en.opensuse.org/Build_Service/Collaboration for information
681         on this topic.]
682
683         See the "request" command for showing and modifing existing requests.
684
685         usage:
686             osc submitreq [OPTIONS]
687             osc submitreq [OPTIONS] DESTPRJ [DESTPKG]
688             osc submitreq [OPTIONS] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
689         ${cmd_option_list}
690         """
691
692         src_update = conf.config['submitrequest_on_accept_action'] or None
693         # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
694         if opts.cleanup:
695             src_update = "cleanup"
696         elif opts.no_cleanup:
697             src_update = "update"
698         elif opts.no_update:
699             src_update = "noupdate"
700
701         args = slash_split(args)
702
703         # remove this block later again
704         oldcmds = ['create', 'list', 'log', 'show', 'decline', 'accept', 'delete', 'revoke']
705         if args and args[0] in oldcmds:
706             print "************************************************************************"
707             print "* WARNING: It looks that you are using this command with a             *"
708             print "*          deprecated syntax.                                          *"
709             print "*          Please run \"osc sr --help\" and \"osc rq --help\"              *"
710             print "*          to see the new syntax.                                      *"
711             print "************************************************************************"
712             if args[0] == 'create':
713                 args.pop(0)
714             else:
715                 sys.exit(1)
716
717         if len(args) > 4:
718             raise oscerr.WrongArgs('Too many arguments.')
719
720         if len(args) > 0 and len(args) <= 2 and is_project_dir(os.getcwd()):
721             sys.exit('osc submitrequest from project directory is only working without target specs and for source linked files\n')
722
723         apiurl = conf.config['apiurl']
724
725         if len(args) == 0 and is_project_dir(os.getcwd()):
726             import cgi
727             # submit requests for multiple packages are currently handled via multiple requests
728             # They could be also one request with multiple actions, but that avoids to accepts parts of it.
729             project = store_read_project(os.curdir)
730             apiurl = store_read_apiurl(os.curdir)
731
732             sr_ids = []
733             pi = []
734             pac = []
735             targetprojects = []
736             # loop via all packages for checking their state
737             for p in meta_get_packagelist(apiurl, project):
738                 if p.startswith("_patchinfo:"):
739                     pi.append(p)
740                 else:
741                     # get _link info from server, who knows about the local state ...
742                     u = makeurl(apiurl, ['source', project, p])
743                     f = http_GET(u)
744                     root = ET.parse(f).getroot()
745                     linkinfo = root.find('linkinfo')
746                     if linkinfo == None:
747                         print "Package ", p, " is not a source link."
748                         sys.exit("This is currently not supported.")
749                     if linkinfo.get('error'):
750                         print "Package ", p, " is a broken source link."
751                         sys.exit("Please fix this first")
752                     t = linkinfo.get('project')
753                     if t:
754                         if len(root.findall('entry')) > 1: # This is not really correct, but should work mostly
755                                                            # Real fix is to ask the api if sources are modificated
756                                                            # but there is no such call yet.
757                             targetprojects.append(t)
758                             pac.append(p)
759                             print "Submitting package ", p
760                         else:
761                             print "  Skipping package ", p
762                     else:
763                         print "Skipping package ", p,  " since it is a source link pointing inside to the project."
764
765             if not opts.yes:
766                 if pi:
767                     print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
768                 print "\nEverything fine? Can we create the requests ? [y/n]"
769                 if sys.stdin.read(1) != "y":
770                     sys.exit("Aborted...")
771
772             # loop via all packages to do the action
773             for p in pac:
774                 result = create_submit_request(apiurl, project, p)
775                 if not result:
776 #                   sys.exit(result)
777                    sys.exit("submit request creation failed")
778                 sr_ids.append(result)
779
780             # create submit requests for all found patchinfos
781             actionxml=""
782             options_block=""
783             if src_update:
784                 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
785
786             for p in pi:
787                 for t in targetprojects:
788                     s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>"""  % \
789                            (project, p, t, p, options_block)
790                     actionxml += s
791
792             if actionxml != "":
793                 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
794                       (actionxml, cgi.escape(opts.message or ""))
795                 u = makeurl(apiurl, ['request'], query='cmd=create')
796                 f = http_POST(u, data=xml)
797          
798                 root = ET.parse(f).getroot()
799                 sr_ids.append(root.get('id'))
800
801             print "Requests created: ",
802             for i in sr_ids:
803                print i,
804             sys.exit('Successfull finished')
805
806         elif len(args) <= 2:
807             # try using the working copy at hand
808             p = findpacs(os.curdir)[0]
809             src_project = p.prjname
810             src_package = p.name
811             apiurl = p.apiurl
812             if len(args) == 0 and p.islink():
813                 dst_project = p.linkinfo.project
814                 dst_package = p.linkinfo.package
815             elif len(args) > 0:
816                 dst_project = args[0]
817                 if len(args) == 2:
818                     dst_package = args[1]
819                 else:
820                     dst_package = src_package
821             else:
822                 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
823                          'Please provide it the target via commandline arguments.' % p.name)
824
825             modified = [i for i in p.filenamelist if p.status(i) != ' ' and p.status(i) != '?']
826             if len(modified) > 0:
827                 print 'Your working copy has local modifications.'
828                 repl = raw_input('Proceed without committing the local changes? (y|N) ')
829                 if repl != 'y':
830                     sys.exit(1)
831         elif len(args) >= 3:
832             # get the arguments from the commandline
833             src_project, src_package, dst_project = args[0:3]
834             if len(args) == 4:
835                 dst_package = args[3]
836             else:
837                 dst_package = src_package
838         else:
839             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
840                   + self.get_cmd_help('request'))
841
842         if not opts.nodevelproject:
843             devloc = None
844             try:
845                 devloc = show_develproject(apiurl, dst_project, dst_package)
846             except urllib2.HTTPError:
847                 print >>sys.stderr, """\
848 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
849                     % (dst_project, dst_package)
850                 pass
851
852             if devloc and \
853                dst_project != devloc and \
854                src_project != devloc:
855                 print """\
856 A different project, %s, is defined as the place where development
857 of the package %s primarily takes place.
858 Please submit there instead, or use --nodevelproject to force direct submission.""" \
859                 % (devloc, dst_package)
860                 if not opts.diff:
861                     sys.exit(1)
862
863         rdiff = None
864         if opts.diff or not opts.message:
865             try:
866                 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
867                 rdiff += server_diff(apiurl,
868                                     dst_project, dst_package, opts.revision,
869                                     src_project, src_package, None, True)
870             except:
871                 rdiff = ''
872         if opts.diff:
873             print rdiff
874         else:
875             reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit')
876             user = conf.get_apiurl_usr(apiurl)
877             myreqs = [ i for i in reqs if i.state.who == user ]
878             repl = ''
879             if len(myreqs) > 0:
880                 print 'You already created the following submit request: %s.' % \
881                       ', '.join([str(i.reqid) for i in myreqs ])
882                 repl = raw_input('Supersede the old requests? (y/n/c) ')
883                 if repl.lower() == 'c':
884                     print >>sys.stderr, 'Aborting'
885                     sys.exit(1)
886
887             if not opts.message:
888                 difflines = []
889                 doappend = False
890                 changes_re = re.compile(r'^--- .*\.changes ')
891                 for line in rdiff.split('\n'):
892                     if line.startswith('--- '):
893                         if changes_re.match(line):
894                             doappend = True
895                         else:
896                             doappend = False
897                     if doappend:
898                         difflines.append(line)
899                 opts.message = edit_message(footer=rdiff, template='\n'.join(parse_diff_for_commit_message('\n'.join(difflines))))
900
901             result = create_submit_request(apiurl,
902                                            src_project, src_package,
903                                            dst_project, dst_package,
904                                            opts.message, orev=opts.revision, src_update=src_update)
905             if repl.lower() == 'y':
906                 for req in myreqs:
907                     change_request_state(apiurl, str(req.reqid), 'superseded',
908                                          'superseded by %s' % result, result)
909
910             if opts.supersede:
911                 r = change_request_state(conf.config['apiurl'],
912                         opts.supersede, 'superseded', opts.message or '', result)
913
914             print 'created request id', result
915
916
917     @cmdln.option('-m', '--message', metavar='TEXT',
918                   help='specify message TEXT')
919     @cmdln.alias("dr")
920     @cmdln.alias("deletereq")
921     def do_deleterequest(self, subcmd, opts, *args):
922         """${cmd_name}: Create request to delete a package or project
923
924
925         usage:
926             osc deletereq [-m TEXT] PROJECT [PACKAGE]
927         ${cmd_option_list}
928         """
929
930         args = slash_split(args)
931
932         if len(args) < 1:
933             raise oscerr.WrongArgs('Please specify at least a project.')
934         if len(args) > 2:
935             raise oscerr.WrongArgs('Too many arguments.')
936
937         apiurl = conf.config['apiurl']
938
939         project = args[0]
940         package = None
941         if len(args) > 1:
942             package = args[1]
943
944         if not opts.message:
945             opts.message = edit_message()
946
947         result = create_delete_request(apiurl, project, package, opts.message)
948         print result
949
950
951     @cmdln.option('-m', '--message', metavar='TEXT',
952                   help='specify message TEXT')
953     @cmdln.alias("cr")
954     @cmdln.alias("changedevelreq")
955     def do_changedevelrequest(self, subcmd, opts, *args):
956         """${cmd_name}: Create request to change the devel package definition.
957
958         [See http://en.opensuse.org/Build_Service/Collaboration for information
959         on this topic.]
960
961         See the "request" command for showing and modifing existing requests.
962
963         osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
964         """
965
966         if len(args) > 4:
967             raise oscerr.WrongArgs('Too many arguments.')
968
969         if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
970             wd = os.curdir
971             devel_project = store_read_project(wd)
972             devel_package = package = store_read_package(wd)
973             apiurl = store_read_apiurl(wd)
974             project = conf.config['getpac_default_project']
975         else:
976             if len(args) < 3:
977                 raise oscerr.WrongArgs('Too few arguments.')
978
979             apiurl = conf.config['apiurl']
980
981             devel_project = args[2]
982             project = args[0]
983             package = args[1]
984             devel_package = package
985             if len(args) > 3:
986                 devel_package = args[3]
987
988         if not opts.message:
989             import textwrap
990             footer=textwrap.TextWrapper(width = 66).fill(
991                     'please explain why you like to change the devel project of %s/%s to %s/%s'
992                     % (project,package,devel_project,devel_package))
993             opts.message = edit_message(footer)
994
995         result = create_change_devel_request(apiurl,
996                                        devel_project, devel_package,
997                                        project, package,
998                                        opts.message)
999         print result
1000
1001
1002     @cmdln.option('-d', '--diff', action='store_true',
1003                   help='generate a diff')
1004     @cmdln.option('-u', '--unified', action='store_true',
1005                   help='output the diff in the unified diff format')
1006     @cmdln.option('-m', '--message', metavar='TEXT',
1007                   help='specify message TEXT')
1008     @cmdln.option('-t', '--type', metavar='TYPE',
1009                   help='limit to requests which contain a given action type (submit/delete/change_devel)')
1010     @cmdln.option('-a', '--all', action='store_true',
1011                         help='all states. Same as\'-s all\'')
1012     @cmdln.option('-s', '--state', default='',  # default is 'all' if no args given, 'new' otherwise
1013                         help='only list requests in one of the comma separated given states (new/accepted/revoked/declined) or "all" [default=new, or all, if no args given]')
1014     @cmdln.option('-D', '--days', metavar='DAYS',
1015                         help='only list requests in state "new" or changed in the last DAYS. [default=%(request_list_days)s]')
1016     @cmdln.option('-U', '--user', metavar='USER',
1017                         help='same as -M, but for the specified USER')
1018     @cmdln.option('-b', '--brief', action='store_true', default=False,
1019                         help='print output in list view as list subcommand')
1020     @cmdln.option('-M', '--mine', action='store_true',
1021                         help='only show requests created by yourself')
1022     @cmdln.option('-B', '--bugowner', action='store_true',
1023                         help='also show requests about packages where I am bugowner')
1024     @cmdln.option('-i', '--interactive', action='store_true',
1025                         help='interactive review of request')
1026     @cmdln.alias("rq")
1027     @cmdln.alias("review")
1028     def do_request(self, subcmd, opts, *args):
1029         """${cmd_name}: Show and modify requests
1030
1031         [See http://en.opensuse.org/Build_Service/Collaboration for information
1032         on this topic.]
1033
1034         This command shows and modifies existing requests. To create new requests
1035         you need to call one of the following:
1036           osc submitrequest
1037           osc deleterequest
1038           osc changedevelrequest
1039         To send low level requests to the buildservice API, use:
1040           osc api
1041
1042         This command has the following sub commands:
1043
1044         "list" lists open requests attached to a project or package or person.
1045         Uses the project/package of the current directory if none of
1046         -M, -U USER, project/package are given.
1047
1048         "log" will show the history of the given ID
1049
1050         "show" will show the request itself, and generate a diff for review, if
1051         used with the --diff option. The keyword show can be omitted if the ID is numeric.
1052
1053         "decline" will change the request state to "declined" and append a
1054         message that you specify with the --message option.
1055
1056         "wipe" will permanently delete a request.
1057
1058         "revoke" will set the request state to "revoked" and append a
1059         message that you specify with the --message option.
1060
1061         "accept" will change the request state to "accepted" and will trigger
1062         the actual submit process. That would normally be a server-side copy of
1063         the source package to the target package.
1064
1065         "checkout" will checkout the request's source package. This only works for "submit" requests.
1066
1067         usage:
1068             osc request list [-M] [-U USER] [-s state] [-D DAYS] [-t type] [-B] [PRJ [PKG]]
1069             osc request log ID
1070             osc request [show] [-d] [-b] ID
1071             osc request accept [-m TEXT] ID
1072             osc request decline [-m TEXT] ID
1073             osc request revoke [-m TEXT] ID
1074             osc request wipe ID
1075             osc request checkout/co ID
1076             osc review accept [-m TEXT] ID
1077             osc review decline [-m TEXT] ID
1078         ${cmd_option_list}
1079         """
1080
1081         args = slash_split(args)
1082
1083         if opts.all and opts.state:
1084             raise oscerr.WrongOptions('Sorry, the options --all and --state ' \
1085                      'are mutually exclusive.')
1086         if opts.mine and opts.user:
1087             raise oscerr.WrongOptions('Sorry, the options --user and --mine ' \
1088                      'are mutually exclusive.')
1089
1090         if not args:
1091             args = [ 'list' ]
1092             opts.mine = 1
1093             if opts.state == '':
1094                 opts.state = 'all'
1095
1096         if opts.state == '':
1097             opts.state = 'new'
1098
1099         cmds = ['list', 'log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co', 'help']
1100         if not args or args[0] not in cmds:
1101             raise oscerr.WrongArgs('Unknown request action %s. Choose one of %s.' \
1102                                                % (args[0],', '.join(cmds)))
1103
1104         cmd = args[0]
1105         del args[0]
1106
1107         if cmd == 'help':
1108             return self.do_help(['help', 'request'])
1109
1110         if cmd in ['wipe']:
1111             min_args, max_args = 1, 1
1112         elif cmd in ['list']:
1113             min_args, max_args = 0, 2
1114         else:
1115             min_args, max_args = 1, 1
1116         if len(args) < min_args:
1117             raise oscerr.WrongArgs('Too few arguments.')
1118         if len(args) > max_args:
1119             raise oscerr.WrongArgs('Too many arguments.')
1120
1121         apiurl = conf.config['apiurl']
1122
1123         if cmd == 'list':
1124             package = None
1125             project = None
1126             if len(args) > 0:
1127                 project = args[0]
1128             elif not opts.mine and not opts.user:
1129                 try:
1130                     project = store_read_project(os.curdir)
1131                     apiurl = store_read_apiurl(os.curdir)
1132                     package = store_read_package(os.curdir)
1133                 except oscerr.NoWorkingCopy:
1134                     pass
1135
1136             if len(args) > 1:
1137                 package = args[1]
1138         elif cmd in ['log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co']:
1139             reqid = args[0]
1140
1141         # list
1142         if cmd == 'list':
1143             states = ('new', 'accepted', 'revoked', 'declined')
1144             state_list = opts.state.split(',')
1145             if opts.state == 'all':
1146                 state_list = ['all']
1147             else:
1148                 for s in state_list:
1149                     if not s in states:
1150                         raise oscerr.WrongArgs('Unknown state \'%s\', try one of %s' % (s, ','.join(states)))
1151             who = ''
1152             if opts.mine:
1153                 who = conf.get_apiurl_usr(apiurl)
1154             if opts.user:
1155                 who = opts.user
1156             if opts.all:
1157                 state_list = ['all']
1158
1159             ## FIXME -B not implemented!
1160             if opts.bugowner:
1161                 if (self.options.debug):
1162                     print 'list: option --bugowner ignored: not impl.'
1163
1164             results = get_request_list(apiurl,
1165                                        project, package, who, state_list, opts.type)
1166             results.sort(reverse=True)
1167             import time
1168             days = opts.days or conf.config['request_list_days']
1169             since = ''
1170             try:
1171                 days = int(days)
1172             except ValueError:
1173                 days = 0
1174             if days > 0:
1175                 since = time.strftime('%Y-%m-%dT%H:%M:%S',time.localtime(time.time()-days*24*3600))
1176
1177             skipped = 0
1178             ## bs has received 2009-09-20 a new xquery compare() function
1179             ## which allows us to limit the list inside of get_request_list
1180             ## That would be much faster for coolo. But counting the remainder
1181             ## would not be possible with current xquery implementation.
1182             ## Workaround: fetch all, and filter on client side.
1183
1184             ## FIXME: date filtering should become implemented on server side
1185             for result in results:
1186                 if days == 0 or result.state.when > since or result.state.name == 'new':
1187                     print result.list_view()
1188                 else:
1189                     skipped += 1
1190             if skipped:
1191                 print "There are %d requests older than %s days.\n" % (skipped, days)
1192
1193         elif cmd == 'log':
1194             for l in get_request_log(conf.config['apiurl'], reqid):
1195                 print l
1196
1197         # show
1198         elif cmd == 'show':
1199             r = get_request(conf.config['apiurl'], reqid)
1200             if opts.brief:
1201                 print r.list_view()
1202             elif opts.interactive or conf.config['request_show_interactive']:
1203                 return request_interactive_review(conf.config['apiurl'], r)
1204             else:
1205                 print r
1206             # fixme: will inevitably fail if the given target doesn't exist
1207             if opts.diff:
1208                 try:
1209                     print server_diff(conf.config['apiurl'],
1210                                       r.actions[0].dst_project, r.actions[0].dst_package, None,
1211                                       r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified)
1212                 except urllib2.HTTPError, e:
1213                     e.osc_msg = 'Diff not possible'
1214                     raise
1215
1216         # checkout
1217         elif cmd == 'checkout' or cmd == 'co':
1218             r = get_request(conf.config['apiurl'], reqid)
1219             submits = [ i for i in r.actions if i.type == 'submit' ]
1220             if not len(submits):
1221                 raise oscerr.WrongArgs('\'checkout\' only works for \'submit\' requests')
1222             checkout_package(conf.config['apiurl'], submits[0].src_project, submits[0].src_package, \
1223                 submits[0].src_rev, expand_link=True, prj_dir=submits[0].src_project)
1224
1225         else:
1226             if not opts.message:
1227                 opts.message = edit_message()
1228             state_map = {'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked'}
1229             # Change review state only
1230             if subcmd == 'review':
1231                 if cmd in ['accept', 'decline']:
1232                     r = change_review_state(conf.config['apiurl'],
1233                             reqid, state_map[cmd], conf.config['user'], '', opts.message or '')
1234                     print r
1235             # Change state of entire request
1236             elif cmd in ['accept', 'decline', 'wipe', 'revoke']:
1237                 r = change_request_state(conf.config['apiurl'],
1238                         reqid, state_map[cmd], opts.message or '')
1239                 print r
1240
1241     # editmeta and its aliases are all depracated
1242     @cmdln.alias("editprj")
1243     @cmdln.alias("createprj")
1244     @cmdln.alias("editpac")
1245     @cmdln.alias("createpac")
1246     @cmdln.alias("edituser")
1247     @cmdln.alias("usermeta")
1248     @cmdln.hide(1)
1249     def do_editmeta(self, subcmd, opts, *args):
1250         """${cmd_name}:
1251
1252         Obsolete command to edit metadata. Use 'meta' now.
1253
1254         See the help output of 'meta'.
1255
1256         """
1257
1258         print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
1259         print >>sys.stderr, 'See \'osc help meta\'.'
1260         #self.do_help([None, 'meta'])
1261         return 2
1262
1263
1264     @cmdln.option('-r', '--revision', metavar='rev',
1265                   help='use the specified revision.')
1266     @cmdln.option('-u', '--unset', action='store_true',
1267                   help='remove revision in link, it will point always to latest revision')
1268     def do_setlinkrev(self, subcmd, opts, *args):
1269         """${cmd_name}: Updates a revision number in a source link.
1270
1271         This command adds or updates a specified revision number in a source link.
1272         The current revision of the source is used, if no revision number is specified.
1273
1274         usage:
1275             osc setlinkrev
1276             osc setlinkrev PROJECT [PACKAGE]
1277         ${cmd_option_list}
1278         """
1279
1280         args = slash_split(args)
1281         apiurl = conf.config['apiurl']
1282         package = None
1283         if len(args) == 0:
1284             p = findpacs(os.curdir)[0]
1285             project = p.prjname
1286             package = p.name
1287             apiurl = p.apiurl
1288             if not p.islink():
1289                 sys.exit('Local directory is no checked out source link package, aborting')
1290         elif len(args) == 2:
1291             project = args[0]
1292             package = args[1]
1293         elif len(args) == 1:
1294             project = args[0]
1295         else:
1296             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1297                   + self.get_cmd_help('setlinkrev'))
1298
1299         if package:
1300             packages = [ package ]
1301         else:
1302             packages = meta_get_packagelist(apiurl, project)
1303
1304         for p in packages:
1305             print "setting revision for package", p
1306             if opts.unset:
1307                 rev=-1
1308             else:
1309                 rev, dummy = parseRevisionOption(opts.revision)
1310             set_link_rev(apiurl, project, p, rev)
1311
1312
1313     def do_linktobranch(self, subcmd, opts, *args):
1314         """${cmd_name}: Convert a package containing a classic link with patch to a branch
1315
1316         This command tells the server to convert a _link with or without a project.diff
1317         to a branch. This is a full copy with a _link file pointing to the branched place.
1318
1319         usage:
1320             osc linktobranch                    # can be used in checked out package
1321             osc linktobranch PROJECT PACKAGE
1322         ${cmd_option_list}
1323         """
1324
1325         args = slash_split(args)
1326         if len(args) == 0:
1327             wd = os.curdir
1328             project = store_read_project(wd)
1329             package = store_read_package(wd)
1330             apiurl = store_read_apiurl(wd)
1331             update_local_dir = True
1332         elif len(args) < 2:
1333             raise oscerr.WrongArgs('Too few arguments (required none or two)')
1334         elif len(args) > 2:
1335             raise oscerr.WrongArgs('Too many arguments (required none or two)')
1336         else:
1337             apiurl = conf.config['apiurl']
1338             project = args[0]
1339             package = args[1]
1340             update_local_dir = False
1341
1342         # execute
1343         link_to_branch(apiurl, project, package)
1344         if update_local_dir:
1345             pac = Package(wd)
1346             pac.update(rev=pac.latest_rev())
1347
1348
1349     @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'],
1350                   help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.')
1351     @cmdln.option('-c', '--current', action='store_true',
1352                   help='link fixed against current revision.')
1353     @cmdln.option('-r', '--revision', metavar='rev',
1354                   help='link the specified revision.')
1355     @cmdln.option('-f', '--force', action='store_true',
1356                   help='overwrite an existing link file if it is there.')
1357     @cmdln.option('-d', '--disable-publish', action='store_true',
1358                   help='disable publishing of the linked package')
1359     def do_linkpac(self, subcmd, opts, *args):
1360         """${cmd_name}: "Link" a package to another package
1361
1362         A linked package is a clone of another package, but plus local
1363         modifications. It can be cross-project.
1364
1365         The DESTPAC name is optional; the source packages' name will be used if
1366         DESTPAC is omitted.
1367
1368         Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
1369
1370         To add a patch, add the patch as file and add it to the _link file.
1371         You can also specify text which will be inserted at the top of the spec file.
1372
1373         See the examples in the _link file.
1374
1375         usage:
1376             osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1377         ${cmd_option_list}
1378         """
1379
1380         args = slash_split(args)
1381
1382         if not args or len(args) < 3:
1383             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1384                   + self.get_cmd_help('linkpac'))
1385
1386         rev, dummy = parseRevisionOption(opts.revision)
1387
1388         src_project = args[0]
1389         src_package = args[1]
1390         dst_project = args[2]
1391         if len(args) > 3:
1392             dst_package = args[3]
1393         else:
1394             dst_package = src_package
1395
1396         if src_project == dst_project and src_package == dst_package:
1397             raise oscerr.WrongArgs('Error: source and destination are the same.')
1398
1399         if src_project == dst_project and not opts.cicount:
1400             # in this case, the user usually wants to build different spec
1401             # files from the same source
1402             opts.cicount = "copy"
1403
1404         if opts.current:
1405             rev = show_upstream_rev(conf.config['apiurl'], src_project, src_package)
1406
1407         if rev and not checkRevision(src_project, src_package, rev):
1408             print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
1409             sys.exit(1)
1410
1411         link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish)
1412
1413     @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]',
1414                   help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]')
1415     @cmdln.option('-d', '--disable-publish', action='store_true',
1416                   help='disable publishing of the aggregated package')
1417     def do_aggregatepac(self, subcmd, opts, *args):
1418         """${cmd_name}: "Aggregate" a package to another package
1419
1420         Aggregation of a package means that the build results (binaries) of a
1421         package are basically copied into another project.
1422         This can be used to make packages available from building that are
1423         needed in a project but available only in a different project. Note
1424         that this is done at the expense of disk space. See
1425         http://en.opensuse.org/Build_Service/Tips_and_Tricks#_link_and__aggregate
1426         for more information.
1427
1428         The DESTPAC name is optional; the source packages' name will be used if
1429         DESTPAC is omitted.
1430
1431         usage:
1432             osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1433         ${cmd_option_list}
1434         """
1435
1436         args = slash_split(args)
1437
1438         if not args or len(args) < 3:
1439             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1440                   + self.get_cmd_help('aggregatepac'))
1441
1442         src_project = args[0]
1443         src_package = args[1]
1444         dst_project = args[2]
1445         if len(args) > 3:
1446             dst_package = args[3]
1447         else:
1448             dst_package = src_package
1449
1450         if src_project == dst_project and src_package == dst_package:
1451             raise oscerr.WrongArgs('Error: source and destination are the same.')
1452
1453         repo_map = {}
1454         if opts.map_repo:
1455             for pair in opts.map_repo.split(','):
1456                 src_tgt = pair.split('=')
1457                 if len(src_tgt) != 2:
1458                     raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo)
1459                 repo_map[src_tgt[0]] = src_tgt[1]
1460
1461         aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish)
1462
1463
1464     @cmdln.option('-c', '--client-side-copy', action='store_true',
1465                         help='do a (slower) client-side copy')
1466     @cmdln.option('-k', '--keep-maintainers', action='store_true',
1467                         help='keep original maintainers. Default is remove all and replace with the one calling the script.')
1468     @cmdln.option('-d', '--keep-develproject', action='store_true',
1469                         help='keep develproject tag in the package metadata')
1470     @cmdln.option('-r', '--revision', metavar='rev',
1471                         help='link the specified revision.')
1472     @cmdln.option('-t', '--to-apiurl', metavar='URL',
1473                         help='URL of destination api server. Default is the source api server.')
1474     @cmdln.option('-m', '--message', metavar='TEXT',
1475                   help='specify message TEXT')
1476     @cmdln.option('-e', '--expand', action='store_true',
1477                         help='if the source package is a link then copy the expanded version of the link')
1478     def do_copypac(self, subcmd, opts, *args):
1479         """${cmd_name}: Copy a package
1480
1481         A way to copy package to somewhere else.
1482
1483         It can be done across buildservice instances, if the -t option is used.
1484         In that case, a client-side copy is implied.
1485
1486         Using --client-side-copy always involves downloading all files, and
1487         uploading them to the target.
1488
1489         The DESTPAC name is optional; the source packages' name will be used if
1490         DESTPAC is omitted.
1491
1492         usage:
1493             osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1494         ${cmd_option_list}
1495         """
1496
1497         args = slash_split(args)
1498
1499         if not args or len(args) < 3:
1500             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1501                   + self.get_cmd_help('copypac'))
1502
1503         src_project = args[0]
1504         src_package = args[1]
1505         dst_project = args[2]
1506         if len(args) > 3:
1507             dst_package = args[3]
1508         else:
1509             dst_package = src_package
1510
1511         src_apiurl = conf.config['apiurl']
1512         if opts.to_apiurl:
1513             dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl)
1514         else:
1515             dst_apiurl = src_apiurl
1516
1517         if src_project == dst_project and \
1518            src_package == dst_package and \
1519            src_apiurl == dst_apiurl:
1520             raise oscerr.WrongArgs('Source and destination are the same.')
1521
1522         if src_apiurl != dst_apiurl:
1523             opts.client_side_copy = True
1524
1525         rev, dummy = parseRevisionOption(opts.revision)
1526
1527         if opts.message:
1528             comment = opts.message
1529         else:
1530             if not rev:
1531                 rev = show_upstream_rev(src_apiurl, src_project, src_package)
1532             comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev )
1533
1534         r = copy_pac(src_apiurl, src_project, src_package,
1535                      dst_apiurl, dst_project, dst_package,
1536                      client_side_copy=opts.client_side_copy,
1537                      keep_maintainers=opts.keep_maintainers,
1538                      keep_develproject=opts.keep_develproject,
1539                      expand=opts.expand,
1540                      revision=rev,
1541                      comment=comment)
1542         print r
1543
1544
1545     @cmdln.option('-c', '--checkout', action='store_true',
1546                         help='Checkout branched package afterwards ' \
1547                                 '(\'osc bco\' is a shorthand for this option)' )
1548     @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
1549                         help='Use this attribute to find affected packages (default is OBS:Maintained)')
1550     @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE',
1551                         help='Use this attribute to find update projects (default is OBS:UpdateProject) ')
1552     def do_mbranch(self, subcmd, opts, *args):
1553         """${cmd_name}: Multiple branch of a package
1554
1555         [See http://en.opensuse.org/Build_Service/Concepts/Maintenance for information
1556         on this topic.]
1557
1558         This command is used for creating multiple links of defined version of a package
1559         in one project. This is esp. used for maintenance updates.
1560
1561         The branched package will live in
1562             home:USERNAME:branches:ATTRIBUTE:PACKAGE
1563         if nothing else specified.
1564
1565         usage:
1566             osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ]
1567         ${cmd_option_list}
1568         """
1569         args = slash_split(args)
1570         tproject = None
1571
1572         maintained_attribute = conf.config['maintained_attribute']
1573         maintained_update_project_attribute = conf.config['maintained_update_project_attribute']
1574
1575         if not len(args) or len(args) > 2:
1576             raise oscerr.WrongArgs('Wrong number of arguments.')
1577         if len(args) >= 1:
1578             package = args[0]
1579         if len(args) >= 2:
1580             tproject = args[1]
1581
1582         r = attribute_branch_pkg(conf.config['apiurl'], maintained_attribute, maintained_update_project_attribute, \
1583                                  package, tproject)
1584
1585         if r is None:
1586             print >>sys.stderr, 'ERROR: Attribute branch call came not back with a project.' 
1587             sys.exit(1)
1588
1589         print "Project " + r + " created."
1590
1591         if opts.checkout:
1592             init_project_dir(conf.config['apiurl'], r, r)
1593             print statfrmt('A', r)
1594
1595             # all packages
1596             for package in meta_get_packagelist(conf.config['apiurl'], r):
1597                 try:
1598                     checkout_package(conf.config['apiurl'], r, package, expand_link = True, prj_dir = r)
1599                 except:
1600                     print >>sys.stderr, 'Error while checkout package:\n', package
1601
1602             if conf.config['verbose']:
1603                 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1604
1605
1606     @cmdln.alias('branchco')
1607     @cmdln.alias('bco')
1608     @cmdln.alias('getpac')
1609     @cmdln.option('--nodevelproject', action='store_true',
1610                         help='do not follow a defined devel project ' \
1611                              '(primary project where a package is developed)')
1612     @cmdln.option('-c', '--checkout', action='store_true',
1613                         help='Checkout branched package afterwards ' \
1614                                 '(\'osc bco\' is a shorthand for this option)' )
1615     @cmdln.option('-r', '--revision', metavar='rev',
1616                         help='branch against a specific revision')
1617     def do_branch(self, subcmd, opts, *args):
1618         """${cmd_name}: Branch a package
1619
1620         [See http://en.opensuse.org/Build_Service/Collaboration for information
1621         on this topic.]
1622
1623         Create a source link from a package of an existing project to a new
1624         subproject of the requesters home project (home:branches:)
1625
1626         The branched package will live in
1627             home:USERNAME:branches:PROJECT/PACKAGE
1628         if nothing else specified.
1629
1630         With getpac or bco, the branched package will come from
1631             %(getpac_default_project)s
1632         if nothing else specified.
1633
1634         usage:
1635             osc branch SOURCEPROJECT SOURCEPACKAGE
1636             osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT
1637             osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE
1638             osc getpac  SOURCEPACKAGE
1639             osc bco ...
1640         ${cmd_option_list}
1641         """
1642
1643         if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': opts.checkout = True
1644         args = slash_split(args)
1645         tproject = tpackage = None
1646
1647         if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1:
1648             print >>sys.stderr, 'defaulting to %s/%s' % (conf.config['getpac_default_project'], args[0])
1649             # python has no args.unshift ???
1650             args = [ conf.config['getpac_default_project'] , args[0] ]
1651
1652         if len(args) < 2 or len(args) > 4:
1653             raise oscerr.WrongArgs('Wrong number of arguments.')
1654         expected = 'home:%s:branches:%s' % (conf.config['user'], args[0])
1655         if len(args) >= 3:
1656             expected = tproject = args[2]
1657         if len(args) >= 4:
1658             tpackage = args[3]
1659
1660         exists, targetprj, targetpkg, srcprj, srcpkg = \
1661                 branch_pkg(conf.config['apiurl'], args[0], args[1],
1662                            nodevelproject=opts.nodevelproject, rev=opts.revision,
1663                            target_project=tproject, target_package=tpackage,
1664                            return_existing=opts.checkout)
1665         if exists:
1666             print >>sys.stderr, 'Using existing branch project: %s' % targetprj
1667
1668         devloc = None
1669         if not exists and (srcprj is not None and srcprj != args[0] or \
1670                            srcprj is None and targetprj != expected):
1671             devloc = srcprj or targetprj
1672             if not srcprj and 'branches:' in targetprj:
1673                 devloc = targetprj.split('branches:')[1]
1674             print '\nNote: The branch has been created of a different project,\n' \
1675                   '              %s,\n' \
1676                   '      which is the primary location of where development for\n' \
1677                   '      that package takes place.\n' \
1678                   '      That\'s also where you would normally make changes against.\n' \
1679                   '      A direct branch of the specified package can be forced\n' \
1680                   '      with the --nodevelproject option.\n' % devloc
1681
1682         package = tpackage or args[1]
1683         if opts.checkout:
1684             checkout_package(conf.config['apiurl'], targetprj, package,
1685                              expand_link=True, prj_dir=targetprj)
1686             if conf.config['verbose']:
1687                 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1688         else:
1689             apiopt = ''
1690             if conf.get_configParser().get('general', 'apiurl') != conf.config['apiurl']:
1691                 apiopt = '-A %s ' % conf.config['apiurl']
1692             print 'A working copy of the branched package can be checked out with:\n\n' \
1693                   'osc %sco %s/%s' \
1694                       % (apiopt, targetprj, package)
1695         print_request_list(conf.config['apiurl'], args[0], args[1])
1696         if devloc:
1697             print_request_list(conf.config['apiurl'], devloc, args[1])
1698
1699
1700
1701     @cmdln.option('-f', '--force', action='store_true',
1702                         help='deletes a package or an empty project')
1703     def do_rdelete(self, subcmd, opts, *args):
1704         """${cmd_name}: Delete a project or packages on the server.
1705
1706         As a safety measure, project must be empty (i.e., you need to delete all
1707         packages first). If you are sure that you want to remove this project and all
1708         its packages use \'--force\' switch.
1709
1710         usage:
1711            osc rdelete -f PROJECT
1712            osc rdelete PROJECT PACKAGE [PACKAGE ...]
1713
1714         ${cmd_option_list}
1715         """
1716
1717         args = slash_split(args)
1718         if len(args) < 1:
1719             raise oscerr.WrongArgs('Missing argument.')
1720         prj = args[0]
1721         pkgs = args[1:]
1722
1723         if pkgs:
1724             for pkg in pkgs:
1725                # careful: if pkg is an empty string, the package delete request results
1726                # into a project delete request - which works recursively...
1727                 if pkg:
1728                     delete_package(conf.config['apiurl'], prj, pkg)
1729         elif len(meta_get_packagelist(conf.config['apiurl'], prj)) >= 1 and not opts.force:
1730             print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
1731                                 'If you are sure that you want to remove this project and all its ' \
1732                                 'packages use the \'--force\' switch'
1733             sys.exit(1)
1734         else:
1735             delete_project(conf.config['apiurl'], prj)
1736
1737     @cmdln.hide(1)
1738     def do_deletepac(self, subcmd, opts, *args):
1739         print """${cmd_name} is obsolete !
1740
1741                  Please use either
1742                    osc delete       for checked out packages or projects
1743                  or
1744                    osc rdelete      for server side operations."""
1745
1746         sys.exit(1)
1747
1748     @cmdln.hide(1)
1749     @cmdln.option('-f', '--force', action='store_true',
1750                         help='deletes a project and its packages')
1751     def do_deleteprj(self, subcmd, opts, project):
1752         """${cmd_name} is obsolete !
1753
1754                  Please use
1755                    osc rdelete PROJECT
1756         """
1757         sys.exit(1)
1758
1759     @cmdln.alias('metafromspec')
1760     @cmdln.option('', '--specfile', metavar='FILE',
1761                       help='Path to specfile. (if you pass more than working copy this option is ignored)')
1762     def do_updatepacmetafromspec(self, subcmd, opts, *args):
1763         """${cmd_name}: Update package meta information from a specfile
1764
1765         ARG, if specified, is a package working copy.
1766
1767         ${cmd_usage}
1768         ${cmd_option_list}
1769         """
1770
1771         args = parseargs(args)
1772         if opts.specfile and len(args) == 1:
1773             specfile = opts.specfile
1774         else:
1775             specfile = None
1776         pacs = findpacs(args)
1777         for p in pacs:
1778             p.read_meta_from_spec(specfile)
1779             p.update_package_meta()
1780
1781
1782     @cmdln.alias('di')
1783     @cmdln.option('-c', '--change', metavar='rev',
1784                         help='the change made by revision rev (like -r rev-1:rev).'
1785                              'If rev is negative this is like -r rev:rev-1.')
1786     @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
1787                         help='If rev1 is specified it will compare your working copy against '
1788                              'the revision (rev1) on the server. '
1789                              'If rev1 and rev2 are specified it will compare rev1 against rev2 '
1790                              '(NOTE: changes in your working copy are ignored in this case)')
1791     @cmdln.option('-p', '--plain', action='store_true',
1792                         help='output the diff in plain (not unified) diff format')
1793     def do_diff(self, subcmd, opts, *args):
1794         """${cmd_name}: Generates a diff
1795
1796         Generates a diff, comparing local changes against the repository
1797         server.
1798
1799         ARG, specified, is a filename to include in the diff.
1800
1801         ${cmd_usage}
1802         ${cmd_option_list}
1803         """
1804
1805         args = parseargs(args)
1806         pacs = findpacs(args)
1807
1808         if opts.change:
1809             try:
1810                 rev = int(opts.change)
1811                 if rev > 0:
1812                     rev1 = rev - 1
1813                     rev2 = rev
1814                 elif rev < 0:
1815                     rev1 = -rev
1816                     rev2 = -rev - 1
1817                 else:
1818                     return
1819             except:
1820                 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1821                 return
1822         else:
1823             rev1, rev2 = parseRevisionOption(opts.revision)
1824         diff = ''
1825         for pac in pacs:
1826             if not rev2:
1827                 diff += ''.join(make_diff(pac, rev1))
1828             else:
1829                 diff += server_diff(pac.apiurl, pac.prjname, pac.name, rev1,
1830                                     pac.prjname, pac.name, rev2, not opts.plain)
1831         if len(diff) > 0:
1832             print diff
1833
1834
1835     @cmdln.option('--oldprj', metavar='OLDPRJ',
1836                   help='project to compare against'
1837                   ' (deprecated, use 3 argument form)')
1838     @cmdln.option('--oldpkg', metavar='OLDPKG',
1839                   help='package to compare against'
1840                   ' (deprecated, use 3 argument form)')
1841     @cmdln.option('-r', '--revision', metavar='N[:M]',
1842                   help='revision id, where N = old revision and M = new revision')
1843     @cmdln.option('-p', '--plain', action='store_true',
1844                   help='output the diff in plain (not unified) diff format')
1845     @cmdln.option('-c', '--change', metavar='rev',
1846                         help='the change made by revision rev (like -r rev-1:rev). '
1847                              'If rev is negative this is like -r rev:rev-1.')
1848     def do_rdiff(self, subcmd, opts, *args):
1849         """${cmd_name}: Server-side "pretty" diff of two packages
1850
1851         Compares two packages (three or four arguments) or shows the
1852         changes of a specified revision of a package (two arguments)
1853
1854         If no revision is specified the latest revision is used.
1855
1856         Note that this command doesn't return a normal diff (which could be
1857         applied as patch), but a "pretty" diff, which also compares the content
1858         of tarballs.
1859
1860
1861         usage:
1862             osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC]
1863             osc ${cmd_name} PROJECT PACKAGE
1864         ${cmd_option_list}
1865         """
1866
1867         args = slash_split(args)
1868
1869         rev1 = None
1870         rev2 = None
1871
1872         old_project = None
1873         old_package = None
1874         new_project = None
1875         new_package = None
1876
1877         if len(args) == 2:
1878             new_project = args[0]
1879             new_package = args[1]
1880             if opts.oldprj:
1881                 old_project = opts.oldprj
1882             if opts.oldpkg:
1883                 old_package = opts.oldpkg
1884         elif len(args) == 3 or len(args) == 4:
1885             if opts.oldprj or opts.oldpkg:
1886                 raise oscerr.WrongArgs('--oldpkg and --oldprj are only valid with two arguments')
1887             old_project = args[0]
1888             new_package = old_package = args[1]
1889             new_project = args[2]
1890             if len(args) == 4:
1891                 new_package = args[3]
1892         else:
1893             raise oscerr.WrongArgs('Wrong number of arguments')
1894
1895
1896         if opts.change:
1897             try:
1898                 rev = int(opts.change)
1899                 if rev > 0:
1900                     rev1 = rev - 1
1901                     rev2 = rev
1902                 elif rev < 0:
1903                     rev1 = -rev
1904                     rev2 = -rev - 1
1905                 else:
1906                     return
1907             except:
1908                 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1909                 return
1910         else:
1911             if opts.revision:
1912                 rev1, rev2 = parseRevisionOption(opts.revision)
1913
1914         rdiff = server_diff(conf.config['apiurl'],
1915                             old_project, old_package, rev1,
1916                             new_project, new_package, rev2, not opts.plain)
1917
1918         print rdiff
1919
1920     @cmdln.hide(1)
1921     @cmdln.alias('in')
1922     def do_install(self, subcmd, opts, *args):
1923         """${cmd_name}: install a package after build via zypper in -r
1924
1925         Not implemented yet. Use osc repourls,
1926         select the url you best like (standard),
1927         chop off after the last /, this should work with zypper.
1928
1929
1930         ${cmd_usage}
1931         ${cmd_option_list}
1932         """
1933
1934         args = slash_split(args)
1935         args = expand_proj_pack(args)
1936
1937         ## FIXME: 
1938         ## if there is only one argument, and it ends in .ymp
1939         ## then fetch it, Parse XML to get the first 
1940         ##  metapackage.group.repositories.repository.url
1941         ## and construct zypper cmd's for all
1942         ##  metapackage.group.software.item.name
1943         ##
1944         ## if args[0] is already an url, the use it as is.
1945
1946         cmd = "sudo zypper -p http://download.opensuse.org/repositories/%s/%s --no-refresh -v in %s" % (re.sub(':',':/',args[0]), 'openSUSE_11.1', args[1])
1947         print self.do_install.__doc__
1948         print "Example: \n" + cmd
1949
1950
1951     def do_repourls(self, subcmd, opts, *args):
1952         """${cmd_name}: Shows URLs of .repo files
1953
1954         Shows URLs on which to access the project .repos files (yum-style
1955         metadata) on download.opensuse.org.
1956
1957         usage:
1958            osc repourls [PROJECT]
1959
1960         ${cmd_option_list}
1961         """
1962
1963         apiurl = conf.config['apiurl']
1964
1965         if len(args) == 1:
1966             project = args[0]
1967         elif len(args) == 0:
1968             project = store_read_project('.')
1969             apiurl = store_read_apiurl('.')
1970         else:
1971             raise oscerr.WrongArgs('Wrong number of arguments')
1972
1973         # XXX: API should somehow tell that
1974         url_tmpl = 'http://download.opensuse.org/repositories/%s/%s/%s.repo'
1975         repos = get_repositories_of_project(apiurl, project)
1976         for repo in repos:
1977             print url_tmpl % (project.replace(':', ':/'), repo, project)
1978
1979
1980     @cmdln.option('-r', '--revision', metavar='rev',
1981                         help='checkout the specified revision. '
1982                              'NOTE: if you checkout the complete project '
1983                              'this option is ignored!')
1984     @cmdln.option('-e', '--expand-link', action='store_true',
1985                         help='if a package is a link, check out the expanded '
1986                              'sources (no-op, since this became the default)')
1987     @cmdln.option('-u', '--unexpand-link', action='store_true',
1988                         help='if a package is a link, check out the _link file ' \
1989                              'instead of the expanded sources')
1990     @cmdln.option('-c', '--current-dir', action='store_true',
1991                         help='place PACKAGE folder in the current directory' \
1992                              'instead of a PROJECT/PACKAGE directory')
1993     @cmdln.option('-s', '--source-service-files', action='store_true',
1994                         help='server side generated files of source services' \
1995                              'gets downloaded as well' )
1996     @cmdln.alias('co')
1997     def do_checkout(self, subcmd, opts, *args):
1998         """${cmd_name}: Check out content from the repository
1999
2000         Check out content from the repository server, creating a local working
2001         copy.
2002
2003         When checking out a single package, the option --revision can be used
2004         to specify a revision of the package to be checked out.
2005
2006         When a package is a source link, then it will be checked out in
2007         expanded form. If --unexpand-link option is used, the checkout will
2008         instead produce the raw _link file plus patches.
2009
2010         usage:
2011             osc co PROJECT [PACKAGE] [FILE]
2012                osc co PROJECT                    # entire project
2013                osc co PROJECT PACKAGE            # a package
2014                osc co PROJECT PACKAGE FILE       # single file -> to current dir
2015
2016             while inside a project directory:
2017                osc co PACKAGE                    # check out PACKAGE from project
2018
2019         ${cmd_option_list}
2020         """
2021
2022         if opts.unexpand_link:
2023             expand_link = False
2024         else:
2025             expand_link = True
2026         if opts.source_service_files:
2027             service_files = True
2028         else:
2029             service_files = False
2030
2031         args = slash_split(args)
2032         project = package = filename = None
2033         apiurl = conf.config['apiurl']
2034         try:
2035             project = project_dir = args[0]
2036             package = args[1]
2037             filename = args[2]
2038         except:
2039             pass
2040
2041         if args and len(args) == 1:
2042             localdir = os.getcwd()
2043             if is_project_dir(localdir):
2044                 project = store_read_project(localdir)
2045                 project_dir = localdir
2046                 package = args[0]
2047                 apiurl = store_read_apiurl(localdir)
2048
2049         rev, dummy = parseRevisionOption(opts.revision)
2050         if rev==None:
2051             rev="latest"
2052
2053         if rev and rev != "latest" and not checkRevision(project, package, rev):
2054             print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2055             sys.exit(1)
2056
2057         if filename:
2058             get_source_file(apiurl, project, package, filename, revision=rev, progress_obj=self.download_progress)
2059
2060         elif package:
2061             if opts.current_dir:
2062                 project_dir = None
2063             checkout_package(apiurl, project, package, rev, expand_link=expand_link, \
2064                              prj_dir=project_dir, service_files=service_files, progress_obj=self.download_progress)
2065             print_request_list(apiurl, project, package)
2066
2067         elif project:
2068             prj_dir = project
2069             if sys.platform[:3] == 'win':
2070                 prj_dir = prj_dir.replace(':', ';')
2071             if os.path.exists(prj_dir):
2072                 sys.exit('osc: project \'%s\' already exists' % project)
2073
2074             # check if the project does exist (show_project_meta will throw an exception)
2075             show_project_meta(apiurl, project)
2076
2077             init_project_dir(apiurl, prj_dir, project)
2078             print statfrmt('A', prj_dir)
2079
2080             # all packages
2081             for package in meta_get_packagelist(apiurl, project):
2082                 try:
2083                     checkout_package(apiurl, project, package, expand_link = expand_link, \
2084                                      prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress)
2085                 except oscerr.LinkExpandError, e:
2086                     print >>sys.stderr, 'Link cannot be expanded:\n', e
2087                     print >>sys.stderr, 'Use "osc repairlink" for fixing merge conflicts:\n'
2088                     # check out in unexpanded form at least
2089                     checkout_package(apiurl, project, package, expand_link = False, \
2090                                      prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress)
2091             print_request_list(apiurl, project)
2092
2093         else:
2094             raise oscerr.WrongArgs('Missing argument.\n\n' \
2095                   + self.get_cmd_help('checkout'))
2096
2097
2098     @cmdln.option('-q', '--quiet', action='store_true',
2099                         help='print as little as possible')
2100     @cmdln.option('-v', '--verbose', action='store_true',
2101                         help='print extra information')
2102     @cmdln.alias('st')
2103     def do_status(self, subcmd, opts, *args):
2104         """${cmd_name}: Show status of files in working copy
2105
2106         Show the status of files in a local working copy, indicating whether
2107         files have been changed locally, deleted, added, ...
2108
2109         The first column in the output specifies the status and is one of the
2110         following characters:
2111           ' ' no modifications
2112           'A' Added
2113           'C' Conflicted
2114           'D' Deleted
2115           'M' Modified
2116           '?' item is not under version control
2117           '!' item is missing (removed by non-osc command) or incomplete
2118
2119         examples:
2120           osc st
2121           osc st <directory>
2122           osc st file1 file2 ...
2123
2124         usage:
2125             osc status [OPTS] [PATH...]
2126         ${cmd_option_list}
2127         """
2128
2129         args = parseargs(args)
2130
2131         # storage for single Package() objects
2132         pacpaths = []
2133         # storage for a project dir ( { prj_instance : [ package objects ] } )
2134         prjpacs = {}
2135         for arg in args:
2136             # when 'status' is run inside a project dir, it should
2137             # stat all packages existing in the wc
2138             if is_project_dir(arg):
2139                 prj = Project(arg, False)
2140
2141                 if conf.config['do_package_tracking']:
2142                     prjpacs[prj] = []
2143                     for pac in prj.pacs_have:
2144                         # we cannot create package objects if the dir does not exist
2145                         if not pac in prj.pacs_broken:
2146                             prjpacs[prj].append(os.path.join(arg, pac))
2147                 else:
2148                     pacpaths += [arg + '/' + n for n in prj.pacs_have]
2149             elif is_package_dir(arg):
2150                 pacpaths.append(arg)
2151             elif os.path.isfile(arg):
2152                 pacpaths.append(arg)
2153             else:
2154                 msg = '\'%s\' is neither a project or a package directory' % arg
2155                 raise oscerr.NoWorkingCopy, msg
2156         lines = []
2157         # process single packages
2158         lines = getStatus(findpacs(pacpaths), None, opts.verbose, opts.quiet)
2159         # process project dirs
2160         for prj, pacs in prjpacs.iteritems():
2161             lines += getStatus(findpacs(pacs), prj, opts.verbose, opts.quiet)
2162         if lines:
2163             print '\n'.join(lines)
2164
2165
2166     def do_add(self, subcmd, opts, *args):
2167         """${cmd_name}: Mark files to be added upon the next commit
2168
2169         usage:
2170             osc add FILE [FILE...]
2171         ${cmd_option_list}
2172         """
2173         if not args:
2174             raise oscerr.WrongArgs('Missing argument.\n\n' \
2175                   + self.get_cmd_help('add'))
2176
2177         filenames = parseargs(args)
2178         addFiles(filenames)
2179
2180
2181     def do_mkpac(self, subcmd, opts, *args):
2182         """${cmd_name}: Create a new package under version control
2183
2184         usage:
2185             osc mkpac new_package
2186         ${cmd_option_list}
2187         """
2188         if not conf.config['do_package_tracking']:
2189             print >>sys.stderr, "to use this feature you have to enable \'do_package_tracking\' " \
2190                                 "in the [general] section in the configuration file"
2191             sys.exit(1)
2192
2193         if len(args) != 1:
2194             raise oscerr.WrongArgs('Wrong number of arguments.')
2195
2196         createPackageDir(args[0])
2197
2198     @cmdln.option('-r', '--recursive', action='store_true',
2199                         help='If CWD is a project dir then scan all package dirs as well')
2200     @cmdln.alias('ar')
2201     def do_addremove(self, subcmd, opts, *args):
2202         """${cmd_name}: Adds new files, removes disappeared files
2203
2204         Adds all files new in the local copy, and removes all disappeared files.
2205
2206         ARG, if specified, is a package working copy.
2207
2208         ${cmd_usage}
2209         ${cmd_option_list}
2210         """
2211
2212         args = parseargs(args)
2213         arg_list = args[:]
2214         for arg in arg_list:
2215             if is_project_dir(arg) and conf.config['do_package_tracking']:
2216                 prj = Project(arg, False)
2217                 for pac in prj.pacs_unvers:
2218                     pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2219                     if os.path.isdir(pac_dir):
2220                         addFiles([pac_dir], prj)
2221                 for pac in prj.pacs_broken:
2222                     if prj.get_state(pac) != 'D':
2223                         prj.set_state(pac, 'D')
2224                         print statfrmt('D', getTransActPath(os.path.join(prj.dir, pac)))
2225                 if opts.recursive:
2226                     for pac in prj.pacs_have:
2227                         state = prj.get_state(pac)
2228                         if state != None and state != 'D':
2229                             pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2230                             args.append(pac_dir)
2231                 args.remove(arg)
2232                 prj.write_packages()
2233             elif is_project_dir(arg):
2234                 print >>sys.stderr, 'osc: addremove is not supported in a project dir unless ' \
2235                                     '\'do_package_tracking\' is enabled in the configuration file'
2236                 sys.exit(1)
2237
2238         pacs = findpacs(args)
2239         for p in pacs:
2240             p.todo = p.filenamelist + p.filenamelist_unvers
2241
2242             for filename in p.todo:
2243                 if os.path.isdir(filename):
2244                     continue
2245                 # ignore foo.rXX, foo.mine for files which are in 'C' state
2246                 if os.path.splitext(filename)[0] in p.in_conflict:
2247                     continue
2248                 state = p.status(filename)
2249
2250                 if state == '?':
2251                     # TODO: should ignore typical backup files suffix ~ or .orig
2252                     p.addfile(filename)
2253                     print statfrmt('A', getTransActPath(os.path.join(p.dir, filename)))
2254                 elif state == '!':
2255                     p.put_on_deletelist(filename)
2256                     p.write_deletelist()
2257                     os.unlink(os.path.join(p.storedir, filename))
2258                     print statfrmt('D', getTransActPath(os.path.join(p.dir, filename)))
2259
2260
2261
2262     @cmdln.alias('ci')
2263     @cmdln.alias('checkin')
2264     @cmdln.option('-m', '--message', metavar='TEXT',
2265                   help='specify log message TEXT')
2266     @cmdln.option('-F', '--file', metavar='FILE',
2267                   help='read log message from FILE')
2268     @cmdln.option('-f', '--force', default=False, action="store_true",
2269                   help='force commit - do not tests a file list')
2270     def do_commit(self, subcmd, opts, *args):
2271         """${cmd_name}: Upload content to the repository server
2272
2273         Upload content which is changed in your working copy, to the repository
2274         server.
2275
2276         Optionally checks the state of a working copy, if found a file with
2277         unknown state, it requests an user input:
2278          * skip - don't change anything, just move to another file
2279          * remove - remove a file from dir
2280          * edit file list - edit filelist using EDITOR
2281          * commit - don't check anything and commit package
2282          * abort - abort commit - this is default value
2283         This can be supressed by check_filelist config item, or -f/--force
2284         command line option.
2285
2286         examples:
2287            osc ci                   # current dir
2288            osc ci <dir>
2289            osc ci file1 file2 ...
2290
2291         ${cmd_usage}
2292         ${cmd_option_list}
2293         """
2294
2295         args = parseargs(args)
2296
2297         msg = ''
2298         if opts.message:
2299             msg = opts.message
2300         elif opts.file:
2301             try:
2302                 msg = open(opts.file).read()
2303             except:
2304                 sys.exit('could not open file \'%s\'.' % opts.file)
2305
2306         arg_list = args[:]
2307         for arg in arg_list:
2308             if conf.config['do_package_tracking'] and is_project_dir(arg):
2309                 Project(arg).commit(msg=msg)
2310                 if not msg:
2311                     msg = edit_message()
2312                 args.remove(arg)
2313
2314         pacs = findpacs(args)
2315
2316         if conf.config['check_filelist'] and not opts.force:
2317             check_filelist_before_commit(pacs)
2318
2319         if not msg:
2320             template = store_read_file(os.path.abspath('.'), '_commit_msg')
2321             # open editor for commit message
2322             # but first, produce status and diff to append to the template
2323             footer = diffs = []
2324             lines = []
2325             for pac in pacs:
2326                 changed = getStatus([pac], quiet=True)
2327                 if changed:
2328                     footer += changed
2329                     diffs += ['\nDiff for working copy: %s' % pac.dir]
2330                     diffs += make_diff(pac, 0)
2331                     lines.extend(get_commit_message_template(pac))
2332             if template == None:
2333                 template='\n'.join(lines)
2334             # if footer is empty, there is nothing to commit, and no edit needed.
2335             if footer:
2336                 msg = edit_message(footer='\n'.join(footer), template=template)
2337
2338             if msg:
2339                 store_write_string(os.path.abspath('.'), '_commit_msg', msg)
2340             else:
2341                 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2342
2343         if conf.config['do_package_tracking'] and len(pacs) > 0:
2344             prj_paths = {}
2345             single_paths = []
2346             files = {}
2347             # it is possible to commit packages from different projects at the same
2348             # time: iterate over all pacs and put each pac to the right project in the dict
2349             for pac in pacs:
2350                 path = os.path.normpath(os.path.join(pac.dir, os.pardir))
2351                 if is_project_dir(path):
2352                     pac_path = os.path.basename(os.path.normpath(pac.absdir))
2353                     prj_paths.setdefault(path, []).append(pac_path)
2354                     files[pac_path] = pac.todo
2355                 else:
2356                     single_paths.append(pac.dir)
2357             for prj, packages in prj_paths.iteritems():
2358                 Project(prj).commit(tuple(packages), msg, files)
2359             for pac in single_paths:
2360                 Package(pac).commit(msg)
2361         else:
2362             for p in pacs:
2363                 p.commit(msg)
2364
2365         store_unlink_file(os.path.abspath('.'), '_commit_msg')
2366
2367     @cmdln.option('-r', '--revision', metavar='REV',
2368                         help='update to specified revision (this option will be ignored '
2369                              'if you are going to update the complete project or more than '
2370                              'one package)')
2371     @cmdln.option('-u', '--unexpand-link', action='store_true',
2372                         help='if a package is an expanded link, update to the raw _link file')
2373     @cmdln.option('-e', '--expand-link', action='store_true',
2374                         help='if a package is a link, update to the expanded sources')
2375     @cmdln.option('-s', '--source-service-files', action='store_true',
2376                         help='Use server side generated sources instead of local generation.' )
2377     @cmdln.alias('up')
2378     def do_update(self, subcmd, opts, *args):
2379         """${cmd_name}: Update a working copy
2380
2381         examples:
2382
2383         1. osc up
2384                 If the current working directory is a package, update it.
2385                 If the directory is a project directory, update all contained
2386                 packages, AND check out newly added packages.
2387
2388                 To update only checked out packages, without checking out new
2389                 ones, you might want to use "osc up *" from within the project
2390                 dir.
2391
2392         2. osc up PAC
2393                 Update the packages specified by the path argument(s)
2394
2395         When --expand-link is used with source link packages, the expanded
2396         sources will be checked out. Without this option, the _link file and
2397         patches will be checked out. The option --unexpand-link can be used to
2398         switch back to the "raw" source with a _link file plus patch(es).
2399
2400         ${cmd_usage}
2401         ${cmd_option_list}
2402         """
2403
2404         if (opts.expand_link and opts.unexpand_link) \
2405             or (opts.expand_link and opts.revision) \
2406             or (opts.unexpand_link and opts.revision):
2407             raise oscerr.WrongOptions('Sorry, the options --expand-link, --unexpand-link and '
2408                      '--revision are mutually exclusive.')
2409
2410         if opts.source_service_files: service_files = True
2411         else: service_files = False
2412
2413         args = parseargs(args)
2414         arg_list = args[:]
2415
2416         for arg in arg_list:
2417             if is_project_dir(arg):
2418                 prj = Project(arg, progress_obj=self.download_progress)
2419
2420                 if conf.config['do_package_tracking']:
2421                     prj.update(expand_link=opts.expand_link,
2422                                unexpand_link=opts.unexpand_link)
2423                     args.remove(arg)
2424                 else:
2425                     # if not tracking package, and 'update' is run inside a project dir,
2426                     # it should do the following:
2427                     # (a) update all packages
2428                     args += prj.pacs_have
2429                     # (b) fetch new packages
2430                     prj.checkout_missing_pacs(expand_link = not opts.unexpand_link)
2431                     args.remove(arg)
2432                 print_request_list(prj.apiurl, prj.name)
2433
2434         args.sort()
2435         pacs = findpacs(args, progress_obj=self.download_progress)
2436
2437         if opts.revision and len(args) == 1:
2438             rev, dummy = parseRevisionOption(opts.revision)
2439             if not checkRevision(pacs[0].prjname, pacs[0].name, rev, pacs[0].apiurl):
2440                 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2441                 sys.exit(1)
2442         else:
2443             rev = None
2444
2445         for p in pacs:
2446             if len(pacs) > 1:
2447                 print 'Updating %s' % p.name
2448
2449             # FIXME: ugly workaround for #399247
2450             if opts.expand_link or opts.unexpand_link:
2451                 if [ i for i in p.filenamelist+p.filenamelist_unvers if p.status(i) != ' ' and p.status(i) != '?']:
2452                     print >>sys.stderr, 'osc: cannot expand/unexpand because your working ' \
2453                                         'copy has local modifications.\nPlease revert/commit them ' \
2454                                         'and try again.'
2455                     sys.exit(1)
2456
2457             if not rev:
2458                 if opts.expand_link and p.islink() and not p.isexpanded():
2459                     if p.haslinkerror():
2460                         try:
2461                             rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev)
2462                         except:
2463                             rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev, linkrev="base")
2464                             p.mark_frozen()
2465                     else:
2466                         rev = p.linkinfo.xsrcmd5
2467                     print 'Expanding to rev', rev
2468                 elif opts.unexpand_link and p.islink() and p.isexpanded():
2469                     print 'Unexpanding to rev', p.linkinfo.lsrcmd5
2470                     rev = p.linkinfo.lsrcmd5
2471                 elif p.islink() and p.isexpanded():
2472                     rev = p.latest_rev()
2473
2474             p.update(rev, service_files)
2475             if opts.unexpand_link:
2476                 p.unmark_frozen()
2477             rev = None
2478             print_request_list(p.apiurl, p.prjname, p.name)
2479
2480
2481     @cmdln.option('-f', '--force', action='store_true',
2482                         help='forces removal of entire package and its files')
2483     @cmdln.alias('rm')
2484     @cmdln.alias('del')
2485     @cmdln.alias('remove')
2486     def do_delete(self, subcmd, opts, *args):
2487         """${cmd_name}: Mark files or package directories to be deleted upon the next 'checkin'
2488
2489         usage:
2490             cd .../PROJECT/PACKAGE
2491             osc delete FILE [...]
2492             cd .../PROJECT
2493             osc delete PACKAGE [...]
2494
2495         This command works on check out copies. Use "rdelete" for working on server
2496         side only. This is needed for removing the entire project.
2497
2498         As a safety measure, projects must be empty (i.e., you need to delete all
2499         packages first).
2500
2501         If you are sure that you want to remove a package and all
2502         its files use \'--force\' switch. Sometimes this also works without --force.
2503
2504         ${cmd_option_list}
2505         """
2506
2507         if not args:
2508             raise oscerr.WrongArgs('Missing argument.\n\n' \
2509                   + self.get_cmd_help('delete'))
2510
2511         args = parseargs(args)
2512         # check if args contains a package which was removed by
2513         # a non-osc command and mark it with the 'D'-state
2514         arg_list = args[:]
2515         for i in arg_list:
2516             if not os.path.exists(i):
2517                 prj_dir, pac_dir = getPrjPacPaths(i)
2518                 if is_project_dir(prj_dir):
2519                     prj = Project(prj_dir, False)
2520                     if i in prj.pacs_broken:
2521                         if prj.get_state(i) != 'A':
2522                             prj.set_state(pac_dir, 'D')
2523                         else:
2524                             prj.del_package_node(i)
2525                         print statfrmt('D', getTransActPath(i))
2526                         args.remove(i)
2527                         prj.write_packages()
2528         pacs = findpacs(args)
2529
2530         for p in pacs:
2531             if not p.todo:
2532                 prj_dir, pac_dir = getPrjPacPaths(p.absdir)
2533                 if is_project_dir(prj_dir):
2534                     if conf.config['do_package_tracking']:
2535                         prj = Project(prj_dir, False)
2536                         prj.delPackage(p, opts.force)
2537                     else:
2538                         print "WARNING: package tracking is disabled, operation skipped !"
2539             else:
2540                 pathn = getTransActPath(p.dir)
2541                 for filename in p.todo:
2542                     ret, state = p.delete_file(filename, opts.force)
2543                     if ret:
2544                         print statfrmt('D', os.path.join(pathn, filename))
2545                         continue
2546                     if state == '?':
2547                         sys.exit('\'%s\' is not under version control' % filename)
2548                     elif state in ['A', 'M'] and not opts.force:
2549                         sys.exit('\'%s\' has local modifications (use --force to remove this file)' % filename)
2550
2551
2552     def do_resolved(self, subcmd, opts, *args):
2553         """${cmd_name}: Remove 'conflicted' state on working copy files
2554
2555         If an upstream change can't be merged automatically, a file is put into
2556         in 'conflicted' ('C') state. Within the file, conflicts are marked with
2557         special <<<<<<< as well as ======== and >>>>>>> lines.
2558
2559         After manually resolving all conflicting parts, use this command to
2560         remove the 'conflicted' state.
2561
2562         Note:  this subcommand does not semantically resolve conflicts or
2563         remove conflict markers; it merely removes the conflict-related
2564         artifact files and allows PATH to be committed again.
2565
2566         usage:
2567             osc resolved FILE [FILE...]
2568         ${cmd_option_list}
2569         """
2570
2571         if not args:
2572             raise oscerr.WrongArgs('Missing argument.\n\n' \
2573                   + self.get_cmd_help('resolved'))
2574
2575         args = parseargs(args)
2576         pacs = findpacs(args)
2577
2578         for p in pacs:
2579             for filename in p.todo:
2580                 print 'Resolved conflicted state of "%s"' % filename
2581                 p.clear_from_conflictlist(filename)
2582
2583
2584     @cmdln.alias('platforms')
2585     def do_repositories(self, subcmd, opts, *args):
2586         """${cmd_name}: Shows available repositories
2587
2588         Examples:
2589         1. osc repositories
2590                 Shows all available repositories/build targets
2591
2592         2. osc repositories <project>
2593                 Shows the configured repositories/build targets of a project
2594
2595         ${cmd_usage}
2596         ${cmd_option_list}
2597         """
2598
2599         if args:
2600             project = args[0]
2601             print '\n'.join(get_repositories_of_project(conf.config['apiurl'], project))
2602         else:
2603             print '\n'.join(get_repositories(conf.config['apiurl']))
2604
2605
2606     @cmdln.hide(1)
2607     def do_results_meta(self, subcmd, opts, *args):
2608         print "Command results_meta is obsolete. Please use: osc results --xml"
2609         sys.exit(1)
2610
2611     @cmdln.hide(1)
2612     @cmdln.option('-l', '--last-build', action='store_true',
2613                         help='show last build results (succeeded/failed/unknown)')
2614     @cmdln.option('-r', '--repo', action='append', default = [],
2615                         help='Show results only for specified repo(s)')
2616     @cmdln.option('-a', '--arch', action='append', default = [],
2617                         help='Show results only for specified architecture(s)')
2618     @cmdln.option('', '--xml', action='store_true',
2619                         help='generate output in XML (former results_meta)')
2620     def do_rresults(self, subcmd, opts, *args):
2621         print "Command rresults is obsolete. Running 'osc results' instead"
2622         self.do_results('results', opts, *args)
2623         sys.exit(1)
2624
2625
2626     @cmdln.option('-f', '--force', action='store_true', default=False,
2627                         help="Don't ask and delete files")
2628     def do_rremove(self, subcmd, opts, project, package, *files):
2629         """${cmd_name}: Remove source files from selected package
2630
2631         ${cmd_usage}
2632         ${cmd_option_list}
2633         """
2634
2635         if len(files) == 0:
2636             if not '/' in project:
2637                 raise oscerr.WrongArgs("Missing operand, type osc help rremove for help")
2638             else:
2639                 files = (package, )
2640                 project, package = project.split('/')
2641
2642         for file in files:
2643             if not opts.force:
2644                 resp = raw_input("rm: remove source file `%s' from `%s/%s'? (yY|nN) " % (file, project, package))
2645                 if resp not in ('y', 'Y'):
2646                     continue
2647             try:
2648                 delete_files(conf.config['apiurl'], project, package, (file, ))
2649             except urllib2.HTTPError, e:
2650                 if opts.force:
2651                     print >>sys.stderr, e
2652                     body = e.read()
2653                     if e.code in [ 400, 403, 404, 500 ]:
2654                         if '<summary>' in body:
2655                             msg = body.split('<summary>')[1]
2656                             msg = msg.split('</summary>')[0]
2657                             print >>sys.stderr, msg
2658                 else:
2659                     raise e
2660
2661     @cmdln.alias('r')
2662     @cmdln.option('-l', '--last-build', action='store_true',
2663                         help='show last build results (succeeded/failed/unknown)')
2664     @cmdln.option('-r', '--repo', action='append', default = [],
2665                         help='Show results only for specified repo(s)')
2666     @cmdln.option('-a', '--arch', action='append', default = [],
2667                         help='Show results only for specified architecture(s)')
2668     @cmdln.option('', '--xml', action='store_true',
2669                         help='generate output in XML (former results_meta)')
2670     def do_results(self, subcmd, opts, *args):
2671         """${cmd_name}: Shows the build results of a package
2672
2673         Usage:
2674             osc results (inside working copy)
2675             osc results remote_project remote_package
2676
2677         ${cmd_option_list}
2678         """
2679
2680         args = slash_split(args)
2681
2682         apiurl = conf.config['apiurl']
2683         if len(args) == 0:
2684             wd = os.curdir
2685             if is_project_dir(wd):
2686                 opts.csv = None
2687                 opts.arch = None
2688                 opts.repo = None
2689                 opts.hide_legend = None
2690                 opts.name_filter = None
2691                 opts.status_filter = None
2692                 opts.vertical = None
2693                 self.do_prjresults('prjresults', opts, *args);
2694                 sys.exit(0)
2695             else:
2696                 project = store_read_project(wd)
2697                 package = store_read_package(wd)
2698                 apiurl = store_read_apiurl(wd)
2699         elif len(args) < 2:
2700             raise oscerr.WrongArgs('Too few arguments (required none or two)')
2701         elif len(args) > 2:
2702             raise oscerr.WrongArgs('Too many arguments (required none or two)')
2703         else:
2704             project = args[0]
2705             package = args[1]
2706
2707         if not opts.xml:
2708             func = get_results
2709             delim = '\n'
2710         else:
2711             func = show_results_meta
2712             delim = ''
2713
2714         print delim.join(func(apiurl, project, package, opts.last_build, opts.repo, opts.arch))
2715
2716     # WARNING: this function is also called by do_results. You need to set a default there
2717     #          as well when adding a new option!
2718     @cmdln.option('-q', '--hide-legend', action='store_true',
2719                         help='hide the legend')
2720     @cmdln.option('-c', '--csv', action='store_true',
2721                         help='csv output')
2722     @cmdln.option('-s', '--status-filter', metavar='STATUS',
2723                         help='show only packages with buildstatus STATUS (see legend)')
2724     @cmdln.option('-n', '--name-filter', metavar='EXPR',
2725                         help='show only packages whose names match EXPR')
2726     @cmdln.option('-a', '--arch', metavar='ARCH',
2727                         help='show results only for specified architecture(s)')
2728     @cmdln.option('-r', '--repo', metavar='REPO',
2729                         help='show results only for specified repo(s)')
2730     @cmdln.option('-V', '--vertical', action='store_true',
2731                         help='list packages vertically instead horizontally')
2732     @cmdln.alias('pr')
2733     def do_prjresults(self, subcmd, opts, *args):
2734         """${cmd_name}: Shows project-wide build results
2735
2736         Usage:
2737             osc prjresults (inside working copy)
2738             osc prjresults PROJECT
2739
2740         ${cmd_option_list}
2741         """
2742
2743         if args:
2744             apiurl = conf.config['apiurl']
2745             if len(args) == 1:
2746                 project = args[0]
2747             else:
2748                 raise oscerr.WrongArgs('Wrong number of arguments.')
2749         else:
2750             wd = os.curdir
2751             project = store_read_project(wd)
2752             apiurl = store_read_apiurl(wd)
2753
2754         print '\n'.join(get_prj_results(apiurl, project, hide_legend=opts.hide_legend, csv=opts.csv, status_filter=opts.status_filter, name_filter=opts.name_filter, repo=opts.repo, arch=opts.arch, vertical=opts.vertical))
2755
2756
2757     @cmdln.option('-q', '--hide-legend', action='store_true',
2758                         help='hide the legend')
2759     @cmdln.option('-c', '--csv', action='store_true',
2760                         help='csv output')
2761     @cmdln.option('-s', '--status-filter', metavar='STATUS',
2762                         help='show only packages with buildstatus STATUS (see legend)')
2763     @cmdln.option('-n', '--name-filter', metavar='EXPR',
2764                         help='show only packages whose names match EXPR')
2765
2766     @cmdln.hide(1)
2767     def do_rprjresults(self, subcmd, opts, *args):
2768         print "Command rprjresults is obsolete. Please use 'osc prjresults'"
2769         sys.exit(1)
2770
2771     @cmdln.alias('bl')
2772     @cmdln.option('-s', '--start', metavar='START',
2773                     help='get log starting from the offset')
2774     def do_buildlog(self, subcmd, opts, *args):
2775         """${cmd_name}: Shows the build log of a package
2776
2777         Shows the log file of the build of a package. Can be used to follow the
2778         log while it is being written.
2779         Needs to be called from within a package directory.
2780
2781         The arguments REPOSITORY and ARCH are the first two columns in the 'osc
2782         results' output. If the buildlog url is used buildlog command has the
2783         same behavior as remotebuildlog.
2784
2785         ${cmd_usage} [REPOSITORY ARCH | BUILDLOGURL]
2786         ${cmd_option_list}
2787         """
2788
2789         repository = arch = None
2790
2791         if len(args) == 1 and args[0].startswith('http'):
2792             apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2793         else:
2794             wd = os.curdir
2795             package = store_read_package(wd)
2796             project = store_read_project(wd)
2797             apiurl = store_read_apiurl(wd)
2798
2799         offset=0
2800         if opts.start:
2801             offset = int(opts.start)
2802
2803         if not repository or not arch:
2804             if len(args) < 2:
2805                 self.print_repos()
2806             else:
2807                 repository = args[0]
2808                 arch = args[1]
2809
2810         print_buildlog(apiurl, project, package, repository, arch, offset)
2811
2812
2813     def print_repos(self):
2814             wd = os.curdir
2815             doprint = False
2816             if is_package_dir(wd):
2817                 str = "package"
2818                 doprint = True
2819             elif is_project_dir(wd):
2820                 str = "project"
2821                 doprint = True
2822             
2823             if doprint:
2824                 print 'Valid arguments for this %s are:' % str
2825                 print
2826                 self.do_repos(None, None)
2827                 print
2828             raise oscerr.WrongArgs('Missing arguments')
2829
2830     @cmdln.alias('rbl')
2831     @cmdln.alias('rbuildlog')
2832     @cmdln.option('-s', '--start', metavar='START',
2833                     help='get log starting from the offset')
2834     def do_remotebuildlog(self, subcmd, opts, *args):
2835         """${cmd_name}: Shows the build log of a package
2836
2837         Shows the log file of the build of a package. Can be used to follow the
2838         log while it is being written.
2839
2840         usage:
2841             osc remotebuildlog project package repository arch
2842             or
2843             osc remotebuildlog project/package/repository/arch
2844             or
2845             osc remotebuildlog buildlogurl
2846         ${cmd_option_list}
2847         """
2848         if len(args) == 1 and args[0].startswith('http'):
2849             apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2850         else:
2851             args = slash_split(args)
2852             apiurl = conf.config['apiurl']
2853             if len(args) < 4:
2854                 raise oscerr.WrongArgs('Too few arguments.')
2855             elif len(args) > 4:
2856                 raise oscerr.WrongArgs('Too many arguments.')
2857             else:
2858                 project, package, repository, arch = args
2859         
2860         offset=0
2861         if opts.start:
2862             offset = int(opts.start)
2863
2864         print_buildlog(apiurl, project, package, repository, arch, offset)
2865
2866     @cmdln.alias('lbl')
2867     @cmdln.option('-s', '--start', metavar='START',
2868                   help='get log starting from offset')
2869     def do_localbuildlog(self, subcmd, opts, *args):
2870         """${cmd_name}: Shows the build log of a local buildchroot
2871
2872         usage:
2873             osc lbl [REPOSITORY ARCH]
2874             osc lbl # show log of newest last local build
2875
2876         ${cmd_option_list}
2877         """
2878         if conf.config['build-type']:
2879             # FIXME: raise Exception instead
2880             print >>sys.stderr, 'Not implemented for VMs'
2881             sys.exit(1)
2882
2883         if len(args) == 0:
2884             package = store_read_package('.')
2885             import glob
2886             files = glob.glob(os.path.join(os.getcwd(), store, "_buildinfo-*"))
2887             if not files:
2888                 self.print_repos()
2889                 raise oscerr.WrongArgs('No buildconfig found, please specify repo and arch manually.')
2890             cfg = files[0]
2891             # find newest file
2892             for f in files[1:]:
2893                 if os.stat(f).st_mtime > os.stat(cfg).st_mtime:
2894                     cfg = f
2895             root = ET.parse(cfg).getroot()
2896             project = root.get("project")
2897             repo = root.get("repository")
2898             arch = root.find("arch").text
2899         elif len(args) == 2:
2900             project = store_read_project('.')
2901             package = store_read_package('.')
2902             repo = args[0]
2903             arch = args[1]
2904         else:
2905             if is_package_dir(os.curdir):
2906                 self.print_repos()
2907             raise oscerr.WrongArgs('Wrong number of arguments.')
2908
2909         buildroot = os.environ.get('OSC_BUILD_ROOT', conf.config['build-root'])
2910         buildroot = buildroot % {'project': project, 'package': package,
2911                                  'repo': repo, 'arch': arch}
2912         offset = 0
2913         if opts.start:
2914             offset = int(opts.start)
2915         logfile = os.path.join(buildroot, '.build.log')
2916         if not os.path.isfile(logfile):
2917             raise oscerr.OscIOError(None, 'logfile \'%s\' does not exist' % logfile)
2918         f = open(logfile, 'r')
2919         f.seek(offset)
2920         data = f.read(BUFSIZE)
2921         while len(data):
2922             sys.stdout.write(data)
2923             data = f.read(BUFSIZE)
2924         f.close()
2925
2926     @cmdln.alias('tr')
2927     def do_triggerreason(self, subcmd, opts, *args):
2928         """${cmd_name}: Show reason why a package got triggered to build
2929
2930         The server decides when a package needs to get rebuild, this command
2931         shows the detailed reason for a package. A brief reason is also stored
2932         in the jobhistory, which can be accessed via "osc jobhistory".
2933
2934         Trigger reasons might be:
2935           - new build (never build yet or rebuild manually forced)
2936           - source change (eg. on updating sources)
2937           - meta change (packages which are used for building have changed)
2938           - rebuild count sync (In case that it is configured to sync release numbers)
2939
2940         usage in package or project directory:
2941             osc reason REPOSITORY ARCH
2942             osc reason PROJECT PACKAGE REPOSITORY ARCH
2943
2944         ${cmd_option_list}
2945         """
2946         wd = os.curdir
2947         args = slash_split(args)
2948         project = package = repository = arch = None
2949         
2950         if len(args) < 2:
2951             self.print_repos()
2952
2953         if len(args) == 2: # 2
2954             if is_package_dir('.'):
2955                  package = store_read_package(wd)
2956             else:
2957                  raise oscerr.WrongArgs('package is not specified.')
2958             project = store_read_project(wd)
2959             apiurl = store_read_apiurl(wd)
2960             repository = args[0]
2961             arch = args[1]
2962         elif len(args) == 4:
2963             apiurl = conf.config['apiurl']
2964             project = args[0]
2965             package = args[1]
2966             repository = args[2]
2967             arch = args[3]
2968         else:
2969             raise oscerr.WrongArgs('Too many arguments.')
2970
2971         print apiurl, project, package, repository, arch
2972         xml = show_package_trigger_reason(apiurl, project, package, repository, arch)
2973         root = ET.fromstring(xml)
2974         reason = root.find('explain').text
2975         print reason
2976         if reason == "meta change":
2977            print "changed keys:"
2978            for package in root.findall('packagechange'):
2979                print "  ", package.get('change'), package.get('key')
2980
2981
2982     # FIXME: the new osc syntax should allow to specify multiple packages
2983     # FIXME: the command should optionally use buildinfo data to show all dependencies
2984     @cmdln.alias('whatdependson')
2985     def do_dependson(self, subcmd, opts, *args):
2986         """${cmd_name}: Show the build dependencies
2987
2988         The command dependson and whatdependson can be used to find out what
2989         will be triggered when a certain package changes.
2990         This is no guarantee, since the new build might have changed dependencies.
2991
2992         dependson shows the build dependencies inside of a project, valid for a 
2993         given repository and architecture.
2994         NOTE: to see all binary packages, which can trigger a build you need to 
2995               refer the buildinfo, since this command shows only the dependencies
2996               inside of a project.
2997
2998         The arguments REPOSITORY and ARCH can be taken from the first two columns
2999         of the 'osc repos' output.
3000
3001         usage in package or project directory:
3002             osc dependson REPOSITORY ARCH
3003             osc whatdependson REPOSITORY ARCH
3004
3005         usage:
3006             osc dependson PROJECT [PACKAGE] REPOSITORY ARCH
3007             osc whatdependson PROJECT [PACKAGE] REPOSITORY ARCH
3008
3009         ${cmd_option_list}
3010         """
3011         wd = os.curdir
3012         args = slash_split(args)
3013         project = packages = repository = arch = reverse = None
3014         
3015         if len(args) < 2 and (is_package_dir('.') or is_project_dir('.')):
3016             self.print_repos()
3017
3018         if len(args) > 5:
3019             raise oscerr.WrongArgs('Too many arguments.')
3020
3021         if len(args) < 3: # 2
3022             if is_package_dir('.'):
3023                  packages = [store_read_package(wd)]
3024             elif not is_project_dir('.'):
3025                  raise oscerr.WrongArgs('Project and package is not specified.')
3026             project = store_read_project(wd)
3027             apiurl = store_read_apiurl(wd)
3028             repository = args[0]
3029             arch = args[1]
3030
3031         if len(args) == 3:
3032             apiurl = conf.config['apiurl']
3033             project = args[0]
3034             repository = args[1]
3035             arch = args[2]
3036
3037         if len(args) == 4:
3038             apiurl = conf.config['apiurl']
3039             project = args[0]
3040             packages = [args[1]]
3041             repository = args[2]
3042             arch = args[3]
3043
3044         if subcmd == 'whatdependson':
3045             reverse = 1
3046
3047         xml = get_dependson(apiurl, project, repository, arch, packages, reverse)
3048
3049         root = ET.fromstring(xml)
3050         for package in root.findall('package'):
3051             print package.get('name'), ":"
3052             for dep in package.findall('pkgdep'):
3053                print "  ", dep.text
3054
3055
3056     @cmdln.option('-x', '--extra-pkgs', metavar='PAC', action='append',
3057                   help='Add this package when computing the buildinfo')
3058     def do_buildinfo(self, subcmd, opts, *args):
3059         """${cmd_name}: Shows the build info
3060
3061         Shows the build "info" which is used in building a package.
3062         This command is mostly used internally by the 'build' subcommand.
3063         It needs to be called from within a package directory.
3064
3065         The BUILD_DESCR argument is optional. BUILD_DESCR is a local RPM specfile
3066         or Debian "dsc" file. If specified, it is sent to the server, and the
3067         buildinfo will be based on it. If the argument is not supplied, the
3068         buildinfo is derived from the specfile which is currently on the source
3069         repository server.
3070
3071         The returned data is XML and contains a list of the packages used in
3072         building, their source, and the expanded BuildRequires.
3073
3074         The arguments REPOSITORY and ARCH can be taken from the first two columns
3075         of the 'osc repos' output.
3076
3077         usage:
3078             osc buildinfo REPOSITORY ARCH [BUILD_DESCR]    (in pkg dir)
3079             osc buildinfo PROJECT PACKAGE REPOSITORY ARCH [BUILD_DESCR]
3080
3081         ${cmd_option_list}
3082         """
3083         wd = os.curdir
3084         args = slash_split(args)
3085         
3086         if len(args) < 2 and is_package_dir('.'):
3087             self.print_repos()
3088
3089         if len(args) > 5:
3090             raise oscerr.WrongArgs('Too many arguments.')
3091
3092         if len(args) < 4: # 2 or 3
3093             package = store_read_package(wd)
3094             project = store_read_project(wd)
3095             apiurl = store_read_apiurl(wd)
3096             repository = args[0]
3097             arch = args[1]
3098
3099         if len(args) > 3 and len(args) < 6: # 4 or 5
3100             apiurl = conf.config['apiurl']
3101             project = args[0]
3102             package = args[1]
3103             repository = args[2]
3104             arch = args[3]
3105             # for following specfile detection ...
3106             del args[0]
3107             del args[0]
3108