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