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