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