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