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