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