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