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