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