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