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