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