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