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