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