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