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