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