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