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