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