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