- keyring fix
[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://www.opensuse.org/Build_Service_Tutorial
32  * http://www.opensuse.org/Build_Service/CLI
33 .PP
34 You can modify osc commands, or roll you own, via the plugin API:
35  * http://www.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://www.opensuse.org/Build_Service_Tutorial
52     * http://www.opensuse.org/Build_Service/CLI
53
54     You can modify osc commands, or roll you own, via the plugin API:
55     * http://www.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, who 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 to 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.alias("rq")
1031     @cmdln.alias("review")
1032     def do_request(self, subcmd, opts, *args):
1033         """${cmd_name}: Show and modify requests
1034
1035         [See http://en.opensuse.org/Build_Service/Collaboration for information
1036         on this topic.]
1037
1038         This command shows and modifies existing requests. To create new requests
1039         you need to call one of the following:
1040           osc submitrequest
1041           osc deleterequest
1042           osc changedevelrequest
1043         To send low level requests to the buildservice API, use:
1044           osc api
1045
1046         This command has the following sub commands:
1047
1048         "list" lists open requests attached to a project or package or person.
1049         Uses the project/package of the current directory if none of
1050         -M, -U USER, project/package are given.
1051
1052         "log" will show the history of the given ID
1053
1054         "show" will show the request itself, and generate a diff for review, if
1055         used with the --diff option. The keyword show can be omitted if the ID is numeric.
1056
1057         "decline" will change the request state to "declined" and append a
1058         message that you specify with the --message option.
1059
1060         "wipe" will permanently delete a request.
1061
1062         "revoke" will set the request state to "revoked" and append a
1063         message that you specify with the --message option.
1064
1065         "accept" will change the request state to "accepted" and will trigger
1066         the actual submit process. That would normally be a server-side copy of
1067         the source package to the target package.
1068
1069         "checkout" will checkout the request's source package. This only works for "submit" requests.
1070
1071         usage:
1072             osc request list [-M] [-U USER] [-s state] [-D DAYS] [-t type] [-B] [PRJ [PKG]]
1073             osc request log ID
1074             osc request [show] [-d] [-b] ID
1075             osc request accept [-m TEXT] ID
1076             osc request decline [-m TEXT] ID
1077             osc request revoke [-m TEXT] ID
1078             osc request wipe ID
1079             osc request checkout/co ID
1080             osc review accept [-m TEXT] ID
1081             osc review decline [-m TEXT] ID
1082         ${cmd_option_list}
1083         """
1084
1085         args = slash_split(args)
1086
1087         if opts.all and opts.state:
1088             raise oscerr.WrongOptions('Sorry, the options --all and --state ' \
1089                      'are mutually exclusive.')
1090         if opts.mine and opts.user:
1091             raise oscerr.WrongOptions('Sorry, the options --user and --mine ' \
1092                      'are mutually exclusive.')
1093
1094         if not args:
1095             args = [ 'list' ]
1096             opts.mine = 1
1097             if opts.state == '':
1098                 opts.state = 'all'
1099
1100         if opts.state == '':
1101             opts.state = 'new'
1102
1103         cmds = ['list', 'log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co', 'help']
1104         if not args or args[0] not in cmds:
1105             raise oscerr.WrongArgs('Unknown request action %s. Choose one of %s.' \
1106                                                % (args[0],', '.join(cmds)))
1107
1108         cmd = args[0]
1109         del args[0]
1110
1111         if cmd == 'help':
1112             return self.do_help(['help', 'request'])
1113
1114         if cmd in ['wipe']:
1115             min_args, max_args = 1, 1
1116         elif cmd in ['list']:
1117             min_args, max_args = 0, 2
1118         else:
1119             min_args, max_args = 1, 1
1120         if len(args) < min_args:
1121             raise oscerr.WrongArgs('Too few arguments.')
1122         if len(args) > max_args:
1123             raise oscerr.WrongArgs('Too many arguments.')
1124
1125         apiurl = conf.config['apiurl']
1126
1127         if cmd == 'list':
1128             package = None
1129             project = None
1130             if len(args) > 0:
1131                 project = args[0]
1132             elif not opts.mine and not opts.user:
1133                 try:
1134                     project = store_read_project(os.curdir)
1135                     apiurl = store_read_apiurl(os.curdir)
1136                     package = store_read_package(os.curdir)
1137                 except oscerr.NoWorkingCopy:
1138                     pass
1139
1140             if len(args) > 1:
1141                 package = args[1]
1142         elif cmd in ['log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co']:
1143             reqid = args[0]
1144
1145         # list
1146         if cmd == 'list':
1147             states = ('new', 'accepted', 'revoked', 'declined')
1148             state_list = opts.state.split(',')
1149             if opts.state == 'all':
1150                 state_list = ['all']
1151             else:
1152                 for s in state_list:
1153                     if not s in states:
1154                         raise oscerr.WrongArgs('Unknown state \'%s\', try one of %s' % (s, ','.join(states)))
1155             who = ''
1156             if opts.mine:
1157                 who = conf.get_apiurl_usr(apiurl)
1158             if opts.user:
1159                 who = opts.user
1160             if opts.all:
1161                 state_list = ['all']
1162
1163             ## FIXME -B not implemented!
1164             if opts.bugowner:
1165                 if (self.options.debug):
1166                     print 'list: option --bugowner ignored: not impl.'
1167
1168             results = get_request_list(apiurl,
1169                                        project, package, who, state_list, opts.type)
1170             results.sort(reverse=True)
1171             import time
1172             days = opts.days or conf.config['request_list_days']
1173             since = ''
1174             try:
1175                 days = int(days)
1176             except ValueError:
1177                 days = 0
1178             if days > 0:
1179                 since = time.strftime('%Y-%m-%dT%H:%M:%S',time.localtime(time.time()-days*24*3600))
1180
1181             skipped = 0
1182             ## bs has received 2009-09-20 a new xquery compare() function
1183             ## which allows us to limit the list inside of get_request_list
1184             ## That would be much faster for coolo. But counting the remainder
1185             ## would not be possible with current xquery implementation.
1186             ## Workaround: fetch all, and filter on client side.
1187
1188             ## FIXME: date filtering should become implemented on server side
1189             for result in results:
1190                 if days == 0 or result.state.when > since or result.state.name == 'new':
1191                     print result.list_view()
1192                 else:
1193                     skipped += 1
1194             if skipped:
1195                 print "There are %d requests older than %s days.\n" % (skipped, days)
1196
1197         elif cmd == 'log':
1198             for l in get_request_log(conf.config['apiurl'], reqid):
1199                 print l
1200
1201         # show
1202         elif cmd == 'show':
1203             r = get_request(conf.config['apiurl'], reqid)
1204             if opts.brief:
1205                 print r.list_view()
1206             elif opts.interactive or conf.config['request_show_interactive']:
1207                 return request_interactive_review(conf.config['apiurl'], r)
1208             else:
1209                 print r
1210             # fixme: will inevitably fail if the given target doesn't exist
1211             if opts.diff:
1212                 try:
1213                     print server_diff(conf.config['apiurl'],
1214                                       r.actions[0].dst_project, r.actions[0].dst_package, None,
1215                                       r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified)
1216                 except urllib2.HTTPError, e:
1217                     e.osc_msg = 'Diff not possible'
1218                     raise
1219
1220         # checkout
1221         elif cmd == 'checkout' or cmd == 'co':
1222             r = get_request(conf.config['apiurl'], reqid)
1223             submits = [ i for i in r.actions if i.type == 'submit' ]
1224             if not len(submits):
1225                 raise oscerr.WrongArgs('\'checkout\' only works for \'submit\' requests')
1226             checkout_package(conf.config['apiurl'], submits[0].src_project, submits[0].src_package, \
1227                 submits[0].src_rev, expand_link=True, prj_dir=submits[0].src_project)
1228
1229         else:
1230             if not opts.message:
1231                 opts.message = edit_message()
1232             state_map = {'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked'}
1233             # Change review state only
1234             if subcmd == 'review':
1235                 if cmd in ['accept', 'decline']:
1236                     r = change_review_state(conf.config['apiurl'],
1237                             reqid, state_map[cmd], conf.config['user'], '', opts.message or '')
1238                     print r
1239             # Change state of entire request
1240             elif cmd in ['accept', 'decline', 'wipe', 'revoke']:
1241                 r = change_request_state(conf.config['apiurl'],
1242                         reqid, state_map[cmd], opts.message or '')
1243                 print r
1244
1245     # editmeta and its aliases are all depracated
1246     @cmdln.alias("editprj")
1247     @cmdln.alias("createprj")
1248     @cmdln.alias("editpac")
1249     @cmdln.alias("createpac")
1250     @cmdln.alias("edituser")
1251     @cmdln.alias("usermeta")
1252     @cmdln.hide(1)
1253     def do_editmeta(self, subcmd, opts, *args):
1254         """${cmd_name}:
1255
1256         Obsolete command to edit metadata. Use 'meta' now.
1257
1258         See the help output of 'meta'.
1259
1260         """
1261
1262         print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
1263         print >>sys.stderr, 'See \'osc help meta\'.'
1264         #self.do_help([None, 'meta'])
1265         return 2
1266
1267
1268     @cmdln.option('-r', '--revision', metavar='rev',
1269                   help='use the specified revision.')
1270     @cmdln.option('-u', '--unset', action='store_true',
1271                   help='remove revision in link, it will point always to latest revision')
1272     def do_setlinkrev(self, subcmd, opts, *args):
1273         """${cmd_name}: Updates a revision number in a source link.
1274
1275         This command adds or updates a specified revision number in a source link.
1276         The current revision of the source is used, if no revision number is specified.
1277
1278         usage:
1279             osc setlinkrev
1280             osc setlinkrev PROJECT [PACKAGE]
1281         ${cmd_option_list}
1282         """
1283
1284         args = slash_split(args)
1285         apiurl = conf.config['apiurl']
1286         package = None
1287         if len(args) == 0:
1288             p = findpacs(os.curdir)[0]
1289             project = p.prjname
1290             package = p.name
1291             apiurl = p.apiurl
1292             if not p.islink():
1293                 sys.exit('Local directory is no checked out source link package, aborting')
1294         elif len(args) == 2:
1295             project = args[0]
1296             package = args[1]
1297         elif len(args) == 1:
1298             project = args[0]
1299         else:
1300             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1301                   + self.get_cmd_help('setlinkrev'))
1302
1303         if package:
1304             packages = [ package ]
1305         else:
1306             packages = meta_get_packagelist(apiurl, project)
1307
1308         for p in packages:
1309             print "setting revision for package", p
1310             if opts.unset:
1311                 rev=-1
1312             else:
1313                 rev, dummy = parseRevisionOption(opts.revision)
1314             set_link_rev(apiurl, project, p, rev)
1315
1316
1317     def do_linktobranch(self, subcmd, opts, *args):
1318         """${cmd_name}: Convert a package containing a classic link with patch to a branch
1319
1320         This command tells the server to convert a _link with or without a project.diff
1321         to a branch. This is a full copy with a _link file pointing to the branched place.
1322
1323         usage:
1324             osc linktobranch                    # can be used in checked out package
1325             osc linktobranch PROJECT PACKAGE
1326         ${cmd_option_list}
1327         """
1328
1329         args = slash_split(args)
1330         if len(args) == 0:
1331             wd = os.curdir
1332             project = store_read_project(wd)
1333             package = store_read_package(wd)
1334             apiurl = store_read_apiurl(wd)
1335             update_local_dir = True
1336         elif len(args) < 2:
1337             raise oscerr.WrongArgs('Too few arguments (required none or two)')
1338         elif len(args) > 2:
1339             raise oscerr.WrongArgs('Too many arguments (required none or two)')
1340         else:
1341             apiurl = conf.config['apiurl']
1342             project = args[0]
1343             package = args[1]
1344             update_local_dir = False
1345
1346         # execute
1347         link_to_branch(apiurl, project, package)
1348         if update_local_dir:
1349             pac = Package(wd)
1350             pac.update(rev=pac.latest_rev())
1351
1352
1353     @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'],
1354                   help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.')
1355     @cmdln.option('-c', '--current', action='store_true',
1356                   help='link fixed against current revision.')
1357     @cmdln.option('-r', '--revision', metavar='rev',
1358                   help='link the specified revision.')
1359     @cmdln.option('-f', '--force', action='store_true',
1360                   help='overwrite an existing link file if it is there.')
1361     @cmdln.option('-d', '--disable-publish', action='store_true',
1362                   help='disable publishing of the linked package')
1363     def do_linkpac(self, subcmd, opts, *args):
1364         """${cmd_name}: "Link" a package to another package
1365
1366         A linked package is a clone of another package, but plus local
1367         modifications. It can be cross-project.
1368
1369         The DESTPAC name is optional; the source packages' name will be used if
1370         DESTPAC is omitted.
1371
1372         Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
1373
1374         To add a patch, add the patch as file and add it to the _link file.
1375         You can also specify text which will be inserted at the top of the spec file.
1376
1377         See the examples in the _link file.
1378
1379         usage:
1380             osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1381         ${cmd_option_list}
1382         """
1383
1384         args = slash_split(args)
1385
1386         if not args or len(args) < 3:
1387             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1388                   + self.get_cmd_help('linkpac'))
1389
1390         rev, dummy = parseRevisionOption(opts.revision)
1391
1392         src_project = args[0]
1393         src_package = args[1]
1394         dst_project = args[2]
1395         if len(args) > 3:
1396             dst_package = args[3]
1397         else:
1398             dst_package = src_package
1399
1400         if src_project == dst_project and src_package == dst_package:
1401             raise oscerr.WrongArgs('Error: source and destination are the same.')
1402
1403         if src_project == dst_project and not opts.cicount:
1404             # in this case, the user usually wants to build different spec
1405             # files from the same source
1406             opts.cicount = "copy"
1407
1408         if opts.current:
1409             rev = show_upstream_rev(conf.config['apiurl'], src_project, src_package)
1410
1411         if rev and not checkRevision(src_project, src_package, rev):
1412             print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
1413             sys.exit(1)
1414
1415         link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish)
1416
1417     @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]',
1418                   help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]')
1419     @cmdln.option('-d', '--disable-publish', action='store_true',
1420                   help='disable publishing of the aggregated package')
1421     def do_aggregatepac(self, subcmd, opts, *args):
1422         """${cmd_name}: "Aggregate" a package to another package
1423
1424         Aggregation of a package means that the build results (binaries) of a
1425         package are basically copied into another project.
1426         This can be used to make packages available from building that are
1427         needed in a project but available only in a different project. Note
1428         that this is done at the expense of disk space. See
1429         http://en.opensuse.org/Build_Service/Tips_and_Tricks#_link_and__aggregate
1430         for more information.
1431
1432         The DESTPAC name is optional; the source packages' name will be used if
1433         DESTPAC is omitted.
1434
1435         usage:
1436             osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1437         ${cmd_option_list}
1438         """
1439
1440         args = slash_split(args)
1441
1442         if not args or len(args) < 3:
1443             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1444                   + self.get_cmd_help('aggregatepac'))
1445
1446         src_project = args[0]
1447         src_package = args[1]
1448         dst_project = args[2]
1449         if len(args) > 3:
1450             dst_package = args[3]
1451         else:
1452             dst_package = src_package
1453
1454         if src_project == dst_project and src_package == dst_package:
1455             raise oscerr.WrongArgs('Error: source and destination are the same.')
1456
1457         repo_map = {}
1458         if opts.map_repo:
1459             for pair in opts.map_repo.split(','):
1460                 src_tgt = pair.split('=')
1461                 if len(src_tgt) != 2:
1462                     raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo)
1463                 repo_map[src_tgt[0]] = src_tgt[1]
1464
1465         aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish)
1466
1467
1468     @cmdln.option('-c', '--client-side-copy', action='store_true',
1469                         help='do a (slower) client-side copy')
1470     @cmdln.option('-k', '--keep-maintainers', action='store_true',
1471                         help='keep original maintainers. Default is remove all and replace with the one calling the script.')
1472     @cmdln.option('-d', '--keep-develproject', action='store_true',
1473                         help='keep develproject tag in the package metadata')
1474     @cmdln.option('-r', '--revision', metavar='rev',
1475                         help='link the specified revision.')
1476     @cmdln.option('-t', '--to-apiurl', metavar='URL',
1477                         help='URL of destination api server. Default is the source api server.')
1478     @cmdln.option('-m', '--message', metavar='TEXT',
1479                   help='specify message TEXT')
1480     @cmdln.option('-e', '--expand', action='store_true',
1481                         help='if the source package is a link then copy the expanded version of the link')
1482     def do_copypac(self, subcmd, opts, *args):
1483         """${cmd_name}: Copy a package
1484
1485         A way to copy package to somewhere else.
1486
1487         It can be done across buildservice instances, if the -t option is used.
1488         In that case, a client-side copy is implied.
1489
1490         Using --client-side-copy always involves downloading all files, and
1491         uploading them to the target.
1492
1493         The DESTPAC name is optional; the source packages' name will be used if
1494         DESTPAC is omitted.
1495
1496         usage:
1497             osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1498         ${cmd_option_list}
1499         """
1500
1501         args = slash_split(args)
1502
1503         if not args or len(args) < 3:
1504             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1505                   + self.get_cmd_help('copypac'))
1506
1507         src_project = args[0]
1508         src_package = args[1]
1509         dst_project = args[2]
1510         if len(args) > 3:
1511             dst_package = args[3]
1512         else:
1513             dst_package = src_package
1514
1515         src_apiurl = conf.config['apiurl']
1516         if opts.to_apiurl:
1517             dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl)
1518         else:
1519             dst_apiurl = src_apiurl
1520
1521         if src_project == dst_project and \
1522            src_package == dst_package and \
1523            src_apiurl == dst_apiurl:
1524             raise oscerr.WrongArgs('Source and destination are the same.')
1525
1526         if src_apiurl != dst_apiurl:
1527             opts.client_side_copy = True
1528
1529         rev, dummy = parseRevisionOption(opts.revision)
1530
1531         if opts.message:
1532             comment = opts.message
1533         else:
1534             if not rev:
1535                 rev = show_upstream_rev(src_apiurl, src_project, src_package)
1536             comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev )
1537
1538         r = copy_pac(src_apiurl, src_project, src_package,
1539                      dst_apiurl, dst_project, dst_package,
1540                      client_side_copy=opts.client_side_copy,
1541                      keep_maintainers=opts.keep_maintainers,
1542                      keep_develproject=opts.keep_develproject,
1543                      expand=opts.expand,
1544                      revision=rev,
1545                      comment=comment)
1546         print r
1547
1548
1549     @cmdln.option('-c', '--checkout', action='store_true',
1550                         help='Checkout branched package afterwards ' \
1551                                 '(\'osc bco\' is a shorthand for this option)' )
1552     @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
1553                         help='Use this attribute to find affected packages (default is OBS:Maintained)')
1554     @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE',
1555                         help='Use this attribute to find update projects (default is OBS:UpdateProject) ')
1556     def do_mbranch(self, subcmd, opts, *args):
1557         """${cmd_name}: Multiple branch of a package
1558
1559         [See http://en.opensuse.org/Build_Service/Concepts/Maintenance for information
1560         on this topic.]
1561
1562         This command is used for creating multiple links of defined version of a package
1563         in one project. This is esp. used for maintenance updates.
1564
1565         The branched package will live in
1566             home:USERNAME:branches:ATTRIBUTE:PACKAGE
1567         if nothing else specified.
1568
1569         usage:
1570             osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ]
1571         ${cmd_option_list}
1572         """
1573         args = slash_split(args)
1574         tproject = None
1575
1576         maintained_attribute = conf.config['maintained_attribute']
1577         maintained_update_project_attribute = conf.config['maintained_update_project_attribute']
1578
1579         if not len(args) or len(args) > 2:
1580             raise oscerr.WrongArgs('Wrong number of arguments.')
1581         if len(args) >= 1:
1582             package = args[0]
1583         if len(args) >= 2:
1584             tproject = args[1]
1585
1586         r = attribute_branch_pkg(conf.config['apiurl'], maintained_attribute, maintained_update_project_attribute, \
1587                                  package, tproject)
1588
1589         if r is None:
1590             print >>sys.stderr, 'ERROR: Attribute branch call came not back with a project.' 
1591             sys.exit(1)
1592
1593         print "Project " + r + " created."
1594
1595         if opts.checkout:
1596             init_project_dir(conf.config['apiurl'], r, r)
1597             print statfrmt('A', r)
1598
1599             # all packages
1600             for package in meta_get_packagelist(conf.config['apiurl'], r):
1601                 try:
1602                     checkout_package(conf.config['apiurl'], r, package, expand_link = True, prj_dir = r)
1603                 except:
1604                     print >>sys.stderr, 'Error while checkout package:\n', package
1605
1606             if conf.config['verbose']:
1607                 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1608
1609
1610     @cmdln.alias('branchco')
1611     @cmdln.alias('bco')
1612     @cmdln.alias('getpac')
1613     @cmdln.option('--nodevelproject', action='store_true',
1614                         help='do not follow a defined devel project ' \
1615                              '(primary project where a package is developed)')
1616     @cmdln.option('-c', '--checkout', action='store_true',
1617                         help='Checkout branched package afterwards ' \
1618                                 '(\'osc bco\' is a shorthand for this option)' )
1619     @cmdln.option('-r', '--revision', metavar='rev',
1620                         help='branch against a specific revision')
1621     def do_branch(self, subcmd, opts, *args):
1622         """${cmd_name}: Branch a package
1623
1624         [See http://en.opensuse.org/Build_Service/Collaboration for information
1625         on this topic.]
1626
1627         Create a source link from a package of an existing project to a new
1628         subproject of the requesters home project (home:branches:)
1629
1630         The branched package will live in
1631             home:USERNAME:branches:PROJECT/PACKAGE
1632         if nothing else specified.
1633
1634         With getpac or bco, the branched package will come from
1635             %(getpac_default_project)s
1636         if nothing else specified.
1637
1638         usage:
1639             osc branch SOURCEPROJECT SOURCEPACKAGE
1640             osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT
1641             osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE
1642             osc getpac  SOURCEPACKAGE
1643             osc bco ...
1644         ${cmd_option_list}
1645         """
1646
1647         if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': opts.checkout = True
1648         args = slash_split(args)
1649         tproject = tpackage = None
1650
1651         if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1:
1652             print >>sys.stderr, 'defaulting to %s/%s' % (conf.config['getpac_default_project'], args[0])
1653             # python has no args.unshift ???
1654             args = [ conf.config['getpac_default_project'] , args[0] ]
1655
1656         if len(args) < 2 or len(args) > 4:
1657             raise oscerr.WrongArgs('Wrong number of arguments.')
1658         expected = 'home:%s:branches:%s' % (conf.config['user'], args[0])
1659         if len(args) >= 3:
1660             expected = tproject = args[2]
1661         if len(args) >= 4:
1662             tpackage = args[3]
1663
1664         exists, targetprj, targetpkg, srcprj, srcpkg = \
1665                 branch_pkg(conf.config['apiurl'], args[0], args[1],
1666                            nodevelproject=opts.nodevelproject, rev=opts.revision,
1667                            target_project=tproject, target_package=tpackage,
1668                            return_existing=opts.checkout)
1669         if exists:
1670             print >>sys.stderr, 'Using existing branch project: %s' % targetprj
1671
1672         devloc = None
1673         if not exists and (srcprj is not None and srcprj != args[0] or \
1674                            srcprj is None and targetprj != expected):
1675             devloc = srcprj or targetprj
1676             if not srcprj and 'branches:' in targetprj:
1677                 devloc = targetprj.split('branches:')[1]
1678             print '\nNote: The branch has been created of a different project,\n' \
1679                   '              %s,\n' \
1680                   '      which is the primary location of where development for\n' \
1681                   '      that package takes place.\n' \
1682                   '      That\'s also where you would normally make changes against.\n' \
1683                   '      A direct branch of the specified package can be forced\n' \
1684                   '      with the --nodevelproject option.\n' % devloc
1685
1686         package = tpackage or args[1]
1687         if opts.checkout:
1688             checkout_package(conf.config['apiurl'], targetprj, package,
1689                              expand_link=True, prj_dir=targetprj)
1690             if conf.config['verbose']:
1691                 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1692         else:
1693             apiopt = ''
1694             if conf.get_configParser().get('general', 'apiurl') != conf.config['apiurl']:
1695                 apiopt = '-A %s ' % conf.config['apiurl']
1696             print 'A working copy of the branched package can be checked out with:\n\n' \
1697                   'osc %sco %s/%s' \
1698                       % (apiopt, targetprj, package)
1699         print_request_list(conf.config['apiurl'], args[0], args[1])
1700         if devloc:
1701             print_request_list(conf.config['apiurl'], devloc, args[1])
1702
1703
1704
1705     @cmdln.option('-f', '--force', action='store_true',
1706                         help='deletes a package or an empty project')
1707     def do_rdelete(self, subcmd, opts, *args):
1708         """${cmd_name}: Delete a project or packages on the server.
1709
1710         As a safety measure, project must be empty (i.e., you need to delete all
1711         packages first). If you are sure that you want to remove this project and all
1712         its packages use \'--force\' switch.
1713
1714         usage:
1715            osc rdelete -f PROJECT
1716            osc rdelete PROJECT PACKAGE [PACKAGE ...]
1717
1718         ${cmd_option_list}
1719         """
1720
1721         args = slash_split(args)
1722         if len(args) < 1:
1723             raise oscerr.WrongArgs('Missing argument.')
1724         prj = args[0]
1725         pkgs = args[1:]
1726
1727         if pkgs:
1728             for pkg in pkgs:
1729                # careful: if pkg is an empty string, the package delete request results
1730                # into a project delete request - which works recursively...
1731                 if pkg:
1732                     delete_package(conf.config['apiurl'], prj, pkg)
1733         elif len(meta_get_packagelist(conf.config['apiurl'], prj)) >= 1 and not opts.force:
1734             print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
1735                                 'If you are sure that you want to remove this project and all its ' \
1736                                 'packages use the \'--force\' switch'
1737             sys.exit(1)
1738         else:
1739             delete_project(conf.config['apiurl'], prj)
1740
1741     @cmdln.hide(1)
1742     def do_deletepac(self, subcmd, opts, *args):
1743         print """${cmd_name} is obsolete !
1744
1745                  Please use either
1746                    osc delete       for checked out packages or projects
1747                  or
1748                    osc rdelete      for server side operations."""
1749
1750         sys.exit(1)
1751
1752     @cmdln.hide(1)
1753     @cmdln.option('-f', '--force', action='store_true',
1754                         help='deletes a project and its packages')
1755     def do_deleteprj(self, subcmd, opts, project):
1756         """${cmd_name} is obsolete !
1757
1758                  Please use
1759                    osc rdelete PROJECT
1760         """
1761         sys.exit(1)
1762
1763     @cmdln.alias('metafromspec')
1764     @cmdln.option('', '--specfile', metavar='FILE',
1765                       help='Path to specfile. (if you pass more than working copy this option is ignored)')
1766     def do_updatepacmetafromspec(self, subcmd, opts, *args):
1767         """${cmd_name}: Update package meta information from a specfile
1768
1769         ARG, if specified, is a package working copy.
1770
1771         ${cmd_usage}
1772         ${cmd_option_list}
1773         """
1774
1775         args = parseargs(args)
1776         if opts.specfile and len(args) == 1:
1777             specfile = opts.specfile
1778         else:
1779             specfile = None
1780         pacs = findpacs(args)
1781         for p in pacs:
1782             p.read_meta_from_spec(specfile)
1783             p.update_package_meta()
1784
1785
1786     @cmdln.alias('di')
1787     @cmdln.option('-c', '--change', metavar='rev',
1788                         help='the change made by revision rev (like -r rev-1:rev).'
1789                              'If rev is negative this is like -r rev:rev-1.')
1790     @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
1791                         help='If rev1 is specified it will compare your working copy against '
1792                              'the revision (rev1) on the server. '
1793                              'If rev1 and rev2 are specified it will compare rev1 against rev2 '
1794                              '(NOTE: changes in your working copy are ignored in this case)')
1795     @cmdln.option('-p', '--plain', action='store_true',
1796                         help='output the diff in plain (not unified) diff format')
1797     def do_diff(self, subcmd, opts, *args):
1798         """${cmd_name}: Generates a diff
1799
1800         Generates a diff, comparing local changes against the repository
1801         server.
1802
1803         ARG, specified, is a filename to include in the diff.
1804
1805         ${cmd_usage}
1806         ${cmd_option_list}
1807         """
1808
1809         args = parseargs(args)
1810         pacs = findpacs(args)
1811
1812         if opts.change:
1813             try:
1814                 rev = int(opts.change)
1815                 if rev > 0:
1816                     rev1 = rev - 1
1817                     rev2 = rev
1818                 elif rev < 0:
1819                     rev1 = -rev
1820                     rev2 = -rev - 1
1821                 else:
1822                     return
1823             except:
1824                 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1825                 return
1826         else:
1827             rev1, rev2 = parseRevisionOption(opts.revision)
1828         diff = ''
1829         for pac in pacs:
1830             if not rev2:
1831                 diff += ''.join(make_diff(pac, rev1))
1832             else:
1833                 diff += server_diff(pac.apiurl, pac.prjname, pac.name, rev1,
1834                                     pac.prjname, pac.name, rev2, not opts.plain)
1835         if len(diff) > 0:
1836             print diff
1837
1838
1839     @cmdln.option('--oldprj', metavar='OLDPRJ',
1840                   help='project to compare against'
1841                   ' (deprecated, use 3 argument form)')
1842     @cmdln.option('--oldpkg', metavar='OLDPKG',
1843                   help='package to compare against'
1844                   ' (deprecated, use 3 argument form)')
1845     @cmdln.option('-r', '--revision', metavar='N[:M]',
1846                   help='revision id, where N = old revision and M = new revision')
1847     @cmdln.option('-p', '--plain', action='store_true',
1848                   help='output the diff in plain (not unified) diff format')
1849     @cmdln.option('-c', '--change', metavar='rev',
1850                         help='the change made by revision rev (like -r rev-1:rev). '
1851                              'If rev is negative this is like -r rev:rev-1.')
1852     def do_rdiff(self, subcmd, opts, *args):
1853         """${cmd_name}: Server-side "pretty" diff of two packages
1854
1855         Compares two packages (three or four arguments) or shows the
1856         changes of a specified revision of a package (two arguments)
1857
1858         If no revision is specified the latest revision is used.
1859
1860         Note that this command doesn't return a normal diff (which could be
1861         applied as patch), but a "pretty" diff, which also compares the content
1862         of tarballs.
1863
1864
1865         usage:
1866             osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC]
1867             osc ${cmd_name} PROJECT PACKAGE
1868         ${cmd_option_list}
1869         """
1870
1871         args = slash_split(args)
1872
1873         rev1 = None
1874         rev2 = None
1875
1876         old_project = None
1877         old_package = None
1878         new_project = None
1879         new_package = None
1880
1881         if len(args) == 2:
1882             new_project = args[0]
1883             new_package = args[1]
1884             if opts.oldprj:
1885                 old_project = opts.oldprj
1886             if opts.oldpkg:
1887                 old_package = opts.oldpkg
1888         elif len(args) == 3 or len(args) == 4:
1889             if opts.oldprj or opts.oldpkg:
1890                 raise oscerr.WrongArgs('--oldpkg and --oldprj are only valid with two arguments')
1891             old_project = args[0]
1892             new_package = old_package = args[1]
1893             new_project = args[2]
1894             if len(args) == 4:
1895                 new_package = args[3]
1896         else:
1897             raise oscerr.WrongArgs('Wrong number of arguments')
1898
1899
1900         if opts.change:
1901             try:
1902                 rev = int(opts.change)
1903                 if rev > 0:
1904                     rev1 = rev - 1
1905                     rev2 = rev
1906                 elif rev < 0:
1907                     rev1 = -rev
1908                     rev2 = -rev - 1
1909                 else:
1910                     return
1911             except:
1912                 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1913                 return
1914         else:
1915             if opts.revision:
1916                 rev1, rev2 = parseRevisionOption(opts.revision)
1917
1918         rdiff = server_diff(conf.config['apiurl'],
1919                             old_project, old_package, rev1,
1920                             new_project, new_package, rev2, not opts.plain)
1921
1922         print rdiff
1923
1924     @cmdln.hide(1)
1925     @cmdln.alias('in')
1926     def do_install(self, subcmd, opts, *args):
1927         """${cmd_name}: install a package after build via zypper in -r
1928
1929         Not implemented yet. Use osc repourls,
1930         select the url you best like (standard),
1931         chop off after the last /, this should work with zypper.
1932
1933
1934         ${cmd_usage}
1935         ${cmd_option_list}
1936         """
1937
1938         args = slash_split(args)
1939         args = expand_proj_pack(args)
1940
1941         ## FIXME: 
1942         ## if there is only one argument, and it ends in .ymp
1943         ## then fetch it, Parse XML to get the first 
1944         ##  metapackage.group.repositories.repository.url
1945         ## and construct zypper cmd's for all
1946         ##  metapackage.group.software.item.name
1947         ##
1948         ## if args[0] is already an url, the use it as is.
1949
1950         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])
1951         print self.do_install.__doc__
1952         print "Example: \n" + cmd
1953
1954
1955     def do_repourls(self, subcmd, opts, *args):
1956         """${cmd_name}: Shows URLs of .repo files
1957
1958         Shows URLs on which to access the project .repos files (yum-style
1959         metadata) on download.opensuse.org.
1960
1961         usage:
1962            osc repourls [PROJECT]
1963
1964         ${cmd_option_list}
1965         """
1966
1967         apiurl = conf.config['apiurl']
1968
1969         if len(args) == 1:
1970             project = args[0]
1971         elif len(args) == 0:
1972             project = store_read_project('.')
1973             apiurl = store_read_apiurl('.')
1974         else:
1975             raise oscerr.WrongArgs('Wrong number of arguments')
1976
1977         # XXX: API should somehow tell that
1978         url_tmpl = 'http://download.opensuse.org/repositories/%s/%s/%s.repo'
1979         repos = get_repositories_of_project(apiurl, project)
1980         for repo in repos:
1981             print url_tmpl % (project.replace(':', ':/'), repo, project)
1982
1983
1984     @cmdln.option('-r', '--revision', metavar='rev',
1985                         help='checkout the specified revision. '
1986                              'NOTE: if you checkout the complete project '
1987                              'this option is ignored!')
1988     @cmdln.option('-e', '--expand-link', action='store_true',
1989                         help='if a package is a link, check out the expanded '
1990                              'sources (no-op, since this became the default)')
1991     @cmdln.option('-u', '--unexpand-link', action='store_true',
1992                         help='if a package is a link, check out the _link file ' \
1993                              'instead of the expanded sources')
1994     @cmdln.option('-c', '--current-dir', action='store_true',
1995                         help='place PACKAGE folder in the current directory' \
1996                              'instead of a PROJECT/PACKAGE directory')
1997     @cmdln.option('-s', '--source-service-files', action='store_true',
1998                         help='server side generated files of source services' \
1999                              'gets downloaded as well' )
2000     @cmdln.alias('co')
2001     def do_checkout(self, subcmd, opts, *args):
2002         """${cmd_name}: Check out content from the repository
2003
2004         Check out content from the repository server, creating a local working
2005         copy.
2006
2007         When checking out a single package, the option --revision can be used
2008         to specify a revision of the package to be checked out.
2009
2010         When a package is a source link, then it will be checked out in
2011         expanded form. If --unexpand-link option is used, the checkout will
2012         instead produce the raw _link file plus patches.
2013
2014         usage:
2015             osc co PROJECT [PACKAGE] [FILE]
2016                osc co PROJECT                    # entire project
2017                osc co PROJECT PACKAGE            # a package
2018                osc co PROJECT PACKAGE FILE       # single file -> to current dir
2019
2020             while inside a project directory:
2021                osc co PACKAGE                    # check out PACKAGE from project
2022
2023         ${cmd_option_list}
2024         """
2025
2026         if opts.unexpand_link:
2027             expand_link = False
2028         else:
2029             expand_link = True
2030         if opts.source_service_files:
2031             service_files = True
2032         else:
2033             service_files = False
2034
2035         args = slash_split(args)
2036         project = package = filename = None
2037         apiurl = conf.config['apiurl']
2038         try:
2039             project = project_dir = args[0]
2040             package = args[1]
2041             filename = args[2]
2042         except:
2043             pass
2044
2045         if args and len(args) == 1:
2046             localdir = os.getcwd()
2047             if is_project_dir(localdir):
2048                 project = store_read_project(localdir)
2049                 project_dir = localdir
2050                 package = args[0]
2051                 apiurl = store_read_apiurl(localdir)
2052
2053         rev, dummy = parseRevisionOption(opts.revision)
2054         if rev==None:
2055             rev="latest"
2056
2057         if rev and rev != "latest" and not checkRevision(project, package, rev):
2058             print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2059             sys.exit(1)
2060
2061         if filename:
2062             get_source_file(apiurl, project, package, filename, revision=rev, progress_obj=self.download_progress)
2063
2064         elif package:
2065             if opts.current_dir:
2066                 project_dir = None
2067             checkout_package(apiurl, project, package, rev, expand_link=expand_link, \
2068                              prj_dir=project_dir, service_files=service_files, progress_obj=self.download_progress)
2069             print_request_list(apiurl, project, package)
2070
2071         elif project:
2072             prj_dir = project
2073             if sys.platform[:3] == 'win':
2074                 prj_dir = prj_dir.replace(':', ';')
2075             if os.path.exists(prj_dir):
2076                 sys.exit('osc: project \'%s\' already exists' % project)
2077
2078             # check if the project does exist (show_project_meta will throw an exception)
2079             show_project_meta(apiurl, project)
2080
2081             init_project_dir(apiurl, prj_dir, project)
2082             print statfrmt('A', prj_dir)
2083
2084             # all packages
2085             for package in meta_get_packagelist(apiurl, project):
2086                 try:
2087                     checkout_package(apiurl, project, package, expand_link = expand_link, \
2088                                      prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress)
2089                 except oscerr.LinkExpandError, e:
2090                     print >>sys.stderr, 'Link cannot be expanded:\n', e
2091                     print >>sys.stderr, 'Use "osc repairlink" for fixing merge conflicts:\n'
2092                     # check out in unexpanded form at least
2093                     checkout_package(apiurl, project, package, expand_link = False, \
2094                                      prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress)
2095             print_request_list(apiurl, project)
2096
2097         else:
2098             raise oscerr.WrongArgs('Missing argument.\n\n' \
2099                   + self.get_cmd_help('checkout'))
2100
2101
2102     @cmdln.option('-q', '--quiet', action='store_true',
2103                         help='print as little as possible')
2104     @cmdln.option('-v', '--verbose', action='store_true',
2105                         help='print extra information')
2106     @cmdln.alias('st')
2107     def do_status(self, subcmd, opts, *args):
2108         """${cmd_name}: Show status of files in working copy
2109
2110         Show the status of files in a local working copy, indicating whether
2111         files have been changed locally, deleted, added, ...
2112
2113         The first column in the output specifies the status and is one of the
2114         following characters:
2115           ' ' no modifications
2116           'A' Added
2117           'C' Conflicted
2118           'D' Deleted
2119           'M' Modified
2120           '?' item is not under version control
2121           '!' item is missing (removed by non-osc command) or incomplete
2122
2123         examples:
2124           osc st
2125           osc st <directory>
2126           osc st file1 file2 ...
2127
2128         usage:
2129             osc status [OPTS] [PATH...]
2130         ${cmd_option_list}
2131         """
2132
2133         args = parseargs(args)
2134
2135         # storage for single Package() objects
2136         pacpaths = []
2137         # storage for a project dir ( { prj_instance : [ package objects ] } )
2138         prjpacs = {}
2139         for arg in args:
2140             # when 'status' is run inside a project dir, it should
2141             # stat all packages existing in the wc
2142             if is_project_dir(arg):
2143                 prj = Project(arg, False)
2144
2145                 if conf.config['do_package_tracking']:
2146                     prjpacs[prj] = []
2147                     for pac in prj.pacs_have:
2148                         # we cannot create package objects if the dir does not exist
2149                         if not pac in prj.pacs_broken:
2150                             prjpacs[prj].append(os.path.join(arg, pac))
2151                 else:
2152                     pacpaths += [arg + '/' + n for n in prj.pacs_have]
2153             elif is_package_dir(arg):
2154                 pacpaths.append(arg)
2155             elif os.path.isfile(arg):
2156                 pacpaths.append(arg)
2157             else:
2158                 msg = '\'%s\' is neither a project or a package directory' % arg
2159                 raise oscerr.NoWorkingCopy, msg
2160         lines = []
2161         # process single packages
2162         lines = getStatus(findpacs(pacpaths), None, opts.verbose, opts.quiet)
2163         # process project dirs
2164         for prj, pacs in prjpacs.iteritems():
2165             lines += getStatus(findpacs(pacs), prj, opts.verbose, opts.quiet)
2166         if lines:
2167             print '\n'.join(lines)
2168
2169
2170     def do_add(self, subcmd, opts, *args):
2171         """${cmd_name}: Mark files to be added upon the next commit
2172
2173         usage:
2174             osc add FILE [FILE...]
2175         ${cmd_option_list}
2176         """
2177         if not args:
2178             raise oscerr.WrongArgs('Missing argument.\n\n' \
2179                   + self.get_cmd_help('add'))
2180
2181         filenames = parseargs(args)
2182         addFiles(filenames)
2183
2184
2185     def do_mkpac(self, subcmd, opts, *args):
2186         """${cmd_name}: Create a new package under version control
2187
2188         usage:
2189             osc mkpac new_package
2190         ${cmd_option_list}
2191         """
2192         if not conf.config['do_package_tracking']:
2193             print >>sys.stderr, "to use this feature you have to enable \'do_package_tracking\' " \
2194                                 "in the [general] section in the configuration file"
2195             sys.exit(1)
2196
2197         if len(args) != 1:
2198             raise oscerr.WrongArgs('Wrong number of arguments.')
2199
2200         createPackageDir(args[0])
2201
2202     @cmdln.option('-r', '--recursive', action='store_true',
2203                         help='If CWD is a project dir then scan all package dirs as well')
2204     @cmdln.alias('ar')
2205     def do_addremove(self, subcmd, opts, *args):
2206         """${cmd_name}: Adds new files, removes disappeared files
2207
2208         Adds all files new in the local copy, and removes all disappeared files.
2209
2210         ARG, if specified, is a package working copy.
2211
2212         ${cmd_usage}
2213         ${cmd_option_list}
2214         """
2215
2216         args = parseargs(args)
2217         arg_list = args[:]
2218         for arg in arg_list:
2219             if is_project_dir(arg) and conf.config['do_package_tracking']:
2220                 prj = Project(arg, False)
2221                 for pac in prj.pacs_unvers:
2222                     pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2223                     if os.path.isdir(pac_dir):
2224                         addFiles([pac_dir], prj)
2225                 for pac in prj.pacs_broken:
2226                     if prj.get_state(pac) != 'D':
2227                         prj.set_state(pac, 'D')
2228                         print statfrmt('D', getTransActPath(os.path.join(prj.dir, pac)))
2229                 if opts.recursive:
2230                     for pac in prj.pacs_have:
2231                         state = prj.get_state(pac)
2232                         if state != None and state != 'D':
2233                             pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2234                             args.append(pac_dir)
2235                 args.remove(arg)
2236                 prj.write_packages()
2237             elif is_project_dir(arg):
2238                 print >>sys.stderr, 'osc: addremove is not supported in a project dir unless ' \
2239                                     '\'do_package_tracking\' is enabled in the configuration file'
2240                 sys.exit(1)
2241
2242         pacs = findpacs(args)
2243         for p in pacs:
2244             p.todo = p.filenamelist + p.filenamelist_unvers
2245
2246             for filename in p.todo:
2247                 if os.path.isdir(filename):
2248                     continue
2249                 # ignore foo.rXX, foo.mine for files which are in 'C' state
2250                 if os.path.splitext(filename)[0] in p.in_conflict:
2251                     continue
2252                 state = p.status(filename)
2253
2254                 if state == '?':
2255                     # TODO: should ignore typical backup files suffix ~ or .orig
2256                     p.addfile(filename)
2257                     print statfrmt('A', getTransActPath(os.path.join(p.dir, filename)))
2258                 elif state == '!':
2259                     p.put_on_deletelist(filename)
2260                     p.write_deletelist()
2261                     os.unlink(os.path.join(p.storedir, filename))
2262                     print statfrmt('D', getTransActPath(os.path.join(p.dir, filename)))
2263
2264
2265
2266     @cmdln.alias('ci')
2267     @cmdln.alias('checkin')
2268     @cmdln.option('-m', '--message', metavar='TEXT',
2269                   help='specify log message TEXT')
2270     @cmdln.option('-F', '--file', metavar='FILE',
2271                   help='read log message from FILE')
2272     @cmdln.option('-f', '--force', default=False, action="store_true",
2273                   help='force commit - do not tests a file list')
2274     def do_commit(self, subcmd, opts, *args):
2275         """${cmd_name}: Upload content to the repository server
2276
2277         Upload content which is changed in your working copy, to the repository
2278         server.
2279
2280         Optionally checks the state of a working copy, if found a file with
2281         unknown state, it requests an user input:
2282          * skip - don't change anything, just move to another file
2283          * remove - remove a file from dir
2284          * edit file list - edit filelist using EDITOR
2285          * commit - don't check anything and commit package
2286          * abort - abort commit - this is default value
2287         This can be supressed by check_filelist config item, or -f/--force
2288         command line option.
2289
2290         examples:
2291            osc ci                   # current dir
2292            osc ci <dir>
2293            osc ci file1 file2 ...
2294
2295         ${cmd_usage}
2296         ${cmd_option_list}
2297         """
2298
2299         args = parseargs(args)
2300
2301         msg = ''
2302         if opts.message:
2303             msg = opts.message
2304         elif opts.file:
2305             try:
2306                 msg = open(opts.file).read()
2307             except:
2308                 sys.exit('could not open file \'%s\'.' % opts.file)
2309
2310         arg_list = args[:]
2311         for arg in arg_list:
2312             if conf.config['do_package_tracking'] and is_project_dir(arg):
2313                 Project(arg).commit(msg=msg)
2314                 if not msg:
2315                     msg = edit_message()
2316                 args.remove(arg)
2317
2318         pacs = findpacs(args)
2319
2320         if conf.config['check_filelist'] and not opts.force:
2321             check_filelist_before_commit(pacs)
2322
2323         if not msg:
2324             template = store_read_file(os.path.abspath('.'), '_commit_msg')
2325             # open editor for commit message
2326             # but first, produce status and diff to append to the template
2327             footer = diffs = []
2328             lines = []
2329             for pac in pacs:
2330                 changed = getStatus([pac], quiet=True)
2331                 if changed:
2332                     footer += changed
2333                     diffs += ['\nDiff for working copy: %s' % pac.dir]
2334                     diffs += make_diff(pac, 0)
2335                     lines.extend(get_commit_message_template(pac))
2336             if template == None:
2337                 template='\n'.join(lines)
2338             # if footer is empty, there is nothing to commit, and no edit needed.
2339             if footer:
2340                 msg = edit_message(footer='\n'.join(footer), template=template)
2341
2342             if msg:
2343                 store_write_string(os.path.abspath('.'), '_commit_msg', msg)
2344             else:
2345                 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2346
2347         if conf.config['do_package_tracking'] and len(pacs) > 0:
2348             prj_paths = {}
2349             single_paths = []
2350             files = {}
2351             # it is possible to commit packages from different projects at the same
2352             # time: iterate over all pacs and put each pac to the right project in the dict
2353             for pac in pacs:
2354                 path = os.path.normpath(os.path.join(pac.dir, os.pardir))
2355                 if is_project_dir(path):
2356                     pac_path = os.path.basename(os.path.normpath(pac.absdir))
2357                     prj_paths.setdefault(path, []).append(pac_path)
2358                     files[pac_path] = pac.todo
2359                 else:
2360                     single_paths.append(pac.dir)
2361             for prj, packages in prj_paths.iteritems():
2362                 Project(prj).commit(tuple(packages), msg, files)
2363             for pac in single_paths:
2364                 Package(pac).commit(msg)
2365         else:
2366             for p in pacs:
2367                 p.commit(msg)
2368
2369         store_unlink_file(os.path.abspath('.'), '_commit_msg')
2370
2371     @cmdln.option('-r', '--revision', metavar='REV',
2372                         help='update to specified revision (this option will be ignored '
2373                              'if you are going to update the complete project or more than '
2374                              'one package)')
2375     @cmdln.option('-u', '--unexpand-link', action='store_true',
2376                         help='if a package is an expanded link, update to the raw _link file')
2377     @cmdln.option('-e', '--expand-link', action='store_true',
2378                         help='if a package is a link, update to the expanded sources')
2379     @cmdln.option('-s', '--source-service-files', action='store_true',
2380                         help='Use server side generated sources instead of local generation.' )
2381     @cmdln.alias('up')
2382     def do_update(self, subcmd, opts, *args):
2383         """${cmd_name}: Update a working copy
2384
2385         examples:
2386
2387         1. osc up
2388                 If the current working directory is a package, update it.
2389                 If the directory is a project directory, update all contained
2390                 packages, AND check out newly added packages.
2391
2392                 To update only checked out packages, without checking out new
2393                 ones, you might want to use "osc up *" from within the project
2394                 dir.
2395
2396         2. osc up PAC
2397                 Update the packages specified by the path argument(s)
2398
2399         When --expand-link is used with source link packages, the expanded
2400         sources will be checked out. Without this option, the _link file and
2401         patches will be checked out. The option --unexpand-link can be used to
2402         switch back to the "raw" source with a _link file plus patch(es).
2403
2404         ${cmd_usage}
2405         ${cmd_option_list}
2406         """
2407
2408         if (opts.expand_link and opts.unexpand_link) \
2409             or (opts.expand_link and opts.revision) \
2410             or (opts.unexpand_link and opts.revision):
2411             raise oscerr.WrongOptions('Sorry, the options --expand-link, --unexpand-link and '
2412                      '--revision are mutually exclusive.')
2413
2414         if opts.source_service_files: service_files = True
2415         else: service_files = False
2416
2417         args = parseargs(args)
2418         arg_list = args[:]
2419
2420         for arg in arg_list:
2421             if is_project_dir(arg):
2422                 prj = Project(arg, progress_obj=self.download_progress)
2423
2424                 if conf.config['do_package_tracking']:
2425                     prj.update(expand_link=opts.expand_link,
2426                                unexpand_link=opts.unexpand_link)
2427                     args.remove(arg)
2428                 else:
2429                     # if not tracking package, and 'update' is run inside a project dir,
2430                     # it should do the following:
2431                     # (a) update all packages
2432                     args += prj.pacs_have
2433                     # (b) fetch new packages
2434                     prj.checkout_missing_pacs(expand_link = not opts.unexpand_link)
2435                     args.remove(arg)
2436                 print_request_list(prj.apiurl, prj.name)
2437
2438         args.sort()
2439         pacs = findpacs(args, progress_obj=self.download_progress)
2440
2441         if opts.revision and len(args) == 1:
2442             rev, dummy = parseRevisionOption(opts.revision)
2443             if not checkRevision(pacs[0].prjname, pacs[0].name, rev, pacs[0].apiurl):
2444                 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2445                 sys.exit(1)
2446         else:
2447             rev = None
2448
2449         for p in pacs:
2450             if len(pacs) > 1:
2451                 print 'Updating %s' % p.name
2452
2453             # FIXME: ugly workaround for #399247
2454             if opts.expand_link or opts.unexpand_link:
2455                 if [ i for i in p.filenamelist+p.filenamelist_unvers if p.status(i) != ' ' and p.status(i) != '?']:
2456                     print >>sys.stderr, 'osc: cannot expand/unexpand because your working ' \
2457                                         'copy has local modifications.\nPlease revert/commit them ' \
2458                                         'and try again.'
2459                     sys.exit(1)
2460
2461             if not rev:
2462                 if opts.expand_link and p.islink() and not p.isexpanded():
2463                     if p.haslinkerror():
2464                         try:
2465                             rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev)
2466                         except:
2467                             rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev, linkrev="base")
2468                             p.mark_frozen()
2469                     else:
2470                         rev = p.linkinfo.xsrcmd5
2471                     print 'Expanding to rev', rev
2472                 elif opts.unexpand_link and p.islink() and p.isexpanded():
2473                     print 'Unexpanding to rev', p.linkinfo.lsrcmd5
2474                     rev = p.linkinfo.lsrcmd5
2475                 elif p.islink() and p.isexpanded():
2476                     rev = p.latest_rev()
2477
2478             p.update(rev, service_files)
2479             if opts.unexpand_link:
2480                 p.unmark_frozen()
2481             rev = None
2482             print_request_list(p.apiurl, p.prjname, p.name)
2483
2484
2485     @cmdln.option('-f', '--force', action='store_true',
2486                         help='forces removal of entire package and its files')
2487     @cmdln.alias('rm')
2488     @cmdln.alias('del')
2489     @cmdln.alias('remove')
2490     def do_delete(self, subcmd, opts, *args):
2491         """${cmd_name}: Mark files or package directories to be deleted upon the next 'checkin'
2492
2493         usage:
2494             cd .../PROJECT/PACKAGE
2495             osc delete FILE [...]
2496             cd .../PROJECT
2497             osc delete PACKAGE [...]
2498
2499         This command works on check out copies. Use "rdelete" for working on server
2500         side only. This is needed for removing the entire project.
2501
2502         As a safety measure, projects must be empty (i.e., you need to delete all
2503         packages first).
2504
2505         If you are sure that you want to remove a package and all
2506         its files use \'--force\' switch. Sometimes this also works without --force.
2507
2508         ${cmd_option_list}
2509         """
2510
2511         if not args:
2512             raise oscerr.WrongArgs('Missing argument.\n\n' \
2513                   + self.get_cmd_help('delete'))
2514
2515         args = parseargs(args)
2516         # check if args contains a package which was removed by
2517         # a non-osc command and mark it with the 'D'-state
2518         arg_list = args[:]
2519         for i in arg_list:
2520             if not os.path.exists(i):
2521                 prj_dir, pac_dir = getPrjPacPaths(i)
2522                 if is_project_dir(prj_dir):
2523                     prj = Project(prj_dir, False)
2524                     if i in prj.pacs_broken:
2525                         if prj.get_state(i) != 'A':
2526                             prj.set_state(pac_dir, 'D')
2527                         else:
2528                             prj.del_package_node(i)
2529                         print statfrmt('D', getTransActPath(i))
2530                         args.remove(i)
2531                         prj.write_packages()
2532         pacs = findpacs(args)
2533
2534         for p in pacs:
2535             if not p.todo:
2536                 prj_dir, pac_dir = getPrjPacPaths(p.absdir)
2537                 if is_project_dir(prj_dir):
2538                     if conf.config['do_package_tracking']:
2539                         prj = Project(prj_dir, False)
2540                         prj.delPackage(p, opts.force)
2541                     else:
2542                         print "WARNING: package tracking is disabled, operation skipped !"
2543             else:
2544                 pathn = getTransActPath(p.dir)
2545                 for filename in p.todo:
2546                     ret, state = p.delete_file(filename, opts.force)
2547                     if ret:
2548                         print statfrmt('D', os.path.join(pathn, filename))
2549                         continue
2550                     if state == '?':
2551                         sys.exit('\'%s\' is not under version control' % filename)
2552                     elif state in ['A', 'M'] and not opts.force:
2553                         sys.exit('\'%s\' has local modifications (use --force to remove this file)' % filename)
2554
2555
2556     def do_resolved(self, subcmd, opts, *args):
2557         """${cmd_name}: Remove 'conflicted' state on working copy files
2558
2559         If an upstream change can't be merged automatically, a file is put into
2560         in 'conflicted' ('C') state. Within the file, conflicts are marked with
2561         special <<<<<<< as well as ======== and >>>>>>> lines.
2562
2563         After manually resolving all conflicting parts, use this command to
2564         remove the 'conflicted' state.
2565
2566         Note:  this subcommand does not semantically resolve conflicts or
2567         remove conflict markers; it merely removes the conflict-related
2568         artifact files and allows PATH to be committed again.
2569
2570         usage:
2571             osc resolved FILE [FILE...]
2572         ${cmd_option_list}
2573         """
2574
2575         if not args:
2576             raise oscerr.WrongArgs('Missing argument.\n\n' \
2577                   + self.get_cmd_help('resolved'))
2578
2579         args = parseargs(args)
2580         pacs = findpacs(args)
2581
2582         for p in pacs:
2583             for filename in p.todo:
2584                 print 'Resolved conflicted state of "%s"' % filename
2585                 p.clear_from_conflictlist(filename)
2586
2587
2588     @cmdln.alias('platforms')
2589     def do_repositories(self, subcmd, opts, *args):
2590         """${cmd_name}: Shows available repositories
2591
2592         Examples:
2593         1. osc repositories
2594                 Shows all available repositories/build targets
2595
2596         2. osc repositories <project>
2597                 Shows the configured repositories/build targets of a project
2598
2599         ${cmd_usage}
2600         ${cmd_option_list}
2601         """
2602
2603         if args:
2604             project = args[0]
2605             print '\n'.join(get_repositories_of_project(conf.config['apiurl'], project))
2606         else:
2607             print '\n'.join(get_repositories(conf.config['apiurl']))
2608
2609
2610     @cmdln.hide(1)
2611     def do_results_meta(self, subcmd, opts, *args):
2612         print "Command results_meta is obsolete. Please use: osc results --xml"
2613         sys.exit(1)
2614
2615     @cmdln.hide(1)
2616     @cmdln.option('-l', '--last-build', action='store_true',
2617                         help='show last build results (succeeded/failed/unknown)')
2618     @cmdln.option('-r', '--repo', action='append', default = [],
2619                         help='Show results only for specified repo(s)')
2620     @cmdln.option('-a', '--arch', action='append', default = [],
2621                         help='Show results only for specified architecture(s)')
2622     @cmdln.option('', '--xml', action='store_true',
2623                         help='generate output in XML (former results_meta)')
2624     def do_rresults(self, subcmd, opts, *args):
2625         print "Command rresults is obsolete. Running 'osc results' instead"
2626         self.do_results('results', opts, *args)
2627         sys.exit(1)
2628
2629
2630     @cmdln.option('-f', '--force', action='store_true', default=False,
2631                         help="Don't ask and delete files")
2632     def do_rremove(self, subcmd, opts, project, package, *files):
2633         """${cmd_name}: Remove source files from selected package
2634
2635         ${cmd_usage}
2636         ${cmd_option_list}
2637         """
2638
2639         if len(files) == 0:
2640             if not '/' in project:
2641                 raise oscerr.WrongArgs("Missing operand, type osc help rremove for help")
2642             else:
2643                 files = (package, )
2644                 project, package = project.split('/')
2645
2646         for file in files:
2647             if not opts.force:
2648                 resp = raw_input("rm: remove source file `%s' from `%s/%s'? (yY|nN) " % (file, project, package))
2649                 if resp not in ('y', 'Y'):
2650                     continue
2651             try:
2652                 delete_files(conf.config['apiurl'], project, package, (file, ))
2653             except urllib2.HTTPError, e:
2654                 if opts.force:
2655                     print >>sys.stderr, e
2656                     body = e.read()
2657                     if e.code in [ 400, 403, 404, 500 ]:
2658                         if '<summary>' in body:
2659                             msg = body.split('<summary>')[1]
2660                             msg = msg.split('</summary>')[0]
2661                             print >>sys.stderr, msg
2662                 else:
2663                     raise e
2664
2665     @cmdln.alias('r')
2666     @cmdln.option('-l', '--last-build', action='store_true',
2667                         help='show last build results (succeeded/failed/unknown)')
2668     @cmdln.option('-r', '--repo', action='append', default = [],
2669                         help='Show results only for specified repo(s)')
2670     @cmdln.option('-a', '--arch', action='append', default = [],
2671                         help='Show results only for specified architecture(s)')
2672     @cmdln.option('', '--xml', action='store_true',
2673                         help='generate output in XML (former results_meta)')
2674     def do_results(self, subcmd, opts, *args):
2675         """${cmd_name}: Shows the build results of a package
2676
2677         Usage:
2678             osc results (inside working copy)
2679             osc results remote_project remote_package
2680
2681         ${cmd_option_list}
2682         """
2683
2684         args = slash_split(args)
2685
2686         apiurl = conf.config['apiurl']
2687         if len(args) == 0:
2688             wd = os.curdir
2689             if is_project_dir(wd):
2690                 opts.csv = None
2691                 opts.arch = None
2692                 opts.repo = None
2693                 opts.hide_legend = None
2694                 opts.name_filter = None
2695                 opts.status_filter = None
2696                 opts.vertical = None
2697                 self.do_prjresults('prjresults', opts, *args);
2698                 sys.exit(0)
2699             else:
2700                 project = store_read_project(wd)
2701                 package = store_read_package(wd)
2702                 apiurl = store_read_apiurl(wd)
2703         elif len(args) < 2:
2704             raise oscerr.WrongArgs('Too few arguments (required none or two)')
2705         elif len(args) > 2:
2706             raise oscerr.WrongArgs('Too many arguments (required none or two)')
2707         else:
2708             project = args[0]
2709             package = args[1]
2710
2711         if not opts.xml:
2712             func = get_results
2713             delim = '\n'
2714         else:
2715             func = show_results_meta
2716             delim = ''
2717
2718         print delim.join(func(apiurl, project, package, opts.last_build, opts.repo, opts.arch))
2719
2720     # WARNING: this function is also called by do_results. You need to set a default there
2721     #          as well when adding a new option!
2722     @cmdln.option('-q', '--hide-legend', action='store_true',
2723                         help='hide the legend')
2724     @cmdln.option('-c', '--csv', action='store_true',
2725                         help='csv output')
2726     @cmdln.option('-s', '--status-filter', metavar='STATUS',
2727                         help='show only packages with buildstatus STATUS (see legend)')
2728     @cmdln.option('-n', '--name-filter', metavar='EXPR',
2729                         help='show only packages whose names match EXPR')
2730     @cmdln.option('-a', '--arch', metavar='ARCH',
2731                         help='show results only for specified architecture(s)')
2732     @cmdln.option('-r', '--repo', metavar='REPO',
2733                         help='show results only for specified repo(s)')
2734     @cmdln.option('-V', '--vertical', action='store_true',
2735                         help='list packages vertically instead horizontally')
2736     @cmdln.alias('pr')
2737     def do_prjresults(self, subcmd, opts, *args):
2738         """${cmd_name}: Shows project-wide build results
2739
2740         Usage:
2741             osc prjresults (inside working copy)
2742             osc prjresults PROJECT
2743
2744         ${cmd_option_list}
2745         """
2746
2747         if args:
2748             apiurl = conf.config['apiurl']
2749             if len(args) == 1:
2750                 project = args[0]
2751             else:
2752                 raise oscerr.WrongArgs('Wrong number of arguments.')
2753         else:
2754             wd = os.curdir
2755             project = store_read_project(wd)
2756             apiurl = store_read_apiurl(wd)
2757
2758         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))
2759
2760
2761     @cmdln.option('-q', '--hide-legend', action='store_true',
2762                         help='hide the legend')
2763     @cmdln.option('-c', '--csv', action='store_true',
2764                         help='csv output')
2765     @cmdln.option('-s', '--status-filter', metavar='STATUS',
2766                         help='show only packages with buildstatus STATUS (see legend)')
2767     @cmdln.option('-n', '--name-filter', metavar='EXPR',
2768                         help='show only packages whose names match EXPR')
2769
2770     @cmdln.hide(1)
2771     def do_rprjresults(self, subcmd, opts, *args):
2772         print "Command rprjresults is obsolete. Please use 'osc prjresults'"
2773         sys.exit(1)
2774
2775     @cmdln.alias('bl')
2776     @cmdln.option('-s', '--start', metavar='START',
2777                     help='get log starting from the offset')
2778     def do_buildlog(self, subcmd, opts, *args):
2779         """${cmd_name}: Shows the build log of a package
2780
2781         Shows the log file of the build of a package. Can be used to follow the
2782         log while it is being written.
2783         Needs to be called from within a package directory.
2784
2785         The arguments REPOSITORY and ARCH are the first two columns in the 'osc
2786         results' output. If the buildlog url is used buildlog command has the
2787         same behavior as remotebuildlog.
2788
2789         ${cmd_usage} [REPOSITORY ARCH | BUILDLOGURL]
2790         ${cmd_option_list}
2791         """
2792
2793         repository = arch = None
2794
2795         if len(args) == 1 and args[0].startswith('http'):
2796             apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2797         else:
2798             wd = os.curdir
2799             package = store_read_package(wd)
2800             project = store_read_project(wd)
2801             apiurl = store_read_apiurl(wd)
2802
2803         offset=0
2804         if opts.start:
2805             offset = int(opts.start)
2806
2807         if not repository or not arch:
2808             if len(args) < 2:
2809                 self.print_repos()
2810             else:
2811                 repository = args[0]
2812                 arch = args[1]
2813
2814         print_buildlog(apiurl, project, package, repository, arch, offset)
2815
2816
2817     def print_repos(self):
2818             wd = os.curdir
2819             doprint = False
2820             if is_package_dir(wd):
2821                 str = "package"
2822                 doprint = True
2823             elif is_project_dir(wd):
2824                 str = "project"
2825                 doprint = True
2826             
2827             if doprint:
2828                 print 'Valid arguments for this %s are:' % str
2829                 print
2830                 self.do_repos(None, None)
2831                 print
2832             raise oscerr.WrongArgs('Missing arguments')
2833
2834     @cmdln.alias('rbl')
2835     @cmdln.alias('rbuildlog')
2836     @cmdln.option('-s', '--start', metavar='START',
2837                     help='get log starting from the offset')
2838     def do_remotebuildlog(self, subcmd, opts, *args):
2839         """${cmd_name}: Shows the build log of a package
2840
2841         Shows the log file of the build of a package. Can be used to follow the
2842         log while it is being written.
2843
2844         usage:
2845             osc remotebuildlog project package repository arch
2846             or
2847             osc remotebuildlog project/package/repository/arch
2848             or
2849             osc remotebuildlog buildlogurl
2850         ${cmd_option_list}
2851         """
2852         if len(args) == 1 and args[0].startswith('http'):
2853             apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2854         else:
2855             args = slash_split(args)
2856             apiurl = conf.config['apiurl']
2857             if len(args) < 4:
2858                 raise oscerr.WrongArgs('Too few arguments.')
2859             elif len(args) > 4:
2860                 raise oscerr.WrongArgs('Too many arguments.')
2861             else:
2862                 project, package, repository, arch = args
2863         
2864         offset=0
2865         if opts.start:
2866             offset = int(opts.start)
2867
2868         print_buildlog(apiurl, project, package, repository, arch, offset)
2869
2870     @cmdln.alias('lbl')
2871     @cmdln.option('-s', '--start', metavar='START',
2872                   help='get log starting from offset')
2873     def do_localbuildlog(self, subcmd, opts, *args):
2874         """${cmd_name}: Shows the build log of a local buildchroot
2875
2876         usage:
2877             osc lbl [REPOSITORY ARCH]
2878             osc lbl # show log of newest last local build
2879
2880         ${cmd_option_list}
2881         """
2882         if conf.config['build-type']:
2883             # FIXME: raise Exception instead
2884             print >>sys.stderr, 'Not implemented for VMs'
2885             sys.exit(1)
2886
2887         if len(args) == 0:
2888             package = store_read_package('.')
2889             import glob
2890             files = glob.glob(os.path.join(os.getcwd(), store, "_buildinfo-*"))
2891             if not files:
2892                 self.print_repos()
2893                 raise oscerr.WrongArgs('No buildconfig found, please specify repo and arch manually.')
2894             cfg = files[0]
2895             # find newest file
2896             for f in files[1:]:
2897                 if os.stat(f).st_mtime > os.stat(cfg).st_mtime:
2898                     cfg = f
2899             root = ET.parse(cfg).getroot()
2900             project = root.get("project")
2901             repo = root.get("repository")
2902             arch = root.find("arch").text
2903         elif len(args) == 2:
2904             project = store_read_project('.')
2905             package = store_read_package('.')
2906             repo = args[0]
2907             arch = args[1]
2908         else:
2909             if is_package_dir(os.curdir):
2910                 self.print_repos()
2911             raise oscerr.WrongArgs('Wrong number of arguments.')
2912
2913         buildroot = os.environ.get('OSC_BUILD_ROOT', conf.config['build-root'])
2914         buildroot = buildroot % {'project': project, 'package': package,
2915                                  'repo': repo, 'arch': arch}
2916         offset = 0
2917         if opts.start:
2918             offset = int(opts.start)
2919         logfile = os.path.join(buildroot, '.build.log')
2920         if not os.path.isfile(logfile):
2921             raise oscerr.OscIOError(None, 'logfile \'%s\' does not exist' % logfile)
2922         f = open(logfile, 'r')
2923         f.seek(offset)
2924         data = f.read(BUFSIZE)
2925         while len(data):
2926             sys.stdout.write(data)
2927             data = f.read(BUFSIZE)
2928         f.close()
2929
2930     @cmdln.alias('tr')
2931     def do_triggerreason(self, subcmd, opts, *args):
2932         """${cmd_name}: Show reason why a package got triggered to build
2933
2934         The server decides when a package needs to get rebuild, this command
2935         shows the detailed reason for a package. A brief reason is also stored
2936         in the jobhistory, which can be accessed via "osc jobhistory".
2937
2938         Trigger reasons might be:
2939           - new build (never build yet or rebuild manually forced)
2940           - source change (eg. on updating sources)
2941           - meta change (packages which are used for building have changed)
2942           - rebuild count sync (In case that it is configured to sync release numbers)
2943
2944         usage in package or project directory:
2945             osc reason REPOSITORY ARCH
2946             osc reason PROJECT PACKAGE REPOSITORY ARCH
2947
2948         ${cmd_option_list}
2949         """
2950         wd = os.curdir
2951         args = slash_split(args)
2952         project = package = repository = arch = None
2953         
2954         if len(args) < 2:
2955             self.print_repos()
2956
2957         if len(args) == 2: # 2
2958             if is_package_dir('.'):
2959                  package = store_read_package(wd)
2960             else:
2961                  raise oscerr.WrongArgs('package is not specified.')
2962             project = store_read_project(wd)
2963             apiurl = store_read_apiurl(wd)
2964             repository = args[0]
2965             arch = args[1]
2966         elif len(args) == 4:
2967             apiurl = conf.config['apiurl']
2968             project = args[0]
2969             package = args[1]
2970             repository = args[2]
2971             arch = args[3]
2972         else:
2973             raise oscerr.WrongArgs('Too many arguments.')
2974
2975         print apiurl, project, package, repository, arch
2976         xml = show_package_trigger_reason(apiurl, project, package, repository, arch)
2977         root = ET.fromstring(xml)
2978         reason = root.find('explain').text
2979         print reason
2980         if reason == "meta change":
2981            print "changed keys:"
2982            for package in root.findall('packagechange'):
2983                print "  ", package.get('change'), package.get('key')
2984
2985
2986     # FIXME: the new osc syntax should allow to specify multiple packages
2987     # FIXME: the command should optionally use buildinfo data to show all dependencies
2988     @cmdln.alias('whatdependson')
2989     def do_dependson(self, subcmd, opts, *args):
2990         """${cmd_name}: Show the build dependencies
2991
2992         The command dependson and whatdependson can be used to find out what
2993         will be triggered when a certain package changes.
2994         This is no guarantee, since the new build might have changed dependencies.
2995
2996         dependson shows the build dependencies inside of a project, valid for a 
2997         given repository and architecture.
2998         NOTE: to see all binary packages, which can trigger a build you need to 
2999               refer the buildinfo, since this command shows only the dependencies
3000               inside of a project.
3001
3002         The arguments REPOSITORY and ARCH can be taken from the first two columns
3003         of the 'osc repos' output.
3004
3005         usage in package or project directory:
3006             osc dependson REPOSITORY ARCH
3007             osc whatdependson REPOSITORY ARCH
3008
3009         usage:
3010             osc dependson PROJECT [PACKAGE] REPOSITORY ARCH
3011             osc whatdependson PROJECT [PACKAGE] REPOSITORY ARCH
3012
3013         ${cmd_option_list}
3014         """
3015         wd = os.curdir
3016         args = slash_split(args)
3017         project = packages = repository = arch = reverse = None
3018         
3019         if len(args) < 2 and (is_package_dir('.') or is_project_dir('.')):
3020             self.print_repos()
3021
3022         if len(args) > 5:
3023             raise oscerr.WrongArgs('Too many arguments.')
3024
3025         if len(args) < 3: # 2
3026             if is_package_dir('.'):
3027                  packages = [store_read_package(wd)]
3028             elif not is_project_dir('.'):
3029                  raise oscerr.WrongArgs('Project and package is not specified.')
3030             project = store_read_project(wd)
3031             apiurl = store_read_apiurl(wd)
3032             repository = args[0]
3033             arch = args[1]
3034
3035         if len(args) == 3:
3036             apiurl = conf.config['apiurl']
3037             project = args[0]
3038             repository = args[1]
3039             arch = args[2]
3040
3041         if len(args) == 4:
3042             apiurl = conf.config['apiurl']
3043             project = args[0]
3044             packages = [args[1]]
3045             repository = args[2]
3046             arch = args[3]
3047
3048         if subcmd == 'whatdependson':
3049             reverse = 1
3050
3051         xml = get_dependson(apiurl, project, repository, arch, packages, reverse)
3052
3053         root = ET.fromstring(xml)
3054         for package in root.findall('package'):
3055             print package.get('name'), ":"
3056             for dep in package.findall('pkgdep'):
3057                print "  ", dep.text
3058
3059
3060     @cmdln.option('-x', '--extra-pkgs', metavar='PAC', action='append',
3061                   help='Add this package when computing the buildinfo')
3062     def do_buildinfo(self, subcmd, opts, *args):
3063         """${cmd_name}: Shows the build info
3064
3065         Shows the build "info" which is used in building a package.
3066         This command is mostly used internally by the 'build' subcommand.
3067         It needs to be called from within a package directory.
3068
3069         The BUILD_DESCR argument is optional. BUILD_DESCR is a local RPM specfile
3070         or Debian "dsc" file. If specified, it is sent to the server, and the
3071         buildinfo will be based on it. If the argument is not supplied, the
3072         buildinfo is derived from the specfile which is currently on the source
3073         repository server.
3074
3075         The returned data is XML and contains a list of the packages used in
3076         building, their source, and the expanded BuildRequires.
3077
3078         The arguments REPOSITORY and ARCH can be taken from the first two columns
3079         of the 'osc repos' output.
3080
3081         usage:
3082             osc buildinfo REPOSITORY ARCH [BUILD_DESCR]    (in pkg dir)
3083             osc buildinfo PROJECT PACKAGE REPOSITORY ARCH [BUILD_DESCR]
3084
3085         ${cmd_option_list}
3086         """
3087         wd = os.curdir
3088         args = slash_split(args)
3089         
3090         if len(args) < 2 and is_package_dir('.'):
3091             self.print_repos()
3092
3093         if len(args) > 5:
3094             raise oscerr.WrongArgs('Too many arguments.')
3095
3096         if len(args) < 4: # 2 or 3
3097             package = store_read_package(wd)
3098             project = store_read_project(wd)
3099             apiurl = store_read_apiurl(wd)
3100             repository = args[0]
3101             arch = args[1]
3102
3103         if len(args) > 3 and len(args) < 6: # 4 or 5
3104             apiurl = conf.config['apiurl']
3105             project = args[0]
3106             package = args[1]
3107             repository = args[2]
3108             arch =&