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