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