fix output of 'osc buildinfo' if parameter are missing
[opensuse:osc.git] / osc / commandline.py
1 # Copyright (C) 2006 Novell Inc.  All rights reserved.
2 # This program is free software; it may be used, copied, modified
3 # and distributed under the terms of the GNU General Public Licence,
4 # either version 2, or version 3 (at your option).
5
6
7 from core import *
8 import cmdln
9 import conf
10 import oscerr
11 from optparse import SUPPRESS_HELP
12
13 MAN_HEADER = r""".TH %(ucname)s "1" "%(date)s" "%(name)s %(version)s" "User Commands"
14 .SH NAME
15 %(name)s \- openSUSE build service command-line tool.
16 .SH SYNOPSIS
17 .B %(name)s
18 [\fIGLOBALOPTS\fR] \fISUBCOMMAND \fR[\fIOPTS\fR] [\fIARGS\fR...]
19 .br
20 .B %(name)s
21 \fIhelp SUBCOMMAND\fR
22 .SH DESCRIPTION
23 openSUSE build service command-line tool.
24 """
25 MAN_FOOTER = r"""
26 .SH "SEE ALSO"
27 Type 'osc help <subcommand>' for more detailed help on a specific subcommand.
28 .PP
29 For additional information, see
30  * http://en.opensuse.org/openSUSE:Build_Service_Tutorial
31  * http://en.opensuse.org/openSUSE:OSC
32 .PP
33 You can modify osc commands, or roll you own, via the plugin API:
34  * http://en.opensuse.org/openSUSE:OSC_plugins
35 .SH AUTHOR
36 osc was written by several authors. This man page is automatically generated.
37 """
38
39 class Osc(cmdln.Cmdln):
40     """Usage: osc [GLOBALOPTS] SUBCOMMAND [OPTS] [ARGS...]
41     or: osc help SUBCOMMAND
42
43     openSUSE build service command-line tool.
44     Type 'osc help <subcommand>' for help on a specific subcommand.
45
46     ${command_list}
47     ${help_list}
48     global ${option_list}
49     For additional information, see
50     * http://en.opensuse.org/openSUSE:Build_Service_Tutorial
51     * http://en.opensuse.org/openSUSE:OSC
52
53     You can modify osc commands, or roll you own, via the plugin API:
54     * http://en.opensuse.org/openSUSE:OSC_plugins
55     """
56     name = 'osc'
57     conf = None
58
59     man_header = MAN_HEADER
60     man_footer = MAN_FOOTER
61
62     def __init__(self, *args, **kwargs):
63         cmdln.Cmdln.__init__(self, *args, **kwargs)
64         cmdln.Cmdln.do_help.aliases.append('h')
65
66     def get_version(self):
67         return get_osc_version()
68
69     def get_optparser(self):
70         """this is the parser for "global" options (not specific to subcommand)"""
71
72         optparser = cmdln.CmdlnOptionParser(self, version=get_osc_version())
73         optparser.add_option('--debugger', action='store_true',
74                       help='jump into the debugger before executing anything')
75         optparser.add_option('--post-mortem', action='store_true',
76                       help='jump into the debugger in case of errors')
77         optparser.add_option('-t', '--traceback', action='store_true',
78                       help='print call trace in case of errors')
79         optparser.add_option('-H', '--http-debug', action='store_true',
80                       help='debug HTTP traffic')
81         optparser.add_option('-d', '--debug', action='store_true',
82                       help='print info useful for debugging')
83         optparser.add_option('-A', '--apiurl', dest='apiurl',
84                       metavar='URL/alias',
85                       help='specify URL to access API server at or an alias')
86         optparser.add_option('-c', '--config', dest='conffile',
87                       metavar='FILE',
88                       help='specify alternate configuration file')
89         optparser.add_option('--no-keyring', action='store_true',
90                       help='disable usage of desktop keyring system')
91         optparser.add_option('--no-gnome-keyring', action='store_true',
92                       help='disable usage of GNOME Keyring')
93         optparser.add_option('-v', '--verbose', dest='verbose', action='count', default=0,
94                       help='increase verbosity')
95         optparser.add_option('-q', '--quiet',   dest='verbose', action='store_const', const=-1,
96                       help='be quiet, not verbose')
97         return optparser
98
99
100     def postoptparse(self, try_again = True):
101         """merge commandline options into the config"""
102         try:
103             conf.get_config(override_conffile = self.options.conffile,
104                             override_apiurl = self.options.apiurl,
105                             override_debug = self.options.debug,
106                             override_http_debug = self.options.http_debug,
107                             override_traceback = self.options.traceback,
108                             override_post_mortem = self.options.post_mortem,
109                             override_no_keyring = self.options.no_keyring,
110                             override_no_gnome_keyring = self.options.no_gnome_keyring,
111                             override_verbose = self.options.verbose)
112         except oscerr.NoConfigfile, e:
113             print >>sys.stderr, e.msg
114             print >>sys.stderr, 'Creating osc configuration file %s ...' % e.file
115             import getpass
116             config = {}
117             config['user'] = raw_input('Username: ')
118             config['pass'] = getpass.getpass()
119             if self.options.no_keyring:
120                 config['use_keyring'] = '0'
121             if self.options.no_gnome_keyring:
122                 config['gnome_keyring'] = '0'
123             if self.options.apiurl:
124                 config['apiurl'] = self.options.apiurl
125
126             conf.write_initial_config(e.file, config)
127             print >>sys.stderr, 'done'
128             if try_again: self.postoptparse(try_again = False)
129         except oscerr.ConfigMissingApiurl, e:
130             print >>sys.stderr, e.msg
131             import getpass
132             user = raw_input('Username: ')
133             passwd = getpass.getpass()
134             conf.add_section(e.file, e.url, user, passwd)
135             if try_again: self.postoptparse(try_again = False)
136
137         self.options.verbose = conf.config['verbose']
138         self.download_progress = None
139         if conf.config.get('show_download_progress', False):
140             from meter import TextMeter
141             self.download_progress = TextMeter(hide_finished=True)
142
143
144     def get_cmd_help(self, cmdname):
145         doc = self._get_cmd_handler(cmdname).__doc__
146         doc = self._help_reindent(doc)
147         doc = self._help_preprocess(doc, cmdname)
148         doc = doc.rstrip() + '\n' # trim down trailing space
149         return self._str(doc)
150
151     def get_api_url(self):
152         localdir = os.getcwd()
153         if (is_package_dir(localdir) or is_project_dir(localdir)) and not self.options.apiurl:
154            return store_read_apiurl(os.curdir)
155         else:
156            return conf.config['apiurl']
157
158     # overridden from class Cmdln() to use config variables in help texts
159     def _help_preprocess(self, help, cmdname):
160         help = cmdln.Cmdln._help_preprocess(self, help, cmdname)
161         return help % conf.config
162
163
164     def do_init(self, subcmd, opts, project, package=None):
165         """${cmd_name}: Initialize a directory as working copy
166
167         Initialize an existing directory to be a working copy of an
168         (already existing) buildservice project/package.
169
170         (This is the same as checking out a package and then copying sources
171         into the directory. It does NOT create a new package. To create a
172         package, use 'osc meta pkg ... ...')
173
174         You wouldn't normally use this command.
175
176         To get a working copy of a package (e.g. for building it or working on
177         it, you would normally use the checkout command. Use "osc help
178         checkout" to get help for it.
179
180         usage:
181             osc init PRJ
182             osc init PRJ PAC
183         ${cmd_option_list}
184         """
185
186         apiurl = self.get_api_url()
187
188         if not package:
189             init_project_dir(apiurl, os.curdir, project)
190             print 'Initializing %s (Project: %s)' % (os.curdir, project)
191         else:
192             init_package_dir(apiurl, project, package, os.path.curdir)
193             print 'Initializing %s (Project: %s, Package: %s)' % (os.curdir, project, package)
194
195     @cmdln.alias('ls')
196     @cmdln.alias('ll')
197     @cmdln.alias('lL')
198     @cmdln.alias('LL')
199     @cmdln.option('-a', '--arch', metavar='ARCH',
200                         help='specify architecture (only for binaries)')
201     @cmdln.option('-r', '--repo', metavar='REPO',
202                         help='specify repository (only for binaries)')
203     @cmdln.option('-b', '--binaries', action='store_true',
204                         help='list built binaries instead of sources')
205     @cmdln.option('-R', '--revision', metavar='REVISION',
206                         help='specify revision (only for sources)')
207     @cmdln.option('-e', '--expand', action='store_true',
208                         help='expand linked package (only for sources)')
209     @cmdln.option('-u', '--unexpand', action='store_true',
210                         help='always work with unexpanded (source) packages')
211     @cmdln.option('-v', '--verbose', action='store_true',
212                         help='print extra information')
213     @cmdln.option('-l', '--long', action='store_true', dest='verbose',
214                         help='print extra information')
215     @cmdln.option('-D', '--deleted', action='store_true',
216                         help='show only the former deleted projects or packages')
217     def do_list(self, subcmd, opts, *args):
218         """${cmd_name}: List sources or binaries on the server
219
220         Examples for listing sources:
221            ls                         # list all projects
222            ls PROJECT                  # list packages in a project
223            ls PROJECT PACKAGE          # list source files of package of a project
224            ls PROJECT PACKAGE <file>   # list <file> if this file exists
225            ls -v PROJECT PACKAGE       # verbosely list source files of package
226            ls -l PROJECT PACKAGE       # verbosely list source files of package
227            ll PROJECT PACKAGE          # verbosely list source files of package
228            LL PROJECT PACKAGE          # verbosely list source files of expanded link
229
230         With --verbose, the following fields will be shown for each item:
231            MD5 hash of file
232            Revision number of the last commit
233            Size (in bytes)
234            Date and time of the last commit
235
236         Examples for listing binaries:
237            ls -b PROJECT               # list all binaries of a project
238            ls -b PROJECT -a ARCH       # list ARCH binaries of a project
239            ls -b PROJECT -r REPO       # list binaries in REPO
240            ls -b PROJECT PACKAGE REPO ARCH
241
242         Usage:
243            ${cmd_name} [PROJECT [PACKAGE]]
244            ${cmd_name} -b [PROJECT [PACKAGE [REPO [ARCH]]]]
245         ${cmd_option_list}
246         """
247
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         apiurl = self.get_api_url() 
290
291         # list binaries
292         if opts.binaries:
293             # ls -b toplevel doesn't make sense, so use info from
294             # current dir if available
295             if len(args) == 0:
296                 dir = os.getcwd()
297                 if is_project_dir(dir):
298                     project = store_read_project(dir)
299                 elif is_package_dir(dir):
300                     project = store_read_project(dir)
301                     package = store_read_package(dir)
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(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(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(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(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/openSUSE: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                 change_request_state(apiurl, opts.supersede, 'superseded', 
959                                      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             # submit requests for multiple packages are currently handled via multiple requests
989             # They could be also one request with multiple actions, but that avoids to accepts parts of it.
990             project = store_read_project(os.curdir)
991
992             pi = []
993             pac = []
994             targetprojects = []
995             rdiffmsg = []
996             # loop via all packages for checking their state
997             for p in meta_get_packagelist(apiurl, project):
998                 if p.startswith("_patchinfo:"):
999                     pi.append(p)
1000                 else:
1001                     # get _link info from server, that knows about the local state ...
1002                     u = makeurl(apiurl, ['source', project, p])
1003                     f = http_GET(u)
1004                     root = ET.parse(f).getroot()
1005                     linkinfo = root.find('linkinfo')
1006                     if linkinfo == None:
1007                         print "Package ", p, " is not a source link."
1008                         sys.exit("This is currently not supported.")
1009                     if linkinfo.get('error'):
1010                         print "Package ", p, " is a broken source link."
1011                         sys.exit("Please fix this first")
1012                     t = linkinfo.get('project')
1013                     if t:
1014                         rdiff = ''
1015                         try:
1016                             rdiff = server_diff(apiurl, t, p, opts.revision, project, p, None, True)
1017                         except:
1018                             rdiff = ''
1019
1020                         if rdiff != '':
1021                             targetprojects.append(t)
1022                             pac.append(p)
1023                             rdiffmsg.append("old: %s/%s\nnew: %s/%s\n%s" %(t, p, project, p,rdiff))
1024                         else:
1025                             print "Skipping package ", p,  " since it has no difference with the target package."
1026                     else:
1027                         print "Skipping package ", p,  " since it is a source link pointing inside the project."
1028             if opts.diff:
1029                 print ''.join(rdiffmsg)
1030                 sys.exit(0)
1031
1032                 if not opts.yes:
1033                     if pi:
1034                         print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
1035                     print "\nEverything fine? Can we create the requests ? [y/n]"
1036                     if sys.stdin.read(1) != "y":
1037                         sys.exit("Aborted...")
1038
1039             # loop via all packages to do the action
1040             for p in pac:
1041                 s = """<action type="submit"> <source project="%s" package="%s"  rev="%s"/> <target project="%s" package="%s"/> %s </action>"""  % \
1042                        (project, p, opts.revision or show_upstream_rev(apiurl, project, p), t, p, options_block)
1043                 actionxml += s
1044
1045             # create submit requests for all found patchinfos
1046             for p in pi:
1047                 for t in targetprojects:
1048                     s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>"""  % \
1049                            (project, p, t, p, options_block)
1050                     actionxml += s
1051
1052             return actionxml
1053
1054         elif len(args) <= 2:
1055             # try using the working copy at hand
1056             p = findpacs(os.curdir)[0]
1057             src_project = p.prjname
1058             src_package = p.name
1059             if len(args) == 0 and p.islink():
1060                 dst_project = p.linkinfo.project
1061                 dst_package = p.linkinfo.package
1062             elif len(args) > 0:
1063                 dst_project = args[0]
1064                 if len(args) == 2:
1065                     dst_package = args[1]
1066                 else:
1067                     dst_package = src_package
1068             else:
1069                 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
1070                          'Please provide it the target via commandline arguments.' % p.name)
1071
1072             modified = [i for i in p.filenamelist if p.status(i) != ' ' and p.status(i) != '?']
1073             if len(modified) > 0:
1074                 print 'Your working copy has local modifications.'
1075                 repl = raw_input('Proceed without committing the local changes? (y|N) ')
1076                 if repl != 'y':
1077                     sys.exit(1)
1078         elif len(args) >= 3:
1079             # get the arguments from the commandline
1080             src_project, src_package, dst_project = args[0:3]
1081             if len(args) == 4:
1082                 dst_package = args[3]
1083             else:
1084                 dst_package = src_package
1085         else:
1086             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1087                   + self.get_cmd_help('request'))
1088
1089         if not opts.nodevelproject:
1090             devloc = None
1091             try:
1092                 devloc = show_develproject(apiurl, dst_project, dst_package)
1093             except urllib2.HTTPError:
1094                 print >>sys.stderr, """\
1095 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
1096                     % (dst_project, dst_package)
1097                 pass
1098
1099             if devloc and \
1100                dst_project != devloc and \
1101                src_project != devloc:
1102                 print """\
1103 A different project, %s, is defined as the place where development
1104 of the package %s primarily takes place.
1105 Please submit there instead, or use --nodevelproject to force direct submission.""" \
1106                 % (devloc, dst_package)
1107                 if not opts.diff:
1108                     sys.exit(1)
1109
1110         rdiff = None
1111         if opts.diff:
1112             try:
1113                 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
1114                 rdiff += server_diff(apiurl,
1115                                     dst_project, dst_package, opts.revision,
1116                                     src_project, src_package, None, True)
1117             except:
1118                 rdiff = ''
1119         if opts.diff:
1120             print rdiff
1121         else:
1122             reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit')
1123             user = conf.get_apiurl_usr(apiurl)
1124             myreqs = [ i for i in reqs if i.state.who == user ]
1125             repl = ''
1126             if len(myreqs) > 0:
1127                 print 'You already created the following submit request: %s.' % \
1128                       ', '.join([str(i.reqid) for i in myreqs ])
1129                 repl = raw_input('Supersede the old requests? (y/n/c) ')
1130                 if repl.lower() == 'c':
1131                     print >>sys.stderr, 'Aborting'
1132                     sys.exit(1)
1133
1134             actionxml = """<action type="submit"> <source project="%s" package="%s"  rev="%s"/> <target project="%s" package="%s"/> %s </action>"""  % \
1135                     (src_project, src_package, opts.revision or show_upstream_rev(apiurl, src_project, src_package), dst_project, dst_package, options_block)
1136             if repl.lower() == 'y':
1137                 for req in myreqs:
1138                     change_request_state(apiurl, str(req.reqid), 'superseded',
1139                                          'superseded by %s' % result, result)
1140
1141             if opts.supersede:
1142                 change_request_state(apiurl, opts.supersede, 'superseded', '', result)
1143
1144             #print 'created request id', result
1145             return actionxml
1146
1147     def _delete_request(self, args, opts):
1148         if len(args) < 1:
1149             raise oscerr.WrongArgs('Please specify at least a project.')
1150         if len(args) > 2:
1151             raise oscerr.WrongArgs('Too many arguments.')
1152
1153         package = ""
1154         if len(args) > 1:
1155             package = """package="%s" """ % (args[1])
1156         actionxml = """<action type="delete"> <target project="%s" %s/> </action> """ % (args[0], package)
1157         return actionxml
1158
1159     def _changedevel_request(self, args, opts):
1160         if len(args) > 4:
1161             raise oscerr.WrongArgs('Too many arguments.')
1162
1163         if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
1164             wd = os.curdir
1165             devel_project = store_read_project(wd)
1166             devel_package = package = store_read_package(wd)
1167             project = conf.config['getpac_default_project']
1168         else:
1169             if len(args) < 3:
1170                 raise oscerr.WrongArgs('Too few arguments.')
1171
1172             devel_project = args[2]
1173             project = args[0]
1174             package = args[1]
1175             devel_package = package
1176             if len(args) > 3:
1177                 devel_package = args[3]
1178
1179         actionxml = """ <action type="change_devel"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> </action> """ % \
1180                 (devel_project, devel_package, project, package)
1181
1182         return actionxml
1183
1184     def _add_role(self, args, opts):
1185         if len(args) > 4:
1186             raise oscerr.WrongArgs('Too many arguments.')
1187         if len(args) < 3:
1188             raise oscerr.WrongArgs('Too few arguments.')
1189
1190         apiurl = self.get_api_url()
1191
1192         user = args[0]
1193         role = args[1]
1194         project = args[2]
1195         if len(args) > 3:
1196             package = args[3]
1197
1198         if get_user_meta(apiurl, user) == None:
1199             raise oscerr.WrongArgs('osc: an error occured.')
1200
1201         actionxml = """ <action type="add_role"> <target project="%s" package="%s" /> <person name="%s" role="%s" /> </action> """ % \
1202                 (project, package, user, role)
1203
1204         return actionxml
1205
1206     def _set_bugowner(self, args, opts):
1207         if len(args) > 3:
1208             raise oscerr.WrongArgs('Too many arguments.')
1209         if len(args) < 2:
1210             raise oscerr.WrongArgs('Too few arguments.')
1211
1212         apiurl = self.get_api_url()
1213
1214         user = args[0]
1215         project = args[1]
1216         if len(args) > 2:
1217             package = args[2]
1218
1219         if get_user_meta(apiurl, user) == None:
1220             raise oscerr.WrongArgs('osc: an error occured.')
1221
1222         actionxml = """ <action type="set_bugowner"> <target project="%s" package="%s" /> <person name="%s" /> </action> """ % \
1223                 (project, package, user)
1224
1225         return actionxml
1226
1227     @cmdln.option('-a', '--action', action='callback', callback = _actionparser,dest = 'actions',
1228                   help='specify action type of a request, can be : submit/delete/change_devel/add_role/set_bugowner')
1229     @cmdln.option('-m', '--message', metavar='TEXT',
1230                   help='specify message TEXT')
1231     @cmdln.option('-r', '--revision', metavar='REV',
1232                   help='for "create", specify a certain source revision ID (the md5 sum)')
1233     @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
1234                   help='Superseding another request by this one')
1235     @cmdln.option('--nodevelproject', action='store_true',
1236                   help='do not follow a defined devel project ' \
1237                        '(primary project where a package is developed)')
1238     @cmdln.option('--cleanup', action='store_true',
1239                   help='remove package if submission gets accepted (default for home:<id>:branch projects)')
1240     @cmdln.option('--no-cleanup', action='store_true',
1241                   help='never remove source package on accept, but update its content')
1242     @cmdln.option('--no-update', action='store_true',
1243                   help='never touch source package on accept (will break source links)')
1244     @cmdln.option('-d', '--diff', action='store_true',
1245                   help='show diff only instead of creating the actual request')
1246     @cmdln.option('--yes', action='store_true',
1247                   help='proceed without asking.')
1248     @cmdln.alias("creq")
1249     def do_createrequest(self, subcmd, opts, *args):
1250         """${cmd_name}: create multiple requests with a single command
1251
1252         usage:
1253             osc creq [OPTIONS] [ 
1254                 -a submit SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG] 
1255                 -a delete PROJECT [PACKAGE] 
1256                 -a change_devel PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE] 
1257                 -a add_role USER ROLE PROJECT [PACKAGE]
1258                 -a set_bugowner USER PROJECT [PACKAGE]
1259                 ]
1260
1261             Option -m works for all types of request, the rest work only for submit.
1262         example:
1263             osc creq -a submit -a delete home:someone:branches:openSUSE:Tools -a change_devel openSUSE:Tools osc home:someone:branches:openSUSE:Tools -m ok
1264
1265             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.
1266         ${cmd_option_list}
1267         """
1268         src_update = conf.config['submitrequest_on_accept_action'] or None
1269         # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
1270         if opts.cleanup:
1271             src_update = "cleanup"
1272         elif opts.no_cleanup:
1273             src_update = "update"
1274         elif opts.no_update:
1275             src_update = "noupdate"
1276
1277         options_block=""
1278         if src_update:
1279             options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
1280
1281         args = slash_split(args)
1282
1283         apiurl = self.get_api_url()
1284         
1285         i = 0
1286         actionsxml = ""
1287         for ai in opts.actions:
1288             if ai == 'submit':
1289                 args = opts.actiondata[i]
1290                 i = i+1
1291                 actionsxml += self._submit_request(args,opts, options_block)
1292             elif ai == 'delete':
1293                 args = opts.actiondata[i]
1294                 actionsxml += self._delete_request(args,opts)
1295                 i = i+1
1296             elif ai == 'change_devel':
1297                 args = opts.actiondata[i]
1298                 actionsxml += self._changedevel_request(args,opts)
1299                 i = i+1
1300             elif ai == 'add_role':
1301                 args = opts.actiondata[i]
1302                 actionsxml += self._add_role(args,opts)
1303                 i = i+1
1304             elif ai == 'set_bugowner':
1305                 args = opts.actiondata[i]
1306                 actionsxml += self._set_bugowner(args,opts)
1307                 i = i+1
1308             else:
1309                 raise oscerr.WrongArgs('Unsupported action %s' % ai)
1310         if actionsxml == "":
1311             sys.exit('No actions need to be taken.')
1312
1313         if not opts.message:
1314             opts.message = edit_message()
1315
1316         import cgi
1317         xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
1318               (actionsxml, cgi.escape(opts.message or ""))
1319         u = makeurl(apiurl, ['request'], query='cmd=create')
1320         f = http_POST(u, data=xml)
1321
1322         root = ET.parse(f).getroot()
1323         return root.get('id')
1324
1325
1326     @cmdln.option('-m', '--message', metavar='TEXT',
1327                   help='specify message TEXT')
1328     @cmdln.alias("dr")
1329     @cmdln.alias("deletereq")
1330     def do_deleterequest(self, subcmd, opts, *args):
1331         """${cmd_name}: Create request to delete a package or project
1332
1333         usage:
1334             osc deletereq [-m TEXT]                        # works in checked out project/package
1335             osc deletereq [-m TEXT] PROJECT [PACKAGE]
1336         ${cmd_option_list}
1337         """
1338
1339         args = slash_split(args)
1340
1341         project = None
1342         package = None
1343
1344         if len(args) < 1:
1345             if is_project_dir(os.getcwd()):
1346                 project = store_read_project(os.curdir)
1347             elif is_package_dir(os.getcwd()):
1348                 project = store_read_project(os.curdir)
1349                 package = store_read_package(os.curdir)
1350             else: 
1351                 raise oscerr.WrongArgs('Please specify at least a project.')
1352         elif len(args) == 1:
1353             project = args[0]
1354         elif len(args) > 1:
1355             project = args[0]
1356             package = args[1]
1357         elif len(args) > 2:
1358             raise oscerr.WrongArgs('Too many arguments.')
1359
1360         apiurl = self.get_api_url()
1361
1362         if not opts.message:
1363             import textwrap
1364             if package is not None:
1365                 footer=textwrap.TextWrapper(width = 66).fill(
1366                         'please explain why you like to delete package %s of project %s'
1367                         % (package,project))
1368             else:
1369                 footer=textwrap.TextWrapper(width = 66).fill(
1370                         'please explain why you like to delete project %s' % project)
1371             opts.message = edit_message(footer)
1372
1373         result = create_delete_request(apiurl, project, package, opts.message)
1374         print result
1375
1376
1377     @cmdln.option('-m', '--message', metavar='TEXT',
1378                   help='specify message TEXT')
1379     @cmdln.alias("cr")
1380     @cmdln.alias("changedevelreq")
1381     def do_changedevelrequest(self, subcmd, opts, *args):
1382         """${cmd_name}: Create request to change the devel package definition.
1383
1384         [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration 
1385         for information on this topic.]
1386
1387         See the "request" command for showing and modifing existing requests.
1388
1389         osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
1390         """
1391
1392         if len(args) > 4:
1393             raise oscerr.WrongArgs('Too many arguments.')
1394
1395         apiurl = self.get_api_url()
1396
1397         if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
1398             wd = os.curdir
1399             devel_project = store_read_project(wd)
1400             devel_package = package = store_read_package(wd)
1401             project = conf.config['getpac_default_project']
1402         else:
1403             if len(args) < 3:
1404                 raise oscerr.WrongArgs('Too few arguments.')
1405
1406             devel_project = args[2]
1407             project = args[0]
1408             package = args[1]
1409             devel_package = package
1410             if len(args) > 3:
1411                 devel_package = args[3]
1412
1413         if not opts.message:
1414             import textwrap
1415             footer=textwrap.TextWrapper(width = 66).fill(
1416                     'please explain why you like to change the devel project of %s/%s to %s/%s'
1417                     % (project,package,devel_project,devel_package))
1418             opts.message = edit_message(footer)
1419
1420         result = create_change_devel_request(apiurl,
1421                                        devel_project, devel_package,
1422                                        project, package,
1423                                        opts.message)
1424         print result
1425
1426
1427     @cmdln.option('-d', '--diff', action='store_true',
1428                   help='generate a diff')
1429     @cmdln.option('-u', '--unified', action='store_true',
1430                   help='output the diff in the unified diff format')
1431     @cmdln.option('-m', '--message', metavar='TEXT',
1432                   help='specify message TEXT')
1433     @cmdln.option('-t', '--type', metavar='TYPE',
1434                   help='limit to requests which contain a given action type (submit/delete/change_devel)')
1435     @cmdln.option('-a', '--all', action='store_true',
1436                         help='all states. Same as\'-s all\'')
1437     @cmdln.option('-s', '--state', default='',  # default is 'all' if no args given, 'new' otherwise
1438                         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]')
1439     @cmdln.option('-D', '--days', metavar='DAYS',
1440                         help='only list requests in state "new" or changed in the last DAYS. [default=%(request_list_days)s]')
1441     @cmdln.option('-U', '--user', metavar='USER',
1442                         help='requests or reviews limited for the specified USER')
1443     @cmdln.option('-G', '--group', metavar='GROUP',
1444                         help='requests or reviews limited for the specified GROUP')
1445     @cmdln.option('-b', '--brief', action='store_true', default=False,
1446                         help='print output in list view as list subcommand')
1447     @cmdln.option('-M', '--mine', action='store_true',
1448                         help='only show requests created by yourself')
1449     @cmdln.option('-B', '--bugowner', action='store_true',
1450                         help='also show requests about packages where I am bugowner')
1451     @cmdln.option('-i', '--interactive', action='store_true',
1452                         help='interactive review of request')
1453     @cmdln.option('--non-interactive', action='store_true',
1454                         help='non-interactive review of request')
1455     @cmdln.option('--exclude-target-project', action='append',
1456                         help='exclude target project from request list')
1457     @cmdln.option('--involved-projects', action='store_true',
1458                         help='show all requests for project/packages where USER is involved')
1459     @cmdln.alias("rq")
1460     @cmdln.alias("review")
1461     def do_request(self, subcmd, opts, *args):
1462         """${cmd_name}: Show and modify requests
1463
1464         [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration
1465         for information on this topic.]
1466
1467         This command shows and modifies existing requests. To create new requests
1468         you need to call one of the following:
1469           osc submitrequest
1470           osc deleterequest
1471           osc changedevelrequest
1472         To send low level requests to the buildservice API, use:
1473           osc api
1474
1475         This command has the following sub commands:
1476
1477         "list" lists open requests attached to a project or package or person.
1478         Uses the project/package of the current directory if none of
1479         -M, -U USER, project/package are given.
1480
1481         "log" will show the history of the given ID
1482
1483         "show" will show the request itself, and generate a diff for review, if
1484         used with the --diff option. The keyword show can be omitted if the ID is numeric.
1485
1486         "decline" will change the request state to "declined" and append a
1487         message that you specify with the --message option.
1488
1489         "wipe" will permanently delete a request.
1490
1491         "revoke" will set the request state to "revoked" and append a
1492         message that you specify with the --message option.
1493
1494         "accept" will change the request state to "accepted" and will trigger
1495         the actual submit process. That would normally be a server-side copy of
1496         the source package to the target package.
1497
1498         "checkout" will checkout the request's source package. This only works for "submit" requests.
1499
1500         usage:
1501             osc request list [-M] [-U USER] [-s state] [-D DAYS] [-t type] [-B] [PRJ [PKG]]
1502             osc request log ID
1503             osc request [show] [-d] [-b] ID
1504             osc request accept [-m TEXT] ID
1505             osc request reopen [-m TEXT] ID
1506             osc request approvenew [-m TEXT] PROJECT
1507             osc request decline [-m TEXT] ID
1508             osc request revoke [-m TEXT] ID
1509             osc request wipe ID
1510             osc request checkout/co ID
1511             osc review list [-U USER] [-G GROUP] [-s state]
1512             osc review add [-U USER] [-G GROUP] ID
1513             osc review accept [-m TEXT] ID
1514             osc review decline [-m TEXT] ID
1515             osc review new [-m TEXT] ID            # for setting a temporary comment without changing the state
1516         ${cmd_option_list}
1517         """
1518
1519         args = slash_split(args)
1520
1521         if opts.all and opts.state:
1522             raise oscerr.WrongOptions('Sorry, the options \'--all\' and \'--state\' ' \
1523                     'are mutually exclusive.')
1524         if opts.mine and opts.user:
1525             raise oscerr.WrongOptions('Sorry, the options \'--user\' and \'--mine\' ' \
1526                     'are mutually exclusive.')
1527         if opts.interactive and opts.non_interactive:
1528             raise oscerr.WrongOptions('Sorry, the options \'--interactive\' and ' \
1529                     '\'--non-interactive\' are mutually exclusive')
1530
1531         if not args:
1532             args = [ 'list' ]
1533             opts.mine = 1
1534             if opts.state == '':
1535                 opts.state = 'all'
1536
1537         if opts.state == '':
1538             opts.state = 'new'
1539
1540         cmds = ['add', 'list', 'log', 'show', 'decline', 'reopen', 'accept', 'approvenew', 'wipe', 'revoke', 'checkout', 'co', 'help']
1541         if not args or args[0] not in cmds:
1542             raise oscerr.WrongArgs('Unknown request action %s. Choose one of %s.' \
1543                                                % (args[0],', '.join(cmds)))
1544
1545         cmd = args[0]
1546         del args[0]
1547
1548         if cmd == 'help':
1549             return self.do_help(['help', 'request'])
1550
1551         if cmd in ['list']:
1552             min_args, max_args = 0, 2
1553         else:
1554             min_args, max_args = 1, 1
1555         if len(args) < min_args:
1556             raise oscerr.WrongArgs('Too few arguments.')
1557         if len(args) > max_args:
1558             raise oscerr.WrongArgs('Too many arguments.')
1559         if cmd in ['add'] and not opts.user and not opts.group:
1560             raise oscerr.WrongArgs('Need either a user or group as reviewer')
1561
1562         apiurl = self.get_api_url()
1563
1564         if cmd == 'list' or cmd == 'approvenew':
1565             package = None
1566             project = None
1567             if len(args) > 0:
1568                 project = args[0]
1569             elif not opts.mine and not opts.user:
1570                 try:
1571                     project = store_read_project(os.curdir)
1572                     package = store_read_package(os.curdir)
1573                 except oscerr.NoWorkingCopy:
1574                     pass
1575
1576             if len(args) > 1:
1577                 package = args[1]
1578         elif cmd in ['log', 'add', 'show', 'decline', 'reopen', 'accept', 'wipe', 'revoke', 'checkout', 'co']:
1579             reqid = args[0]
1580
1581         # add new reviewer to existing request
1582         if cmd in ['add'] and subcmd == 'review':
1583             query = { 'cmd': 'addreview' }
1584             if opts.user:
1585                 query['by_user'] = opts.user
1586             if opts.group:
1587                 query['by_group'] = opts.group
1588             url = makeurl(apiurl, ['request', reqid], query)
1589             r = http_POST(url)
1590             print r.read()
1591
1592         # list and approvenew
1593         elif cmd == 'list' or cmd == 'approvenew':
1594             states = ('new', 'accepted', 'revoked', 'declined')
1595             who = ''
1596             group = opts.group
1597             if cmd == 'approvenew':
1598                states = ('new')
1599                results = get_request_list(apiurl, project, package, '', ['new'])
1600             else:
1601                state_list = opts.state.split(',')
1602                if opts.all:
1603                    state_list = ['all']
1604                if subcmd == 'review':
1605                    state_list = ['review']
1606                elif opts.state == 'all':
1607                    state_list = ['all']
1608                else:
1609                    for s in state_list:
1610                        if not s in states:
1611                            raise oscerr.WrongArgs('Unknown state \'%s\', try one of %s' % (s, ','.join(states)))
1612                if opts.mine:
1613                    who = conf.get_apiurl_usr(apiurl)
1614                if opts.user:
1615                    who = opts.user
1616
1617                ## FIXME -B not implemented!
1618                if opts.bugowner:
1619                    if (self.options.debug):
1620                        print 'list: option --bugowner ignored: not impl.'
1621
1622                if subcmd == 'review':
1623                        # FIXME: do the review list for the user and for all groups he belong to
1624                        results = get_review_list(apiurl, project, package, who, opts.group, state_list)
1625                else:
1626                    if opts.involved_projects:
1627                        who = who or conf.get_apiurl_usr(apiurl)
1628                        results = get_user_projpkgs_request_list(apiurl, who, req_state=state_list,
1629                                                                 req_type=opts.type, exclude_projects=opts.exclude_target_project or [])
1630                    else:
1631                        results = get_request_list(apiurl, project, package, who,
1632                                                   state_list, opts.type, opts.exclude_target_project or [])
1633
1634             results.sort(reverse=True)
1635             import time
1636             days = opts.days or conf.config['request_list_days']
1637             since = ''
1638             try:
1639                 days = int(days)
1640             except ValueError:
1641                 days = 0
1642             if days > 0:
1643                 since = time.strftime('%Y-%m-%dT%H:%M:%S',time.localtime(time.time()-days*24*3600))
1644
1645             skipped = 0
1646             ## bs has received 2009-09-20 a new xquery compare() function
1647             ## which allows us to limit the list inside of get_request_list
1648             ## That would be much faster for coolo. But counting the remainder
1649             ## would not be possible with current xquery implementation.
1650             ## Workaround: fetch all, and filter on client side.
1651
1652             ## FIXME: date filtering should become implemented on server side
1653             for result in results:
1654                 if days == 0 or result.state.when > since or result.state.name == 'new':
1655                     print result.list_view()
1656                 else:
1657                     skipped += 1
1658             if skipped:
1659                 print "There are %d requests older than %s days.\n" % (skipped, days)
1660
1661             if cmd == 'approvenew':
1662                 print "\n *** Approve them all ? [y/n] ***"
1663                 if sys.stdin.read(1) == "y":
1664     
1665                     if not opts.message:
1666                         opts.message = edit_message()
1667                     for result in results:
1668                         print result.reqid, ": ",
1669                         r = change_request_state(apiurl,
1670                                 str(result.reqid), 'accepted', opts.message or '')
1671                         print r
1672                 else:
1673                     print >>sys.stderr, 'Aborted...'
1674                     raise oscerr.UserAbort()
1675
1676         elif cmd == 'log':
1677             for l in get_request_log(apiurl, reqid):
1678                 print l
1679
1680         # show
1681         elif cmd == 'show':
1682             r = get_request('apiurl', reqid)
1683             if opts.brief:
1684                 print r.list_view()
1685             elif (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive:
1686                 return request_interactive_review(apiurl, r)
1687             else:
1688                 print r
1689             # fixme: will inevitably fail if the given target doesn't exist
1690             if opts.diff and r.actions[0].type != 'submit':
1691                 raise oscerr.WrongOptions('\'--diff\' is not possible for request type: \'%s\'' % r.actions[0].type)
1692             elif opts.diff:
1693                 try:
1694                     print server_diff(apiurl,
1695                                       r.actions[0].dst_project, r.actions[0].dst_package, None,
1696                                       r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, True)
1697                 except urllib2.HTTPError, e:
1698                     if e.code != 400:
1699                         e.osc_msg = 'Diff not possible'
1700                         raise e
1701                     # backward compatiblity: only a recent api/backend supports the missingok parameter
1702                     try:
1703                         print server_diff(apiurl,
1704                                           r.actions[0].dst_project, r.actions[0].dst_package, None,
1705                                           r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, False)
1706                     except urllib2.HTTPError, e:
1707                         e.osc_msg = 'Diff not possible'
1708                         raise
1709
1710         # checkout
1711         elif cmd == 'checkout' or cmd == 'co':
1712             r = get_request(apiurl, reqid)
1713             submits = [ i for i in r.actions if i.type == 'submit' ]
1714             if not len(submits):
1715                 raise oscerr.WrongArgs('\'checkout\' only works for \'submit\' requests')
1716             checkout_package(apiurl, submits[0].src_project, submits[0].src_package, \
1717                 submits[0].src_rev, expand_link=True, prj_dir=submits[0].src_project)
1718
1719         else:
1720             if not opts.message:
1721                 opts.message = edit_message()
1722             state_map = {'reopen' : 'new', 'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked'}
1723             # Change review state only
1724             if subcmd == 'review':
1725                 if cmd in ['accept', 'decline', 'new']:
1726                     r = change_review_state(apiurl,
1727                             reqid, state_map[cmd], conf.config['user'], opts.message or '')
1728                     print r
1729             # Change state of entire request
1730             elif cmd in ['reopen', 'accept', 'decline', 'wipe', 'revoke']:
1731                 r = change_request_state(apiurl,
1732                         reqid, state_map[cmd], opts.message or '')
1733                 print r
1734
1735     # editmeta and its aliases are all depracated
1736     @cmdln.alias("editprj")
1737     @cmdln.alias("createprj")
1738     @cmdln.alias("editpac")
1739     @cmdln.alias("createpac")
1740     @cmdln.alias("edituser")
1741     @cmdln.alias("usermeta")
1742     @cmdln.hide(1)
1743     def do_editmeta(self, subcmd, opts, *args):
1744         """${cmd_name}:
1745
1746         Obsolete command to edit metadata. Use 'meta' now.
1747
1748         See the help output of 'meta'.
1749
1750         """
1751
1752         print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
1753         print >>sys.stderr, 'See \'osc help meta\'.'
1754         #self.do_help([None, 'meta'])
1755         return 2
1756
1757
1758     @cmdln.option('-r', '--revision', metavar='rev',
1759                   help='use the specified revision.')
1760     @cmdln.option('-u', '--unset', action='store_true',
1761                   help='remove revision in link, it will point always to latest revision')
1762     def do_setlinkrev(self, subcmd, opts, *args):
1763         """${cmd_name}: Updates a revision number in a source link.
1764
1765         This command adds or updates a specified revision number in a source link.
1766         The current revision of the source is used, if no revision number is specified.
1767
1768         usage:
1769             osc setlinkrev
1770             osc setlinkrev PROJECT [PACKAGE]
1771         ${cmd_option_list}
1772         """
1773
1774         args = slash_split(args)
1775         apiurl = self.get_api_url()
1776         package = None
1777         if len(args) == 0:
1778             p = findpacs(os.curdir)[0]
1779             project = p.prjname
1780             package = p.name
1781             apiurl = p.apiurl
1782             if not p.islink():
1783                 sys.exit('Local directory is no checked out source link package, aborting')
1784         elif len(args) == 2:
1785             project = args[0]
1786             package = args[1]
1787         elif len(args) == 1:
1788             project = args[0]
1789         else:
1790             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1791                   + self.get_cmd_help('setlinkrev'))
1792
1793         if package:
1794             packages = [ package ]
1795         else:
1796             packages = meta_get_packagelist(apiurl, project)
1797
1798         for p in packages:
1799             print "setting revision for package", p
1800             if opts.unset:
1801                 rev=-1
1802             else:
1803                 rev, dummy = parseRevisionOption(opts.revision)
1804             set_link_rev(apiurl, project, p, rev)
1805
1806
1807     def do_linktobranch(self, subcmd, opts, *args):
1808         """${cmd_name}: Convert a package containing a classic link with patch to a branch
1809
1810         This command tells the server to convert a _link with or without a project.diff
1811         to a branch. This is a full copy with a _link file pointing to the branched place.
1812
1813         usage:
1814             osc linktobranch                    # can be used in checked out package
1815             osc linktobranch PROJECT PACKAGE
1816         ${cmd_option_list}
1817         """
1818         args = slash_split(args)
1819         apiurl = self.get_api_url()
1820
1821         if len(args) == 0:
1822             wd = os.curdir
1823             project = store_read_project(wd)
1824             package = store_read_package(wd)
1825             update_local_dir = True
1826         elif len(args) < 2:
1827             raise oscerr.WrongArgs('Too few arguments (required none or two)')
1828         elif len(args) > 2:
1829             raise oscerr.WrongArgs('Too many arguments (required none or two)')
1830         else:
1831             project = args[0]
1832             package = args[1]
1833             update_local_dir = False
1834
1835         # execute
1836         link_to_branch(apiurl, project, package)
1837         if update_local_dir:
1838             pac = Package(wd)
1839             pac.update(rev=pac.latest_rev())
1840
1841
1842     @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'],
1843                   help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.')
1844     @cmdln.option('-c', '--current', action='store_true',
1845                   help='link fixed against current revision.')
1846     @cmdln.option('-r', '--revision', metavar='rev',
1847                   help='link the specified revision.')
1848     @cmdln.option('-f', '--force', action='store_true',
1849                   help='overwrite an existing link file if it is there.')
1850     @cmdln.option('-d', '--disable-publish', action='store_true',
1851                   help='disable publishing of the linked package')
1852     def do_linkpac(self, subcmd, opts, *args):
1853         """${cmd_name}: "Link" a package to another package
1854
1855         A linked package is a clone of another package, but plus local
1856         modifications. It can be cross-project.
1857
1858         The DESTPAC name is optional; the source packages' name will be used if
1859         DESTPAC is omitted.
1860
1861         Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
1862
1863         To add a patch, add the patch as file and add it to the _link file.
1864         You can also specify text which will be inserted at the top of the spec file.
1865
1866         See the examples in the _link file.
1867
1868         NOTE: In case you are not aware about the difference of 'linkpac' and 'branch' command
1869               you should use the 'branch' command by default.
1870
1871         usage:
1872             osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1873         ${cmd_option_list}
1874         """
1875
1876         args = slash_split(args)
1877         apiurl = self.get_api_url()
1878
1879         if not args or len(args) < 3:
1880             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1881                   + self.get_cmd_help('linkpac'))
1882
1883         rev, dummy = parseRevisionOption(opts.revision)
1884
1885         src_project = args[0]
1886         src_package = args[1]
1887         dst_project = args[2]
1888         if len(args) > 3:
1889             dst_package = args[3]
1890         else:
1891             dst_package = src_package
1892
1893         if src_project == dst_project and src_package == dst_package:
1894             raise oscerr.WrongArgs('Error: source and destination are the same.')
1895
1896         if src_project == dst_project and not opts.cicount:
1897             # in this case, the user usually wants to build different spec
1898             # files from the same source
1899             opts.cicount = "copy"
1900
1901         if opts.current:
1902             rev = show_upstream_rev(apiurl, src_project, src_package)
1903
1904         if rev and not checkRevision(src_project, src_package, rev):
1905             print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
1906             sys.exit(1)
1907
1908         link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish)
1909
1910     @cmdln.option('--nosources', action='store_true',
1911                   help='ignore source packages when copying build results to destination project')
1912     @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]',
1913                   help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]')
1914     @cmdln.option('-d', '--disable-publish', action='store_true',
1915                   help='disable publishing of the aggregated package')
1916     def do_aggregatepac(self, subcmd, opts, *args):
1917         """${cmd_name}: "Aggregate" a package to another package
1918
1919         Aggregation of a package means that the build results (binaries) of a
1920         package are basically copied into another project.
1921         This can be used to make packages available from building that are
1922         needed in a project but available only in a different project. Note
1923         that this is done at the expense of disk space. See
1924         http://en.opensuse.org/openSUSE:Build_Service_Tips_and_Tricks#_link_and__aggregate
1925         for more information.
1926
1927         The DESTPAC name is optional; the source packages' name will be used if
1928         DESTPAC is omitted.
1929
1930         usage:
1931             osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1932         ${cmd_option_list}
1933         """
1934
1935         args = slash_split(args)
1936
1937         if not args or len(args) < 3:
1938             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1939                   + self.get_cmd_help('aggregatepac'))
1940
1941         src_project = args[0]
1942         src_package = args[1]
1943         dst_project = args[2]
1944         if len(args) > 3:
1945             dst_package = args[3]
1946         else:
1947             dst_package = src_package
1948
1949         if src_project == dst_project and src_package == dst_package:
1950             raise oscerr.WrongArgs('Error: source and destination are the same.')
1951
1952         repo_map = {}
1953         if opts.map_repo:
1954             for pair in opts.map_repo.split(','):
1955                 src_tgt = pair.split('=')
1956                 if len(src_tgt) != 2:
1957                     raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo)
1958                 repo_map[src_tgt[0]] = src_tgt[1]
1959
1960         aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish, opts.nosources)
1961
1962
1963     @cmdln.option('-c', '--client-side-copy', action='store_true',
1964                         help='do a (slower) client-side copy')
1965     @cmdln.option('-k', '--keep-maintainers', action='store_true',
1966                         help='keep original maintainers. Default is remove all and replace with the one calling the script.')
1967     @cmdln.option('-d', '--keep-develproject', action='store_true',
1968                         help='keep develproject tag in the package metadata')
1969     @cmdln.option('-r', '--revision', metavar='rev',
1970                         help='link the specified revision.')
1971     @cmdln.option('-t', '--to-apiurl', metavar='URL',
1972                         help='URL of destination api server. Default is the source api server.')
1973     @cmdln.option('-m', '--message', metavar='TEXT',
1974                   help='specify message TEXT')
1975     @cmdln.option('-e', '--expand', action='store_true',
1976                         help='if the source package is a link then copy the expanded version of the link')
1977     def do_copypac(self, subcmd, opts, *args):
1978         """${cmd_name}: Copy a package
1979
1980         A way to copy package to somewhere else.
1981
1982         It can be done across buildservice instances, if the -t option is used.
1983         In that case, a client-side copy is implied.
1984
1985         Using --client-side-copy always involves downloading all files, and
1986         uploading them to the target.
1987
1988         The DESTPAC name is optional; the source packages' name will be used if
1989         DESTPAC is omitted.
1990
1991         usage:
1992             osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1993         ${cmd_option_list}
1994         """
1995
1996         args = slash_split(args)
1997
1998         if not args or len(args) < 3:
1999             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
2000                   + self.get_cmd_help('copypac'))
2001
2002         src_project = args[0]
2003         src_package = args[1]
2004         dst_project = args[2]
2005         if len(args) > 3:
2006             dst_package = args[3]
2007         else:
2008             dst_package = src_package
2009
2010         src_apiurl = conf.config['apiurl']
2011         if opts.to_apiurl:
2012             dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl)
2013         else:
2014             dst_apiurl = src_apiurl
2015
2016         if src_apiurl != dst_apiurl:
2017             opts.client_side_copy = True
2018
2019         rev, dummy = parseRevisionOption(opts.revision)
2020
2021         if opts.message:
2022             comment = opts.message
2023         else:
2024             if not rev:
2025                 rev = show_upstream_rev(src_apiurl, src_project, src_package)
2026             comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev )
2027
2028         if src_project == dst_project and \
2029            src_package == dst_package and \
2030            not rev and \
2031            src_apiurl == dst_apiurl:
2032             raise oscerr.WrongArgs('Source and destination are the same.')
2033
2034         r = copy_pac(src_apiurl, src_project, src_package,
2035                      dst_apiurl, dst_project, dst_package,
2036                      client_side_copy=opts.client_side_copy,
2037                      keep_maintainers=opts.keep_maintainers,
2038                      keep_develproject=opts.keep_develproject,
2039                      expand=opts.expand,
2040                      revision=rev,
2041                      comment=comment)
2042         print r
2043
2044
2045     @cmdln.option('-c', '--checkout', action='store_true',
2046                         help='Checkout branched package afterwards ' \
2047                                 '(\'osc bco\' is a shorthand for this option)' )
2048     @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
2049                         help='Use this attribute to find affected packages (default is OBS:Maintained)')
2050     @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE',
2051                         help='Use this attribute to find update projects (default is OBS:UpdateProject) ')
2052     def do_mbranch(self, subcmd, opts, *args):
2053         """${cmd_name}: Multiple branch of a package
2054
2055         [See http://en.opensuse.org/openSUSE:Build_Service_Concept_Maintenance
2056         for information on this topic.]
2057
2058         This command is used for creating multiple links of defined version of a package
2059         in one project. This is esp. used for maintenance updates.
2060
2061         The branched package will live in
2062             home:USERNAME:branches:ATTRIBUTE:PACKAGE
2063         if nothing else specified.
2064
2065         usage:
2066             osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ]
2067         ${cmd_option_list}
2068         """
2069         args = slash_split(args)
2070         apiurl = self.get_api_url()
2071         tproject = None
2072
2073         maintained_attribute = conf.config['maintained_attribute']
2074         maintained_update_project_attribute = conf.config['maintained_update_project_attribute']
2075
2076         if not len(args) or len(args) > 2:
2077             raise oscerr.WrongArgs('Wrong number of arguments.')
2078         if len(args) >= 1:
2079             package = args[0]
2080         if len(args) >= 2:
2081             tproject = args[1]
2082
2083         r = attribute_branch_pkg(apiurl, maintained_attribute, maintained_update_project_attribute, \
2084                                  package, tproject)
2085
2086         if r is None:
2087             print >>sys.stderr, 'ERROR: Attribute branch call came not back with a project.'
2088             sys.exit(1)
2089
2090         print "Project " + r + " created."
2091
2092         if opts.checkout:
2093             init_project_dir(apiurl, r, r)
2094             print statfrmt('A', r)
2095
2096             # all packages
2097             for package in meta_get_packagelist(apiurl, r):
2098                 try:
2099                     checkout_package(apiurl, r, package, expand_link = True, prj_dir = r)
2100                 except:
2101                     print >>sys.stderr, 'Error while checkout package:\n', package
2102
2103             if conf.config['verbose']:
2104                 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
2105
2106
2107     @cmdln.alias('branchco')
2108     @cmdln.alias('bco')
2109     @cmdln.alias('getpac')
2110     @cmdln.option('--nodevelproject', action='store_true',
2111                         help='do not follow a defined devel project ' \
2112                              '(primary project where a package is developed)')
2113     @cmdln.option('-c', '--checkout', action='store_true',
2114                         help='Checkout branched package afterwards ' \
2115                                 '(\'osc bco\' is a shorthand for this option)' )
2116     @cmdln.option('-f', '--force', default=False, action="store_true",
2117                   help='force branch, overwrite target')
2118     @cmdln.option('-m', '--message', metavar='TEXT',
2119                         help='specify message TEXT')
2120     @cmdln.option('-r', '--revision', metavar='rev',
2121                         help='branch against a specific revision')
2122     def do_branch(self, subcmd, opts, *args):
2123         """${cmd_name}: Branch a package
2124
2125         [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration
2126         for information on this topic.]
2127
2128         Create a source link from a package of an existing project to a new
2129         subproject of the requesters home project (home:branches:)
2130
2131         The branched package will live in
2132             home:USERNAME:branches:PROJECT/PACKAGE
2133         if nothing else specified.
2134
2135         With getpac or bco, the branched package will come from
2136             %(getpac_default_project)s
2137         if nothing else specified.
2138
2139         usage:
2140             osc branch
2141             osc branch SOURCEPROJECT SOURCEPACKAGE
2142             osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT
2143             osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE
2144             osc getpac  SOURCEPACKAGE
2145             osc bco ...
2146         ${cmd_option_list}
2147         """
2148
2149         if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': opts.checkout = True
2150         args = slash_split(args)
2151         tproject = tpackage = None
2152
2153         if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1:
2154             print >>sys.stderr, 'defaulting to %s/%s' % (conf.config['getpac_default_project'], args[0])
2155             # python has no args.unshift ???
2156             args = [ conf.config['getpac_default_project'] , args[0] ]
2157             
2158         if len(args) == 0 and is_package_dir('.'):
2159             args = (store_read_project('.'), store_read_package('.'))
2160
2161         if len(args) < 2 or len(args) > 4:
2162             raise oscerr.WrongArgs('Wrong number of arguments.')
2163
2164         apiurl = self.get_api_url()
2165
2166         expected = 'home:%s:branches:%s' % (conf.config['user'], args[0])
2167         if len(args) >= 3:
2168             expected = tproject = args[2]
2169         if len(args) >= 4:
2170             tpackage = args[3]
2171
2172         if not opts.message:
2173                 footer='please specify the purpose of your branch'
2174                 template='This package was branched from %s in order to ...\n' % args[0]
2175                 opts.message = edit_message(footer, template)
2176
2177         exists, targetprj, targetpkg, srcprj, srcpkg = \
2178                 branch_pkg(apiurl, args[0], args[1],
2179                            nodevelproject=opts.nodevelproject, rev=opts.revision,
2180                            target_project=tproject, target_package=tpackage,
2181                            return_existing=opts.checkout, msg=opts.message or '',
2182                            force=opts.force)
2183         if exists:
2184             print >>sys.stderr, 'Using existing branch project: %s' % targetprj
2185
2186         devloc = None
2187         if not exists and (srcprj is not None and srcprj != args[0] or \
2188                            srcprj is None and targetprj != expected):
2189             devloc = srcprj or targetprj
2190             if not srcprj and 'branches:' in targetprj:
2191                 devloc = targetprj.split('branches:')[1]
2192             print '\nNote: The branch has been created of a different project,\n' \
2193                   '              %s,\n' \
2194                   '      which is the primary location of where development for\n' \
2195                   '      that package takes place.\n' \
2196                   '      That\'s also where you would normally make changes against.\n' \
2197                   '      A direct branch of the specified package can be forced\n' \
2198                   '      with the --nodevelproject option.\n' % devloc
2199
2200         package = tpackage or args[1]
2201         if opts.checkout:
2202             checkout_package(apiurl, targetprj, package,
2203                              expand_link=True, prj_dir=targetprj)
2204             if conf.config['verbose']:
2205                 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
2206         else:
2207             apiopt = ''
2208             if conf.get_configParser().get('general', 'apiurl') != apiurl:
2209                 apiopt = '-A %s ' % apiurl
2210             print 'A working copy of the branched package can be checked out with:\n\n' \
2211                   'osc %sco %s/%s' \
2212                       % (apiopt, targetprj, package)
2213         print_request_list(apiurl, args[0], args[1])
2214         if devloc:
2215             print_request_list(apiurl, devloc, args[1])
2216
2217
2218     def do_undelete(self, subcmd, opts, *args):
2219         """${cmd_name}: Restores a deleted project or package on the server.
2220
2221         The server restores a package including the sources and meta configuration.
2222         Binaries remain to be lost and will be rebuild.
2223
2224         usage:
2225            osc undelete PROJECT
2226            osc undelete PROJECT PACKAGE [PACKAGE ...]
2227
2228         ${cmd_option_list}
2229         """
2230
2231         args = slash_split(args)
2232         if len(args) < 1:
2233             raise oscerr.WrongArgs('Missing argument.')
2234
2235         apiurl = self.get_api_url()
2236         prj = args[0]
2237         pkgs = args[1:]
2238
2239         if pkgs:
2240             for pkg in pkgs:
2241                 undelete_package(apiurl, prj, pkg)
2242         else:
2243             undelete_project(apiurl, prj)
2244
2245
2246     @cmdln.option('-f', '--force', action='store_true',
2247                         help='deletes a package or an empty project')
2248     def do_rdelete(self, subcmd, opts, *args):
2249         """${cmd_name}: Delete a project or packages on the server.
2250
2251         As a safety measure, project must be empty (i.e., you need to delete all
2252         packages first). If you are sure that you want to remove this project and all
2253         its packages use \'--force\' switch.
2254
2255         usage:
2256            osc rdelete -f PROJECT
2257            osc rdelete PROJECT PACKAGE [PACKAGE ...]
2258
2259         ${cmd_option_list}
2260         """
2261
2262         args = slash_split(args)
2263         if len(args) < 1:
2264             raise oscerr.WrongArgs('Missing argument.')
2265
2266         apiurl = self.get_api_url()
2267         prj = args[0]
2268         pkgs = args[1:]
2269
2270         if pkgs:
2271             for pkg in pkgs:
2272                # careful: if pkg is an empty string, the package delete request results
2273                # into a project delete request - which works recursively...
2274                 if pkg:
2275                     delete_package(apiurl, prj, pkg)
2276         elif len(meta_get_packagelist(apiurl, prj)) >= 1 and not opts.force:
2277             print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
2278                                 'If you are sure that you want to remove this project and all its ' \
2279                                 'packages use the \'--force\' switch'
2280             sys.exit(1)
2281         else:
2282             delete_project(apiurl, prj)
2283
2284     @cmdln.hide(1)
2285     def do_deletepac(self, subcmd, opts, *args):
2286         print """${cmd_name} is obsolete !
2287
2288                  Please use either
2289                    osc delete       for checked out packages or projects
2290                  or
2291                    osc rdelete      for server side operations."""
2292
2293         sys.exit(1)
2294
2295     @cmdln.hide(1)
2296     @cmdln.option('-f', '--force', action='store_true',
2297                         help='deletes a project and its packages')
2298     def do_deleteprj(self, subcmd, opts, project):
2299         """${cmd_name} is obsolete !
2300
2301                  Please use
2302                    osc rdelete PROJECT
2303         """
2304         sys.exit(1)
2305
2306     @cmdln.alias('metafromspec')
2307     @cmdln.option('', '--specfile', metavar='FILE',
2308                       help='Path to specfile. (if you pass more than working copy this option is ignored)')
2309     def do_updatepacmetafromspec(self, subcmd, opts, *args):
2310         """${cmd_name}: Update package meta information from a specfile
2311
2312         ARG, if specified, is a package working copy.
2313
2314         ${cmd_usage}
2315         ${cmd_option_list}
2316         """
2317
2318         args = parseargs(args)
2319         if opts.specfile and len(args) == 1:
2320             specfile = opts.specfile
2321         else:
2322             specfile = None
2323         pacs = findpacs(args)
2324         for p in pacs:
2325             p.read_meta_from_spec(specfile)
2326             p.update_package_meta()
2327
2328
2329     @cmdln.alias('di')
2330     @cmdln.option('-c', '--change', metavar='rev',
2331                         help='the change made by revision rev (like -r rev-1:rev).'
2332                              'If rev is negative this is like -r rev:rev-1.')
2333     @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
2334                         help='If rev1 is specified it will compare your working copy against '
2335                              'the revision (rev1) on the server. '
2336                              'If rev1 and rev2 are specified it will compare rev1 against rev2 '
2337                              '(NOTE: changes in your working copy are ignored in this case)')
2338     @cmdln.option('-p', '--plain', action='store_true',
2339                         help='output the diff in plain (not unified) diff format')
2340     @cmdln.option('--missingok', action='store_true',
2341                         help='do not fail if the source or target project/package does not exist on the server')
2342     def do_diff(self, subcmd, opts, *args):
2343         """${cmd_name}: Generates a diff
2344
2345         Generates a diff, comparing local changes against the repository
2346         server.
2347
2348         ARG, specified, is a filename to include in the diff.
2349
2350         ${cmd_usage}
2351         ${cmd_option_list}
2352         """
2353
2354         args = parseargs(args)
2355         pacs = findpacs(args)
2356
2357         if opts.change:
2358             try:
2359                 rev = int(opts.change)
2360                 if rev > 0:
2361                     rev1 = rev - 1
2362                     rev2 = rev
2363                 elif rev < 0:
2364                     rev1 = -rev
2365                     rev2 = -rev - 1
2366                 else:
2367                     return
2368             except:
2369                 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
2370                 return
2371         else:
2372             rev1, rev2 = parseRevisionOption(opts.revision)
2373         diff = ''
2374         for pac in pacs:
2375             if not rev2:
2376                 diff += ''.join(make_diff(pac, rev1))
2377             else:
2378                 diff += server_diff(pac.apiurl, pac.prjname, pac.name, rev1,
2379                                     pac.prjname, pac.name, rev2, not opts.plain, opts.missingok)
2380         if len(diff) > 0:
2381             run_pager(diff)
2382
2383
2384     @cmdln.option('--oldprj', metavar='OLDPRJ',
2385                   help='project to compare against'
2386                   ' (deprecated, use 3 argument form)')
2387     @cmdln.option('--oldpkg', metavar='OLDPKG',
2388                   help='package to compare against'
2389                   ' (deprecated, use 3 argument form)')
2390     @cmdln.option('-r', '--revision', metavar='N[:M]',
2391                   help='revision id, where N = old revision and M = new revision')
2392     @cmdln.option('-p', '--plain', action='store_true',
2393                   help='output the diff in plain (not unified) diff format')
2394     @cmdln.option('-c', '--change', metavar='rev',
2395                         help='the change made by revision rev (like -r rev-1:rev). '
2396                              'If rev is negative this is like -r rev:rev-1.')
2397     @cmdln.option('--missingok', action='store_true',
2398                         help='do not fail if the source or target project/package does not exist on the server')
2399     def do_rdiff(self, subcmd, opts, *args):
2400         """${cmd_name}: Server-side "pretty" diff of two packages
2401
2402         Compares two packages (three or four arguments) or shows the
2403         changes of a specified revision of a package (two arguments)
2404
2405         If no revision is specified the latest revision is used.
2406
2407         Note that this command doesn't return a normal diff (which could be
2408         applied as patch), but a "pretty" diff, which also compares the content
2409         of tarballs.
2410
2411
2412         usage:
2413             osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC]
2414             osc ${cmd_name} PROJECT PACKAGE
2415         ${cmd_option_list}
2416         """
2417
2418         args = slash_split(args)
2419         apiurl = self.get_api_url()
2420
2421         rev1 = None
2422         rev2 = None
2423
2424         old_project = None
2425         old_package = None
2426         new_project = None
2427         new_package = None
2428
2429         if len(args) == 2:
2430             new_project = args[0]
2431             new_package = args[1]
2432             if opts.oldprj:
2433                 old_project = opts.oldprj
2434             if opts.oldpkg:
2435                 old_package = opts.oldpkg
2436         elif len(args) == 3 or len(args) == 4:
2437             if opts.oldprj or opts.oldpkg:
2438                 raise oscerr.WrongArgs('--oldpkg and --oldprj are only valid with two arguments')
2439             old_project = args[0]
2440             new_package = old_package = args[1]
2441             new_project = args[2]
2442             if len(args) == 4:
2443                 new_package = args[3]
2444         else:
2445             raise oscerr.WrongArgs('Wrong number of arguments')
2446
2447
2448         if opts.change:
2449             try:
2450                 rev = int(opts.change)
2451                 if rev > 0:
2452                     rev1 = rev - 1
2453                     rev2 = rev
2454                 elif rev < 0:
2455                     rev1 = -rev
2456                     rev2 = -rev - 1
2457                 else:
2458                     return
2459             except:
2460                 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
2461                 return
2462         else:
2463             if opts.revision:
2464                 rev1, rev2 = parseRevisionOption(opts.revision)
2465
2466         rdiff = server_diff(apiurl,
2467                             old_project, old_package, rev1,
2468                             new_project, new_package, rev2, not opts.plain, opts.missingok)
2469         print rdiff
2470
2471     @cmdln.hide(1)
2472     @cmdln.alias('in')
2473     def do_install(self, subcmd, opts, *args):
2474         """${cmd_name}: install a package after build via zypper in -r
2475
2476         Not implemented yet. Use osc repourls,
2477         select the url you best like (standard),
2478         chop off after the last /, this should work with zypper.
2479
2480
2481         ${cmd_usage}
2482         ${cmd_option_list}
2483         """
2484
2485         args = slash_split(args)
2486         args = expand_proj_pack(args)
2487
2488         ## FIXME:
2489         ## if there is only one argument, and it ends in .ymp
2490         ## then fetch it, Parse XML to get the first
2491         ##  metapackage.group.repositories.repository.url
2492         ## and construct zypper cmd's for all
2493         ##  metapackage.group.software.item.name
2494         ##
2495         ## if args[0] is already an url, the use it as is.
2496
2497         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])
2498         print self.do_install.__doc__
2499         print "Example: \n" + cmd
2500
2501
2502     def do_repourls(self, subcmd, opts, *args):
2503         """${cmd_name}: Shows URLs of .repo files
2504
2505         Shows URLs on which to access the project .repos files (yum-style
2506         metadata) on download.opensuse.org.
2507
2508         usage:
2509            osc repourls [PROJECT]
2510
2511         ${cmd_option_list}
2512         """
2513
2514         apiurl = self.get_api_url()
2515
2516         if len(args) == 1:
2517             project = args[0]
2518         elif len(args) == 0:
2519             project = store_read_project('.')
2520         else:
2521             raise oscerr.WrongArgs('Wrong number of arguments')
2522
2523         # XXX: API should somehow tell that
2524         url_tmpl = 'http://download.opensuse.org/repositories/%s/%s/%s.repo'
2525         repos = get_repositories_of_project(apiurl, project)
2526         for repo in repos:
2527             print url_tmpl % (project.replace(':', ':/'), repo, project)
2528
2529
2530     @cmdln.option('-r', '--revision', metavar='rev',
2531                         help='checkout the specified revision. '
2532                              'NOTE: if you checkout the complete project '
2533                              'this option is ignored!')
2534     @cmdln.option('-e', '--expand-link', action='store_true',
2535                         help='if a package is a link, check out the expanded '
2536                              'sources (no-op, since this became the default)')
2537     @cmdln.option('-u', '--unexpand-link', action='store_true',
2538                         help='if a package is a link, check out the _link file ' \
2539                              'instead of the expanded sources')
2540     @cmdln.option('-M', '--meta', action='store_true',
2541                         help='checkout out meta data instead of sources' )
2542     @cmdln.option('-c', '--current-dir', action='store_true',
2543                         help='place PACKAGE folder in the current directory' \
2544                              'instead of a PROJECT/PACKAGE directory')
2545     @cmdln.option('-s', '--source-service-files', action='store_true',
2546                         help='Use server side generated sources instead of local generation.' )
2547     @cmdln.option('-S', '--server-side-source-service-files', action='store_true',
2548                         help='Use server side generated sources instead of local generation.' )
2549     @cmdln.option('-l', '--limit-size', metavar='limit_size',
2550                         help='Skip all files with a given size')
2551     @cmdln.alias('co')
2552     def do_checkout(self, subcmd, opts, *args):
2553         """${cmd_name}: Check out content from the repository
2554
2555         Check out content from the repository server, creating a local working
2556         copy.
2557
2558         When checking out a single package, the option --revision can be used
2559         to specify a revision of the package to be checked out.
2560
2561         When a package is a source link, then it will be checked out in
2562         expanded form. If --unexpand-link option is used, the checkout will
2563         instead produce the raw _link file plus patches.
2564
2565         usage:
2566             osc co PROJECT [PACKAGE] [FILE]
2567                osc co PROJECT                    # entire project
2568                osc co PROJECT PACKAGE            # a package
2569                osc co PROJECT PACKAGE FILE       # single file -> to current dir
2570
2571             while inside a project directory:
2572                osc co PACKAGE                    # check out PACKAGE from project
2573
2574         ${cmd_option_list}
2575         """
2576
2577         if opts.unexpand_link:
2578             expand_link = False
2579         else:
2580             expand_link = True
2581
2582         args = slash_split(args)
2583         project = package = filename = None
2584
2585         apiurl = self.get_api_url()
2586
2587         try:
2588             project = project_dir = args[0]
2589             package = args[1]
2590             filename = args[2]
2591         except:
2592             pass
2593
2594         if args and len(args) == 1:
2595             localdir = os.getcwd()
2596             if is_project_dir(localdir):
2597                 project = store_read_project(localdir)
2598                 project_dir = localdir
2599                 package = args[0]
2600
2601         rev, dummy = parseRevisionOption(opts.revision)
2602         if rev==None:
2603             rev="latest"
2604
2605         if rev and rev != "latest" and not checkRevision(project, package, rev):
2606             print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2607             sys.exit(1)
2608
2609         if filename:
2610             get_source_file(apiurl, project, package, filename, revision=rev, progress_obj=self.download_progress)
2611
2612         elif package:
2613             if opts.current_dir:
2614                 project_dir = None
2615             checkout_package(apiurl, project, package, rev, expand_link=expand_link, \
2616                              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)
2617             print_request_list(apiurl, project, package)
2618
2619         elif project:
2620             prj_dir = project
2621             if sys.platform[:3] == 'win':
2622                 prj_dir = prj_dir.replace(':', ';')
2623             if os.path.exists(prj_dir):
2624                 sys.exit('osc: project \'%s\' already exists' % project)
2625
2626             # check if the project does exist (show_project_meta will throw an exception)
2627             show_project_meta(apiurl, project)
2628
2629             init_project_dir(apiurl, prj_dir, project)
2630             print statfrmt('A', prj_dir)
2631
2632             # all packages
2633             for package in meta_get_packagelist(apiurl, project):
2634                 try:
2635                     checkout_package(apiurl, project, package, expand_link = expand_link, \
2636                                      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)
2637                 except oscerr.LinkExpandError, e:
2638                     print >>sys.stderr, 'Link cannot be expanded:\n', e
2639                     print >>sys.stderr, 'Use "osc repairlink" for fixing merge conflicts:\n'
2640                     # check out in unexpanded form at least
2641                     checkout_package(apiurl, project, package, expand_link = False, \
2642                                      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)
2643             print_request_list(apiurl, project)
2644
2645         else:
2646             raise oscerr.WrongArgs('Missing argument.\n\n' \
2647                   + self.get_cmd_help('checkout'))
2648
2649
2650     @cmdln.option('-q', '--quiet', action='store_true',
2651                         help='print as little as possible')
2652     @cmdln.option('-v', '--verbose', action='store_true',
2653                         help='print extra information')
2654     @cmdln.alias('st')
2655     def do_status(self, subcmd, opts, *args):
2656         """${cmd_name}: Show status of files in working copy
2657
2658         Show the status of files in a local working copy, indicating whether
2659         files have been changed locally, deleted, added, ...
2660
2661         The first column in the output specifies the status and is one of the
2662         following characters:
2663           ' ' no modifications
2664           'A' Added
2665           'C' Conflicted
2666           'D' Deleted
2667           'M' Modified
2668           '?' item is not under version control
2669           '!' item is missing (removed by non-osc command) or incomplete
2670
2671         examples:
2672           osc st
2673           osc st <directory>
2674           osc st file1 file2 ...
2675
2676         usage:
2677             osc status [OPTS] [PATH...]
2678         ${cmd_option_list}
2679         """
2680
2681         args = parseargs(args)
2682
2683         # storage for single Package() objects
2684         pacpaths = []
2685         # storage for a project dir ( { prj_instance : [ package objects ] } )
2686         prjpacs = {}
2687         for arg in args:
2688             # when 'status' is run inside a project dir, it should
2689             # stat all packages existing in the wc
2690             if is_project_dir(arg):
2691                 prj = Project(arg, False)
2692
2693                 if conf.config['do_package_tracking']:
2694                     prjpacs[prj] = []
2695                     for pac in prj.pacs_have:
2696                         # we cannot create package objects if the dir does not exist
2697                         if not pac in prj.pacs_broken:
2698                             prjpacs[prj].append(os.path.join(arg, pac))
2699                 else:
2700                     pacpaths += [arg + '/' + n for n in prj.pacs_have]
2701             elif is_package_dir(arg):
2702                 pacpaths.append(arg)
2703             elif os.path.isfile(arg):
2704                 pacpaths.append(arg)
2705             else:
2706                 msg = '\'%s\' is neither a project or a package directory' % arg
2707                 raise oscerr.NoWorkingCopy, msg
2708         lines = []
2709         # process single packages
2710         lines = getStatus(findpacs(pacpaths), None, opts.verbose, opts.quiet)
2711         # process project dirs
2712         for prj, pacs in prjpacs.iteritems():
2713             lines += getStatus(findpacs(pacs), prj, opts.verbose, opts.quiet)
2714         if lines:
2715             print '\n'.join(lines)
2716
2717
2718     def do_add(self, subcmd, opts, *args):
2719         """${cmd_name}: Mark files to be added upon the next