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