changed do_repos() to filter out disabled repos of a package
[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('Successfully 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         usage:
1336             osc deletereq [-m TEXT]                        # works in checked out project/package
1337             osc deletereq [-m TEXT] PROJECT [PACKAGE]
1338         ${cmd_option_list}
1339         """
1340
1341         args = slash_split(args)
1342
1343         project = None
1344         package = None
1345
1346         if len(args) < 1:
1347             if is_project_dir(os.getcwd()):
1348                 project = store_read_project(os.curdir)
1349             elif is_package_dir(os.getcwd()):
1350                 project = store_read_project(os.curdir)
1351                 package = store_read_package(os.curdir)
1352             else: 
1353                 raise oscerr.WrongArgs('Please specify at least a project.')
1354         elif len(args) == 1:
1355             project = args[0]
1356         elif len(args) > 1:
1357             project = args[0]
1358             package = args[1]
1359         elif len(args) > 2:
1360             raise oscerr.WrongArgs('Too many arguments.')
1361
1362         apiurl = self.get_api_url()
1363
1364         if not opts.message:
1365             import textwrap
1366             if package is not None:
1367                 footer=textwrap.TextWrapper(width = 66).fill(
1368                         'please explain why you like to delete package %s of project %s'
1369                         % (package,project))
1370             else:
1371                 footer=textwrap.TextWrapper(width = 66).fill(
1372                         'please explain why you like to delete project %s' % project)
1373             opts.message = edit_message(footer)
1374
1375         result = create_delete_request(apiurl, project, package, opts.message)
1376         print result
1377
1378
1379     @cmdln.option('-m', '--message', metavar='TEXT',
1380                   help='specify message TEXT')
1381     @cmdln.alias("cr")
1382     @cmdln.alias("changedevelreq")
1383     def do_changedevelrequest(self, subcmd, opts, *args):
1384         """${cmd_name}: Create request to change the devel package definition.
1385
1386         [See http://en.opensuse.org/Build_Service/Collaboration for information
1387         on this topic.]
1388
1389         See the "request" command for showing and modifing existing requests.
1390
1391         osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
1392         """
1393
1394         if len(args) > 4:
1395             raise oscerr.WrongArgs('Too many arguments.')
1396
1397         apiurl = self.get_api_url()
1398
1399         if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
1400             wd = os.curdir
1401             devel_project = store_read_project(wd)
1402             devel_package = package = store_read_package(wd)
1403             project = conf.config['getpac_default_project']
1404         else:
1405             if len(args) < 3:
1406                 raise oscerr.WrongArgs('Too few arguments.')
1407
1408             devel_project = args[2]
1409             project = args[0]
1410             package = args[1]
1411             devel_package = package
1412             if len(args) > 3:
1413                 devel_package = args[3]
1414
1415         if not opts.message:
1416             import textwrap
1417             footer=textwrap.TextWrapper(width = 66).fill(
1418                     'please explain why you like to change the devel project of %s/%s to %s/%s'
1419                     % (project,package,devel_project,devel_package))
1420             opts.message = edit_message(footer)
1421
1422         result = create_change_devel_request(apiurl,
1423                                        devel_project, devel_package,
1424                                        project, package,
1425                                        opts.message)
1426         print result
1427
1428
1429     @cmdln.option('-d', '--diff', action='store_true',
1430                   help='generate a diff')
1431     @cmdln.option('-u', '--unified', action='store_true',
1432                   help='output the diff in the unified diff format')
1433     @cmdln.option('-m', '--message', metavar='TEXT',
1434                   help='specify message TEXT')
1435     @cmdln.option('-t', '--type', metavar='TYPE',
1436                   help='limit to requests which contain a given action type (submit/delete/change_devel)')
1437     @cmdln.option('-a', '--all', action='store_true',
1438                         help='all states. Same as\'-s all\'')
1439     @cmdln.option('-s', '--state', default='',  # default is 'all' if no args given, 'new' otherwise
1440                         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]')
1441     @cmdln.option('-D', '--days', metavar='DAYS',
1442                         help='only list requests in state "new" or changed in the last DAYS. [default=%(request_list_days)s]')
1443     @cmdln.option('-U', '--user', metavar='USER',
1444                         help='same as -M, but for the specified USER')
1445     @cmdln.option('-b', '--brief', action='store_true', default=False,
1446                         help='print output in list view as list subcommand')
1447     @cmdln.option('-M', '--mine', action='store_true',
1448                         help='only show requests created by yourself')
1449     @cmdln.option('-B', '--bugowner', action='store_true',
1450                         help='also show requests about packages where I am bugowner')
1451     @cmdln.option('-i', '--interactive', action='store_true',
1452                         help='interactive review of request')
1453     @cmdln.option('--non-interactive', action='store_true',
1454                         help='non-interactive review of request')
1455     @cmdln.option('--exclude-target-project', action='append',
1456                         help='exclude target project from request list')
1457     @cmdln.option('--involved-projects', action='store_true',
1458                         help='show all requests for project/packages where USER is involved')
1459     @cmdln.alias("rq")
1460     @cmdln.alias("review")
1461     def do_request(self, subcmd, opts, *args):
1462         """${cmd_name}: Show and modify requests
1463
1464         [See http://en.opensuse.org/Build_Service/Collaboration for information
1465         on this topic.]
1466
1467         This command shows and modifies existing requests. To create new requests
1468         you need to call one of the following:
1469           osc submitrequest
1470           osc deleterequest
1471           osc changedevelrequest
1472         To send low level requests to the buildservice API, use:
1473           osc api
1474
1475         This command has the following sub commands:
1476
1477         "list" lists open requests attached to a project or package or person.
1478         Uses the project/package of the current directory if none of
1479         -M, -U USER, project/package are given.
1480
1481         "log" will show the history of the given ID
1482
1483         "show" will show the request itself, and generate a diff for review, if
1484         used with the --diff option. The keyword show can be omitted if the ID is numeric.
1485
1486         "decline" will change the request state to "declined" and append a
1487         message that you specify with the --message option.
1488
1489         "wipe" will permanently delete a request.
1490
1491         "revoke" will set the request state to "revoked" and append a
1492         message that you specify with the --message option.
1493
1494         "accept" will change the request state to "accepted" and will trigger
1495         the actual submit process. That would normally be a server-side copy of
1496         the source package to the target package.
1497
1498         "checkout" will checkout the request's source package. This only works for "submit" requests.
1499
1500         usage:
1501             osc request list [-M] [-U USER] [-s state] [-D DAYS] [-t type] [-B] [PRJ [PKG]]
1502             osc request log ID
1503             osc request [show] [-d] [-b] ID
1504             osc request accept [-m TEXT] ID
1505             osc request reopen [-m TEXT] ID
1506             osc request approvenew [-m TEXT] PROJECT
1507             osc request decline [-m TEXT] ID
1508             osc request revoke [-m TEXT] ID
1509             osc request wipe ID
1510             osc request checkout/co ID
1511             osc review accept [-m TEXT] ID
1512             osc review decline [-m TEXT] ID
1513             osc review new [-m TEXT] ID            # for setting a temporary comment without changing the state
1514         ${cmd_option_list}
1515         """
1516
1517         args = slash_split(args)
1518
1519         if opts.all and opts.state:
1520             raise oscerr.WrongOptions('Sorry, the options \'--all\' and \'--state\' ' \
1521                     'are mutually exclusive.')
1522         if opts.mine and opts.user:
1523             raise oscerr.WrongOptions('Sorry, the options \'--user\' and \'--mine\' ' \
1524                     'are mutually exclusive.')
1525         if opts.interactive and opts.non_interactive:
1526             raise oscerr.WrongOptions('Sorry, the options \'--interactive\' and ' \
1527                     '\'--non-interactive\' are mutually exclusive')
1528
1529         if not args:
1530             args = [ 'list' ]
1531             opts.mine = 1
1532             if opts.state == '':
1533                 opts.state = 'all'
1534
1535         if opts.state == '':
1536             opts.state = 'new'
1537
1538         cmds = ['list', 'log', 'show', 'decline', 'reopen', 'accept', 'approvenew', 'wipe', 'revoke', 'checkout', 'co', 'help']
1539         if not args or args[0] not in cmds:
1540             raise oscerr.WrongArgs('Unknown request action %s. Choose one of %s.' \
1541                                                % (args[0],', '.join(cmds)))
1542
1543         cmd = args[0]
1544         del args[0]
1545
1546         if cmd == 'help':
1547             return self.do_help(['help', 'request'])
1548
1549         if cmd in ['list']:
1550             min_args, max_args = 0, 2
1551         else:
1552             min_args, max_args = 1, 1
1553         if len(args) < min_args:
1554             raise oscerr.WrongArgs('Too few arguments.')
1555         if len(args) > max_args:
1556             raise oscerr.WrongArgs('Too many arguments.')
1557
1558         apiurl = self.get_api_url()
1559
1560         if cmd == 'list' or cmd == 'approvenew':
1561             package = None
1562             project = None
1563             if len(args) > 0:
1564                 project = args[0]
1565             elif not opts.mine and not opts.user:
1566                 try:
1567                     project = store_read_project(os.curdir)
1568                     package = store_read_package(os.curdir)
1569                 except oscerr.NoWorkingCopy:
1570                     pass
1571
1572             if len(args) > 1:
1573                 package = args[1]
1574         elif cmd in ['log', 'show', 'decline', 'reopen', 'accept', 'wipe', 'revoke', 'checkout', 'co']:
1575             reqid = args[0]
1576
1577         # list and approvenew
1578         if cmd == 'list' or cmd == 'approvenew':
1579             states = ('new', 'accepted', 'revoked', 'declined')
1580             who = ''
1581             if cmd == 'approvenew':
1582                states = ('new')
1583                results = get_request_list(apiurl, project, package, '', ['new'])
1584             else:
1585                state_list = opts.state.split(',')
1586                if opts.state == 'all':
1587                    state_list = ['all']
1588                else:
1589                    for s in state_list:
1590                        if not s in states:
1591                            raise oscerr.WrongArgs('Unknown state \'%s\', try one of %s' % (s, ','.join(states)))
1592                if opts.mine:
1593                    who = conf.get_apiurl_usr(apiurl)
1594                if opts.user:
1595                    who = opts.user
1596                if opts.all:
1597                    state_list = ['all']
1598
1599                ## FIXME -B not implemented!
1600                if opts.bugowner:
1601                    if (self.options.debug):
1602                        print 'list: option --bugowner ignored: not impl.'
1603
1604                if opts.involved_projects:
1605                    who = who or conf.get_apiurl_usr(apiurl)
1606                    results = get_user_projpkgs_request_list(apiurl, who, req_state=state_list,
1607                                                             req_type=opts.type, exclude_projects=opts.exclude_target_project or [])
1608                else:
1609                    results = get_request_list(apiurl, project, package, who,
1610                                               state_list, opts.type, opts.exclude_target_project or [])
1611
1612             results.sort(reverse=True)
1613             import time
1614             days = opts.days or conf.config['request_list_days']
1615             since = ''
1616             try:
1617                 days = int(days)
1618             except ValueError:
1619                 days = 0
1620             if days > 0:
1621                 since = time.strftime('%Y-%m-%dT%H:%M:%S',time.localtime(time.time()-days*24*3600))
1622
1623             skipped = 0
1624             ## bs has received 2009-09-20 a new xquery compare() function
1625             ## which allows us to limit the list inside of get_request_list
1626             ## That would be much faster for coolo. But counting the remainder
1627             ## would not be possible with current xquery implementation.
1628             ## Workaround: fetch all, and filter on client side.
1629
1630             ## FIXME: date filtering should become implemented on server side
1631             for result in results:
1632                 if days == 0 or result.state.when > since or result.state.name == 'new':
1633                     print result.list_view()
1634                 else:
1635                     skipped += 1
1636             if skipped:
1637                 print "There are %d requests older than %s days.\n" % (skipped, days)
1638
1639             if cmd == 'approvenew':
1640                 print "\n *** Approve them all ? [y/n] ***"
1641                 if sys.stdin.read(1) == "y":
1642     
1643                     if not opts.message:
1644                         opts.message = edit_message()
1645                     for result in results:
1646                         print result.reqid, ": ",
1647                         r = change_request_state(conf.config['apiurl'],
1648                                 str(result.reqid), 'accepted', opts.message or '')
1649                         print r
1650                 else:
1651                     print >>sys.stderr, 'Aborted...'
1652                     raise oscerr.UserAbort()
1653
1654         elif cmd == 'log':
1655             for l in get_request_log(conf.config['apiurl'], reqid):
1656                 print l
1657
1658         # show
1659         elif cmd == 'show':
1660             r = get_request(conf.config['apiurl'], reqid)
1661             if opts.brief:
1662                 print r.list_view()
1663             elif (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive:
1664                 return request_interactive_review(conf.config['apiurl'], r)
1665             else:
1666                 print r
1667             # fixme: will inevitably fail if the given target doesn't exist
1668             if opts.diff and r.actions[0].type != 'submit':
1669                 raise oscerr.WrongOptions('\'--diff\' is not possible for request type: \'%s\'' % r.actions[0].type)
1670             elif opts.diff:
1671                 try:
1672                     print server_diff(conf.config['apiurl'],
1673                                       r.actions[0].dst_project, r.actions[0].dst_package, None,
1674                                       r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, True)
1675                 except urllib2.HTTPError, e:
1676                     if e.code != 400:
1677                         e.osc_msg = 'Diff not possible'
1678                         raise e
1679                     # backward compatiblity: only a recent api/backend supports the missingok parameter
1680                     try:
1681                         print server_diff(conf.config['apiurl'],
1682                                           r.actions[0].dst_project, r.actions[0].dst_package, None,
1683                                           r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, False)
1684                     except urllib2.HTTPError, e:
1685                         e.osc_msg = 'Diff not possible'
1686                         raise
1687
1688         # checkout
1689         elif cmd == 'checkout' or cmd == 'co':
1690             r = get_request(conf.config['apiurl'], reqid)
1691             submits = [ i for i in r.actions if i.type == 'submit' ]
1692             if not len(submits):
1693                 raise oscerr.WrongArgs('\'checkout\' only works for \'submit\' requests')
1694             checkout_package(conf.config['apiurl'], submits[0].src_project, submits[0].src_package, \
1695                 submits[0].src_rev, expand_link=True, prj_dir=submits[0].src_project)
1696
1697         else:
1698             if not opts.message:
1699                 opts.message = edit_message()
1700             state_map = {'reopen' : 'new', 'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked'}
1701             # Change review state only
1702             if subcmd == 'review':
1703                 if cmd in ['accept', 'decline', 'new']:
1704                     r = change_review_state(conf.config['apiurl'],
1705                             reqid, state_map[cmd], conf.config['user'], '', opts.message or '')
1706                     print r
1707             # Change state of entire request
1708             elif cmd in ['reopen', 'accept', 'decline', 'wipe', 'revoke']:
1709                 r = change_request_state(conf.config['apiurl'],
1710                         reqid, state_map[cmd], opts.message or '')
1711                 print r
1712
1713     # editmeta and its aliases are all depracated
1714     @cmdln.alias("editprj")
1715     @cmdln.alias("createprj")
1716     @cmdln.alias("editpac")
1717     @cmdln.alias("createpac")
1718     @cmdln.alias("edituser")
1719     @cmdln.alias("usermeta")
1720     @cmdln.hide(1)
1721     def do_editmeta(self, subcmd, opts, *args):
1722         """${cmd_name}:
1723
1724         Obsolete command to edit metadata. Use 'meta' now.
1725
1726         See the help output of 'meta'.
1727
1728         """
1729
1730         print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
1731         print >>sys.stderr, 'See \'osc help meta\'.'
1732         #self.do_help([None, 'meta'])
1733         return 2
1734
1735
1736     @cmdln.option('-r', '--revision', metavar='rev',
1737                   help='use the specified revision.')
1738     @cmdln.option('-u', '--unset', action='store_true',
1739                   help='remove revision in link, it will point always to latest revision')
1740     def do_setlinkrev(self, subcmd, opts, *args):
1741         """${cmd_name}: Updates a revision number in a source link.
1742
1743         This command adds or updates a specified revision number in a source link.
1744         The current revision of the source is used, if no revision number is specified.
1745
1746         usage:
1747             osc setlinkrev
1748             osc setlinkrev PROJECT [PACKAGE]
1749         ${cmd_option_list}
1750         """
1751
1752         args = slash_split(args)
1753         apiurl = conf.config['apiurl']
1754         package = None
1755         if len(args) == 0:
1756             p = findpacs(os.curdir)[0]
1757             project = p.prjname
1758             package = p.name
1759             apiurl = p.apiurl
1760             if not p.islink():
1761                 sys.exit('Local directory is no checked out source link package, aborting')
1762         elif len(args) == 2:
1763             project = args[0]
1764             package = args[1]
1765         elif len(args) == 1:
1766             project = args[0]
1767         else:
1768             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1769                   + self.get_cmd_help('setlinkrev'))
1770
1771         if package:
1772             packages = [ package ]
1773         else:
1774             packages = meta_get_packagelist(apiurl, project)
1775
1776         for p in packages:
1777             print "setting revision for package", p
1778             if opts.unset:
1779                 rev=-1
1780             else:
1781                 rev, dummy = parseRevisionOption(opts.revision)
1782             set_link_rev(apiurl, project, p, rev)
1783
1784
1785     def do_linktobranch(self, subcmd, opts, *args):
1786         """${cmd_name}: Convert a package containing a classic link with patch to a branch
1787
1788         This command tells the server to convert a _link with or without a project.diff
1789         to a branch. This is a full copy with a _link file pointing to the branched place.
1790
1791         usage:
1792             osc linktobranch                    # can be used in checked out package
1793             osc linktobranch PROJECT PACKAGE
1794         ${cmd_option_list}
1795         """
1796         args = slash_split(args)
1797         apiurl = self.get_api_url()
1798
1799         if len(args) == 0:
1800             wd = os.curdir
1801             project = store_read_project(wd)
1802             package = store_read_package(wd)
1803             update_local_dir = True
1804         elif len(args) < 2:
1805             raise oscerr.WrongArgs('Too few arguments (required none or two)')
1806         elif len(args) > 2:
1807             raise oscerr.WrongArgs('Too many arguments (required none or two)')
1808         else:
1809             project = args[0]
1810             package = args[1]
1811             update_local_dir = False
1812
1813         # execute
1814         link_to_branch(apiurl, project, package)
1815         if update_local_dir:
1816             pac = Package(wd)
1817             pac.update(rev=pac.latest_rev())
1818
1819
1820     @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'],
1821                   help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.')
1822     @cmdln.option('-c', '--current', action='store_true',
1823                   help='link fixed against current revision.')
1824     @cmdln.option('-r', '--revision', metavar='rev',
1825                   help='link the specified revision.')
1826     @cmdln.option('-f', '--force', action='store_true',
1827                   help='overwrite an existing link file if it is there.')
1828     @cmdln.option('-d', '--disable-publish', action='store_true',
1829                   help='disable publishing of the linked package')
1830     def do_linkpac(self, subcmd, opts, *args):
1831         """${cmd_name}: "Link" a package to another package
1832
1833         A linked package is a clone of another package, but plus local
1834         modifications. It can be cross-project.
1835
1836         The DESTPAC name is optional; the source packages' name will be used if
1837         DESTPAC is omitted.
1838
1839         Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
1840
1841         To add a patch, add the patch as file and add it to the _link file.
1842         You can also specify text which will be inserted at the top of the spec file.
1843
1844         See the examples in the _link file.
1845
1846         usage:
1847             osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1848         ${cmd_option_list}
1849         """
1850
1851         args = slash_split(args)
1852
1853         if not args or len(args) < 3:
1854             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1855                   + self.get_cmd_help('linkpac'))
1856
1857         rev, dummy = parseRevisionOption(opts.revision)
1858
1859         src_project = args[0]
1860         src_package = args[1]
1861         dst_project = args[2]
1862         if len(args) > 3:
1863             dst_package = args[3]
1864         else:
1865             dst_package = src_package
1866
1867         if src_project == dst_project and src_package == dst_package:
1868             raise oscerr.WrongArgs('Error: source and destination are the same.')
1869
1870         if src_project == dst_project and not opts.cicount:
1871             # in this case, the user usually wants to build different spec
1872             # files from the same source
1873             opts.cicount = "copy"
1874
1875         if opts.current:
1876             rev = show_upstream_rev(conf.config['apiurl'], src_project, src_package)
1877
1878         if rev and not checkRevision(src_project, src_package, rev):
1879             print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
1880             sys.exit(1)
1881
1882         link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish)
1883
1884     @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]',
1885                   help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]')
1886     @cmdln.option('-d', '--disable-publish', action='store_true',
1887                   help='disable publishing of the aggregated package')
1888     def do_aggregatepac(self, subcmd, opts, *args):
1889         """${cmd_name}: "Aggregate" a package to another package
1890
1891         Aggregation of a package means that the build results (binaries) of a
1892         package are basically copied into another project.
1893         This can be used to make packages available from building that are
1894         needed in a project but available only in a different project. Note
1895         that this is done at the expense of disk space. See
1896         http://en.opensuse.org/Build_Service/Tips_and_Tricks#_link_and__aggregate
1897         for more information.
1898
1899         The DESTPAC name is optional; the source packages' name will be used if
1900         DESTPAC is omitted.
1901
1902         usage:
1903             osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1904         ${cmd_option_list}
1905         """
1906
1907         args = slash_split(args)
1908
1909         if not args or len(args) < 3:
1910             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1911                   + self.get_cmd_help('aggregatepac'))
1912
1913         src_project = args[0]
1914         src_package = args[1]
1915         dst_project = args[2]
1916         if len(args) > 3:
1917             dst_package = args[3]
1918         else:
1919             dst_package = src_package
1920
1921         if src_project == dst_project and src_package == dst_package:
1922             raise oscerr.WrongArgs('Error: source and destination are the same.')
1923
1924         repo_map = {}
1925         if opts.map_repo:
1926             for pair in opts.map_repo.split(','):
1927                 src_tgt = pair.split('=')
1928                 if len(src_tgt) != 2:
1929                     raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo)
1930                 repo_map[src_tgt[0]] = src_tgt[1]
1931
1932         aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish)
1933
1934
1935     @cmdln.option('-c', '--client-side-copy', action='store_true',
1936                         help='do a (slower) client-side copy')
1937     @cmdln.option('-k', '--keep-maintainers', action='store_true',
1938                         help='keep original maintainers. Default is remove all and replace with the one calling the script.')
1939     @cmdln.option('-d', '--keep-develproject', action='store_true',
1940                         help='keep develproject tag in the package metadata')
1941     @cmdln.option('-r', '--revision', metavar='rev',
1942                         help='link the specified revision.')
1943     @cmdln.option('-t', '--to-apiurl', metavar='URL',
1944                         help='URL of destination api server. Default is the source api server.')
1945     @cmdln.option('-m', '--message', metavar='TEXT',
1946                   help='specify message TEXT')
1947     @cmdln.option('-e', '--expand', action='store_true',
1948                         help='if the source package is a link then copy the expanded version of the link')
1949     def do_copypac(self, subcmd, opts, *args):
1950         """${cmd_name}: Copy a package
1951
1952         A way to copy package to somewhere else.
1953
1954         It can be done across buildservice instances, if the -t option is used.
1955         In that case, a client-side copy is implied.
1956
1957         Using --client-side-copy always involves downloading all files, and
1958         uploading them to the target.
1959
1960         The DESTPAC name is optional; the source packages' name will be used if
1961         DESTPAC is omitted.
1962
1963         usage:
1964             osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1965         ${cmd_option_list}
1966         """
1967
1968         args = slash_split(args)
1969
1970         if not args or len(args) < 3:
1971             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1972                   + self.get_cmd_help('copypac'))
1973
1974         src_project = args[0]
1975         src_package = args[1]
1976         dst_project = args[2]
1977         if len(args) > 3:
1978             dst_package = args[3]
1979         else:
1980             dst_package = src_package
1981
1982         src_apiurl = conf.config['apiurl']
1983         if opts.to_apiurl:
1984             dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl)
1985         else:
1986             dst_apiurl = src_apiurl
1987
1988         if src_apiurl != dst_apiurl:
1989             opts.client_side_copy = True
1990
1991         rev, dummy = parseRevisionOption(opts.revision)
1992
1993         if opts.message:
1994             comment = opts.message
1995         else:
1996             if not rev:
1997                 rev = show_upstream_rev(src_apiurl, src_project, src_package)
1998             comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev )
1999
2000         if src_project == dst_project and \
2001            src_package == dst_package and \
2002            not rev and \
2003            src_apiurl == dst_apiurl:
2004             raise oscerr.WrongArgs('Source and destination are the same.')
2005
2006         r = copy_pac(src_apiurl, src_project, src_package,
2007                      dst_apiurl, dst_project, dst_package,
2008                      client_side_copy=opts.client_side_copy,
2009                      keep_maintainers=opts.keep_maintainers,
2010                      keep_develproject=opts.keep_develproject,
2011                      expand=opts.expand,
2012                      revision=rev,
2013                      comment=comment)
2014         print r
2015
2016
2017     @cmdln.option('-c', '--checkout', action='store_true',
2018                         help='Checkout branched package afterwards ' \
2019                                 '(\'osc bco\' is a shorthand for this option)' )
2020     @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
2021                         help='Use this attribute to find affected packages (default is OBS:Maintained)')
2022     @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE',
2023                         help='Use this attribute to find update projects (default is OBS:UpdateProject) ')
2024     def do_mbranch(self, subcmd, opts, *args):
2025         """${cmd_name}: Multiple branch of a package
2026
2027         [See http://en.opensuse.org/Build_Service/Concepts/Maintenance for information
2028         on this topic.]
2029
2030         This command is used for creating multiple links of defined version of a package
2031         in one project. This is esp. used for maintenance updates.
2032
2033         The branched package will live in
2034             home:USERNAME:branches:ATTRIBUTE:PACKAGE
2035         if nothing else specified.
2036
2037         usage:
2038             osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ]
2039         ${cmd_option_list}
2040         """
2041         args = slash_split(args)
2042         tproject = None
2043
2044         maintained_attribute = conf.config['maintained_attribute']
2045         maintained_update_project_attribute = conf.config['maintained_update_project_attribute']
2046
2047         if not len(args) or len(args) > 2:
2048             raise oscerr.WrongArgs('Wrong number of arguments.')
2049         if len(args) >= 1:
2050             package = args[0]
2051         if len(args) >= 2:
2052             tproject = args[1]
2053
2054         r = attribute_branch_pkg(conf.config['apiurl'], maintained_attribute, maintained_update_project_attribute, \
2055                                  package, tproject)
2056
2057         if r is None:
2058             print >>sys.stderr, 'ERROR: Attribute branch call came not back with a project.'
2059             sys.exit(1)
2060
2061         print "Project " + r + " created."
2062
2063         if opts.checkout:
2064             init_project_dir(conf.config['apiurl'], r, r)
2065             print statfrmt('A', r)
2066
2067             # all packages
2068             for package in meta_get_packagelist(conf.config['apiurl'], r):
2069                 try:
2070                     checkout_package(conf.config['apiurl'], r, package, expand_link = True, prj_dir = r)
2071                 except:
2072                     print >>sys.stderr, 'Error while checkout package:\n', package
2073
2074             if conf.config['verbose']:
2075                 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
2076
2077
2078     @cmdln.alias('branchco')
2079     @cmdln.alias('bco')
2080     @cmdln.alias('getpac')
2081     @cmdln.option('--nodevelproject', action='store_true',
2082                         help='do not follow a defined devel project ' \
2083                              '(primary project where a package is developed)')
2084     @cmdln.option('-c', '--checkout', action='store_true',
2085                         help='Checkout branched package afterwards ' \
2086                                 '(\'osc bco\' is a shorthand for this option)' )
2087     @cmdln.option('-f', '--force', default=False, action="store_true",
2088                   help='force branch, overwrite target')
2089     @cmdln.option('-m', '--message', metavar='TEXT',
2090                         help='specify message TEXT')
2091     @cmdln.option('-r', '--revision', metavar='rev',
2092                         help='branch against a specific revision')
2093     def do_branch(self, subcmd, opts, *args):
2094         """${cmd_name}: Branch a package
2095
2096         [See http://en.opensuse.org/Build_Service/Collaboration for information
2097         on this topic.]
2098
2099         Create a source link from a package of an existing project to a new
2100         subproject of the requesters home project (home:branches:)
2101
2102         The branched package will live in
2103             home:USERNAME:branches:PROJECT/PACKAGE
2104         if nothing else specified.
2105
2106         With getpac or bco, the branched package will come from
2107             %(getpac_default_project)s
2108         if nothing else specified.
2109
2110         usage:
2111             osc branch
2112             osc branch SOURCEPROJECT SOURCEPACKAGE
2113             osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT
2114             osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE
2115             osc getpac  SOURCEPACKAGE
2116             osc bco ...
2117         ${cmd_option_list}
2118         """
2119
2120         if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': opts.checkout = True
2121         args = slash_split(args)
2122         tproject = tpackage = None
2123
2124         if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1:
2125             print >>sys.stderr, 'defaulting to %s/%s' % (conf.config['getpac_default_project'], args[0])
2126             # python has no args.unshift ???
2127             args = [ conf.config['getpac_default_project'] , args[0] ]
2128             
2129         if len(args) == 0 and is_package_dir('.'):
2130             args = (store_read_project('.'), store_read_package('.'))
2131
2132         if len(args) < 2 or len(args) > 4:
2133             raise oscerr.WrongArgs('Wrong number of arguments.')
2134
2135         expected = 'home:%s:branches:%s' % (conf.config['user'], args[0])
2136         if len(args) >= 3:
2137             expected = tproject = args[2]
2138         if len(args) >= 4:
2139             tpackage = args[3]
2140
2141         if not opts.message:
2142                 footer='please specify the purpose of your branch'
2143                 template='This package was branched from %s in order to ...\n' % args[0]
2144                 opts.message = edit_message(footer, template)
2145
2146         exists, targetprj, targetpkg, srcprj, srcpkg = \
2147                 branch_pkg(conf.config['apiurl'], args[0], args[1],
2148                            nodevelproject=opts.nodevelproject, rev=opts.revision,
2149                            target_project=tproject, target_package=tpackage,
2150                            return_existing=opts.checkout, msg=opts.message or '',
2151                            force=opts.force)
2152         if exists:
2153             print >>sys.stderr, 'Using existing branch project: %s' % targetprj
2154
2155         devloc = None
2156         if not exists and (srcprj is not None and srcprj != args[0] or \
2157                            srcprj is None and targetprj != expected):
2158             devloc = srcprj or targetprj
2159             if not srcprj and 'branches:' in targetprj:
2160                 devloc = targetprj.split('branches:')[1]
2161             print '\nNote: The branch has been created of a different project,\n' \
2162                   '              %s,\n' \
2163                   '      which is the primary location of where development for\n' \
2164                   '      that package takes place.\n' \
2165                   '      That\'s also where you would normally make changes against.\n' \
2166                   '      A direct branch of the specified package can be forced\n' \
2167                   '      with the --nodevelproject option.\n' % devloc
2168
2169         package = tpackage or args[1]
2170         if opts.checkout:
2171             checkout_package(conf.config['apiurl'], targetprj, package,
2172                              expand_link=True, prj_dir=targetprj)
2173             if conf.config['verbose']:
2174                 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
2175         else:
2176             apiopt = ''
2177             if conf.get_configParser().get('general', 'apiurl') != conf.config['apiurl']:
2178                 apiopt = '-A %s ' % conf.config['apiurl']
2179             print 'A working copy of the branched package can be checked out with:\n\n' \
2180                   'osc %sco %s/%s' \
2181                       % (apiopt, targetprj, package)
2182         print_request_list(conf.config['apiurl'], args[0], args[1])
2183         if devloc:
2184             print_request_list(conf.config['apiurl'], devloc, args[1])
2185
2186
2187     def do_undelete(self, subcmd, opts, *args):
2188         """${cmd_name}: Restores a deleted project or package on the server.
2189
2190         The server restores a package including the sources and meta configuration.
2191         Binaries remain to be lost and will be rebuild.
2192
2193         usage:
2194            osc undelete PROJECT
2195            osc undelete PROJECT PACKAGE [PACKAGE ...]
2196
2197         ${cmd_option_list}
2198         """
2199
2200         args = slash_split(args)
2201         if len(args) < 1:
2202             raise oscerr.WrongArgs('Missing argument.')
2203         prj = args[0]
2204         pkgs = args[1:]
2205
2206         if pkgs:
2207             for pkg in pkgs:
2208                 undelete_package(conf.config['apiurl'], prj, pkg)
2209         else:
2210             undelete_project(conf.config['apiurl'], prj)
2211
2212
2213     @cmdln.option('-f', '--force', action='store_true',
2214                         help='deletes a package or an empty project')
2215     def do_rdelete(self, subcmd, opts, *args):
2216         """${cmd_name}: Delete a project or packages on the server.
2217
2218         As a safety measure, project must be empty (i.e., you need to delete all
2219         packages first). If you are sure that you want to remove this project and all
2220         its packages use \'--force\' switch.
2221
2222         usage:
2223            osc rdelete -f PROJECT
2224            osc rdelete PROJECT PACKAGE [PACKAGE ...]
2225
2226         ${cmd_option_list}
2227         """
2228
2229         args = slash_split(args)
2230         if len(args) < 1:
2231             raise oscerr.WrongArgs('Missing argument.')
2232         prj = args[0]
2233         pkgs = args[1:]
2234
2235         if pkgs:
2236             for pkg in pkgs:
2237                # careful: if pkg is an empty string, the package delete request results
2238                # into a project delete request - which works recursively...
2239                 if pkg:
2240                     delete_package(conf.config['apiurl'], prj, pkg)
2241         elif len(meta_get_packagelist(conf.config['apiurl'], prj)) >= 1 and not opts.force:
2242             print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
2243                                 'If you are sure that you want to remove this project and all its ' \
2244                                 'packages use the \'--force\' switch'
2245             sys.exit(1)
2246         else:
2247             delete_project(conf.config['apiurl'], prj)
2248
2249     @cmdln.hide(1)
2250     def do_deletepac(self, subcmd, opts, *args):
2251         print """${cmd_name} is obsolete !
2252
2253                  Please use either
2254                    osc delete       for checked out packages or projects
2255                  or
2256                    osc rdelete      for server side operations."""
2257
2258         sys.exit(1)
2259
2260     @cmdln.hide(1)
2261     @cmdln.option('-f', '--force', action='store_true',
2262                         help='deletes a project and its packages')
2263     def do_deleteprj(self, subcmd, opts, project):
2264         """${cmd_name} is obsolete !
2265
2266                  Please use
2267                    osc rdelete PROJECT
2268         """
2269         sys.exit(1)
2270
2271     @cmdln.alias('metafromspec')
2272     @cmdln.option('', '--specfile', metavar='FILE',
2273                       help='Path to specfile. (if you pass more than working copy this option is ignored)')
2274     def do_updatepacmetafromspec(self, subcmd, opts, *args):
2275         """${cmd_name}: Update package meta information from a specfile
2276
2277         ARG, if specified, is a package working copy.
2278
2279         ${cmd_usage}
2280         ${cmd_option_list}
2281         """
2282
2283         args = parseargs(args)
2284         if opts.specfile and len(args) == 1:
2285             specfile = opts.specfile
2286         else:
2287             specfile = None
2288         pacs = findpacs(args)
2289         for p in pacs:
2290             p.read_meta_from_spec(specfile)
2291             p.update_package_meta()
2292
2293
2294     @cmdln.alias('di')
2295     @cmdln.option('-c', '--change', metavar='rev',
2296                         help='the change made by revision rev (like -r rev-1:rev).'
2297                              'If rev is negative this is like -r rev:rev-1.')
2298     @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
2299                         help='If rev1 is specified it will compare your working copy against '
2300                              'the revision (rev1) on the server. '
2301                              'If rev1 and rev2 are specified it will compare rev1 against rev2 '
2302                              '(NOTE: changes in your working copy are ignored in this case)')
2303     @cmdln.option('-p', '--plain', action='store_true',
2304                         help='output the diff in plain (not unified) diff format')
2305     @cmdln.option('--missingok', action='store_true',
2306                         help='do not fail if the source or target project/package does not exist on the server')
2307     def do_diff(self, subcmd, opts, *args):
2308         """${cmd_name}: Generates a diff
2309
2310         Generates a diff, comparing local changes against the repository
2311         server.
2312
2313         ARG, specified, is a filename to include in the diff.
2314
2315         ${cmd_usage}
2316         ${cmd_option_list}
2317         """
2318
2319         args = parseargs(args)
2320         pacs = findpacs(args)
2321
2322         if opts.change:
2323             try:
2324                 rev = int(opts.change)
2325                 if rev > 0:
2326                     rev1 = rev - 1
2327                     rev2 = rev
2328                 elif rev < 0:
2329                     rev1 = -rev
2330                     rev2 = -rev - 1
2331                 else:
2332                     return
2333             except:
2334                 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
2335                 return
2336         else:
2337             rev1, rev2 = parseRevisionOption(opts.revision)
2338         diff = ''
2339         for pac in pacs:
2340             if not rev2:
2341                 diff += ''.join(make_diff(pac, rev1))
2342             else:
2343                 diff += server_diff(pac.apiurl, pac.prjname, pac.name, rev1,
2344                                     pac.prjname, pac.name, rev2, not opts.plain, opts.missingok)
2345         if len(diff) > 0:
2346             run_pager(diff)
2347
2348
2349     @cmdln.option('--oldprj', metavar='OLDPRJ',
2350                   help='project to compare against'
2351                   ' (deprecated, use 3 argument form)')
2352     @cmdln.option('--oldpkg', metavar='OLDPKG',
2353                   help='package to compare against'
2354                   ' (deprecated, use 3 argument form)')
2355     @cmdln.option('-r', '--revision', metavar='N[:M]',
2356                   help='revision id, where N = old revision and M = new revision')
2357     @cmdln.option('-p', '--plain', action='store_true',
2358                   help='output the diff in plain (not unified) diff format')
2359     @cmdln.option('-c', '--change', metavar='rev',
2360                         help='the change made by revision rev (like -r rev-1:rev). '
2361                              'If rev is negative this is like -r rev:rev-1.')
2362     @cmdln.option('--missingok', action='store_true',
2363                         help='do not fail if the source or target project/package does not exist on the server')
2364     def do_rdiff(self, subcmd, opts, *args):
2365         """${cmd_name}: Server-side "pretty" diff of two packages
2366
2367         Compares two packages (three or four arguments) or shows the
2368         changes of a specified revision of a package (two arguments)
2369
2370         If no revision is specified the latest revision is used.
2371
2372         Note that this command doesn't return a normal diff (which could be
2373         applied as patch), but a "pretty" diff, which also compares the content
2374         of tarballs.
2375
2376
2377         usage:
2378             osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC]
2379             osc ${cmd_name} PROJECT PACKAGE
2380         ${cmd_option_list}
2381         """
2382
2383         args = slash_split(args)
2384
2385         rev1 = None
2386         rev2 = None
2387
2388         old_project = None
2389         old_package = None
2390         new_project = None
2391         new_package = None
2392
2393         if len(args) == 2:
2394             new_project = args[0]
2395             new_package = args[1]
2396             if opts.oldprj:
2397                 old_project = opts.oldprj
2398             if opts.oldpkg:
2399                 old_package = opts.oldpkg
2400         elif len(args) == 3 or len(args) == 4:
2401             if opts.oldprj or opts.oldpkg:
2402                 raise oscerr.WrongArgs('--oldpkg and --oldprj are only valid with two arguments')
2403             old_project = args[0]
2404             new_package = old_package = args[1]
2405             new_project = args[2]
2406             if len(args) == 4:
2407                 new_package = args[3]
2408         else:
2409             raise oscerr.WrongArgs('Wrong number of arguments')
2410
2411
2412         if opts.change:
2413             try:
2414                 rev = int(opts.change)
2415                 if rev > 0:
2416                     rev1 = rev - 1
2417                     rev2 = rev
2418                 elif rev < 0:
2419                     rev1 = -rev
2420                     rev2 = -rev - 1
2421                 else:
2422                     return
2423             except:
2424                 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
2425                 return
2426         else:
2427             if opts.revision:
2428                 rev1, rev2 = parseRevisionOption(opts.revision)
2429
2430         rdiff = server_diff(conf.config['apiurl'],
2431                             old_project, old_package, rev1,
2432                             new_project, new_package, rev2, not opts.plain, opts.missingok)
2433         print rdiff
2434
2435     @cmdln.hide(1)
2436     @cmdln.alias('in')
2437     def do_install(self, subcmd, opts, *args):
2438         """${cmd_name}: install a package after build via zypper in -r
2439
2440         Not implemented yet. Use osc repourls,
2441         select the url you best like (standard),
2442         chop off after the last /, this should work with zypper.
2443
2444
2445         ${cmd_usage}
2446         ${cmd_option_list}
2447         """
2448
2449         args = slash_split(args)
2450         args = expand_proj_pack(args)
2451
2452         ## FIXME:
2453         ## if there is only one argument, and it ends in .ymp
2454         ## then fetch it, Parse XML to get the first
2455         ##  metapackage.group.repositories.repository.url
2456         ## and construct zypper cmd's for all
2457         ##  metapackage.group.software.item.name
2458         ##
2459         ## if args[0] is already an url, the use it as is.
2460
2461         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])
2462         print self.do_install.__doc__
2463         print "Example: \n" + cmd
2464
2465
2466     def do_repourls(self, subcmd, opts, *args):
2467         """${cmd_name}: Shows URLs of .repo files
2468
2469         Shows URLs on which to access the project .repos files (yum-style
2470         metadata) on download.opensuse.org.
2471
2472         usage:
2473            osc repourls [PROJECT]
2474
2475         ${cmd_option_list}
2476         """
2477
2478         apiurl = self.get_api_url()
2479
2480         if len(args) == 1:
2481             project = args[0]
2482         elif len(args) == 0:
2483             project = store_read_project('.')
2484         else:
2485             raise oscerr.WrongArgs('Wrong number of arguments')
2486
2487         # XXX: API should somehow tell that
2488         url_tmpl = 'http://download.opensuse.org/repositories/%s/%s/%s.repo'
2489         repos = get_repositories_of_project(apiurl, project)
2490         for repo in repos:
2491             print url_tmpl % (project.replace(':', ':/'), repo, project)
2492
2493
2494     @cmdln.option('-r', '--revision', metavar='rev',
2495                         help='checkout the specified revision. '
2496                              'NOTE: if you checkout the complete project '
2497                              'this option is ignored!')
2498     @cmdln.option('-e', '--expand-link', action='store_true',
2499                         help='if a package is a link, check out the expanded '
2500                              'sources (no-op, since this became the default)')
2501     @cmdln.option('-u', '--unexpand-link', action='store_true',
2502                         help='if a package is a link, check out the _link file ' \
2503                              'instead of the expanded sources')
2504     @cmdln.option('-M', '--meta', action='store_true',
2505                         help='checkout out meta data instead of sources' )
2506     @cmdln.option('-c', '--current-dir', action='store_true',
2507                         help='place PACKAGE folder in the current directory' \
2508                              'instead of a PROJECT/PACKAGE directory')
2509     @cmdln.option('-s', '--source-service-files', action='store_true',
2510                         help='Use server side generated sources instead of local generation.' )
2511     @cmdln.option('-S', '--server-side-source-service-files', action='store_true',
2512                         help='Use server side generated sources instead of local generation.' )
2513     @cmdln.option('-l', '--limit-size', metavar='limit_size',
2514                         help='Skip all files with a given size')
2515     @cmdln.alias('co')
2516     def do_checkout(self, subcmd, opts, *args):
2517         """${cmd_name}: Check out content from the repository
2518
2519         Check out content from the repository server, creating a local working
2520         copy.
2521
2522         When checking out a single package, the option --revision can be used
2523         to specify a revision of the package to be checked out.
2524
2525         When a package is a source link, then it will be checked out in
2526         expanded form. If --unexpand-link option is used, the checkout will
2527         instead produce the raw _link file plus patches.
2528
2529         usage:
2530             osc co PROJECT [PACKAGE] [FILE]
2531                osc co PROJECT                    # entire project
2532                osc co PROJECT PACKAGE            # a package
2533                osc co PROJECT PACKAGE FILE       # single file -> to current dir
2534
2535             while inside a project directory:
2536                osc co PACKAGE                    # check out PACKAGE from project
2537
2538         ${cmd_option_list}
2539         """
2540
2541         if opts.unexpand_link:
2542             expand_link = False
2543         else:
2544             expand_link = True
2545
2546         args = slash_split(args)
2547         project = package = filename = None
2548
2549         apiurl = self.get_api_url()
2550
2551         try:
2552             project = project_dir = args[0]
2553             package = args[1]
2554             filename = args[2]
2555         except:
2556             pass
2557
2558         if args and len(args) == 1:
2559             localdir = os.getcwd()
2560             if is_project_dir(localdir):
2561                 project = store_read_project(localdir)
2562                 project_dir = localdir
2563                 package = args[0]
2564
2565         rev, dummy = parseRevisionOption(opts.revision)
2566         if rev==None:
2567             rev="latest"
2568
2569         if rev and rev != "latest" and not checkRevision(project, package, rev):
2570             print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2571             sys.exit(1)
2572
2573         if filename:
2574             get_source_file(apiurl, project, package, filename, revision=rev, progress_obj=self.download_progress)
2575
2576         elif package:
2577             if opts.current_dir:
2578                 project_dir = None
2579             checkout_package(apiurl, project, package, rev, expand_link=expand_link, \
2580                              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)
2581             print_request_list(apiurl, project, package)
2582
2583         elif project:
2584             prj_dir = project
2585             if sys.platform[:3] == 'win':
2586                 prj_dir = prj_dir.replace(':', ';')
2587             if os.path.exists(prj_dir):
2588                 sys.exit('osc: project \'%s\' already exists' % project)
2589
2590             # check if the project does exist (show_project_meta will throw an exception)
2591             show_project_meta(apiurl, project)
2592
2593             init_project_dir(apiurl, prj_dir, project)
2594             print statfrmt('A', prj_dir)
2595
2596             # all packages
2597             for package in meta_get_packagelist(apiurl, project):
2598                 try:
2599                     checkout_package(apiurl, project, package, expand_link = expand_link, \
2600                                      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)
2601                 except oscerr.LinkExpandError, e:
2602                     print >>sys.stderr, 'Link cannot be expanded:\n', e
2603                     print >>sys.stderr, 'Use "osc repairlink" for fixing merge conflicts:\n'
2604                     # check out in unexpanded form at least
2605                     checkout_package(apiurl, project, package, expand_link = False, \
2606                                      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)
2607             print_request_list(apiurl, project)
2608
2609         else:
2610             raise oscerr.WrongArgs('Missing argument.\n\n' \
2611                   + self.get_cmd_help('checkout'))
2612
2613
2614     @cmdln.option('-q', '--quiet', action='store_true',
2615                         help='print as little as possible')
2616     @cmdln.option('-v', '--verbose', action='store_true',
2617                         help='print extra information')
2618     @cmdln.alias('st')
2619     def do_status(self, subcmd, opts, *args):
2620         """${cmd_name}: Show status of files in working copy
2621
2622         Show the status of files in a local working copy, indicating whether
2623         files have been changed locally, deleted, added, ...
2624
2625         The first column in the output specifies the status and is one of the
2626         following characters:
2627           ' ' no modifications
2628           'A' Added
2629           'C' Conflicted
2630           'D' Deleted
2631           'M' Modified
2632           '?' item is not under version control
2633           '!' item is missing (removed by non-osc command) or incomplete
2634
2635         examples:
2636           osc st
2637           osc st <directory>
2638           osc st file1 file2 ...
2639
2640         usage:
2641             osc status [OPTS] [PATH...]
2642         ${cmd_option_list}
2643         """
2644
2645         args = parseargs(args)
2646
2647         # storage for single Package() objects
2648         pacpaths = []
2649         # storage for a project dir ( { prj_instance : [ package objects ] } )
2650         prjpacs = {}
2651         for arg in args:
2652             # when 'status' is run inside a project dir, it should
2653             # stat all packages existing in the wc
2654             if is_project_dir(arg):
2655                 prj = Project(arg, False)
2656
2657                 if conf.config['do_package_tracking']:
2658                     prjpacs[prj] = []
2659                     for pac in prj.pacs_have:
2660                         # we cannot create package objects if the dir does not exist
2661                         if not pac in prj.pacs_broken:
2662                             prjpacs[prj].append(os.path.join(arg, pac))
2663                 else:
2664                     pacpaths += [arg + '/' + n for n in prj.pacs_have]
2665             elif is_package_dir(arg):
2666                 pacpaths.append(arg)
2667             elif os.path.isfile(arg):
2668                 pacpaths.append(arg)
2669             else:
2670                 msg = '\'%s\' is neither a project or a package directory' % arg
2671                 raise oscerr.NoWorkingCopy, msg
2672         lines = []
2673         # process single packages
2674         lines = getStatus(findpacs(pacpaths), None, opts.verbose, opts.quiet)
2675         # process project dirs
2676         for prj, pacs in prjpacs.iteritems():
2677             lines += getStatus(findpacs(pacs), prj, opts.verbose, opts.quiet)
2678         if lines:
2679             print '\n'.join(lines)
2680
2681
2682     def do_add(self, subcmd, opts, *args):
2683         """${cmd_name}: Mark files to be added upon the next commit
2684
2685         In case a URL is given the file will get downloaded and registered to be downloaded
2686         by the server as well via the download_url source service.
2687
2688         This is recommended for release tar balls to track their source and to help
2689         others to review your changes esp. on version upgrades.
2690
2691         usage:
2692             osc add URL [URL...]
2693             osc add FILE [FILE...]
2694         ${cmd_option_list}
2695         """
2696         if not args:
2697             raise oscerr.WrongArgs('Missing argument.\n\n' \
2698                   + self.get_cmd_help('add'))
2699
2700         # Do some magic here, when adding a url. We want that the server to download the tar ball and to verify it
2701         for arg in parseargs(args):
2702             if arg.startswith('http://') or arg.startswith('https://') or arg.startswith('ftp://'):
2703                 addDownloadUrlService(arg)
2704             else:
2705                 addFiles([arg])
2706
2707
2708     def do_mkpac(self, subcmd, opts, *args):
2709         """${cmd_name}: Create a new package under version control
2710
2711         usage:
2712             osc mkpac new_package
2713         ${cmd_option_list}
2714         """
2715         if not conf.config['do_package_tracking']:
2716             print >>sys.stderr, "to use this feature you have to enable \'do_package_tracking\' " \
2717                                 "in the [general] section in the configuration file"
2718             sys.exit(1)
2719
2720         if len(args) != 1: