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