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