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