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