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