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