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