Putting names at the top of files is is not recommended. Collective wisdom for
[opensuse:osc.git] / osc / commandline.py
1 #!/usr/bin/python
2 # vim: sw=4 et
3
4 # Copyright (C) 2006 Novell Inc.  All rights reserved.
5 # This program is free software; it may be used, copied, modified
6 # and distributed under the terms of the GNU General Public Licence,
7 # either version 2, or (at your option) any later version.
8
9
10 from core import *
11 import cmdln
12 import conf
13 import oscerr
14
15
16 class Osc(cmdln.Cmdln):
17     """usage:
18         osc [GLOBALOPTS] SUBCOMMAND [OPTS] [ARGS...]
19         osc help SUBCOMMAND
20     OpenSUSE build service command-line tool.
21     Type 'osc help <subcommand>' for help on a specific subcommand.
22
23     ${command_list}
24     ${help_list}
25     global ${option_list}
26     For additional information, see 
27     * http://www.opensuse.org/Build_Service_Tutorial
28     * http://www.opensuse.org/Build_Service/CLI
29
30     You can modify osc commands, or roll you own, via the plugin API:
31     * http://www.opensuse.org/Build_Service/osc_plugins
32     """
33     name = 'osc'
34     conf = None
35
36
37     def __init__(self, *args, **kwargs):
38         cmdln.Cmdln.__init__(self, *args, **kwargs)
39         cmdln.Cmdln.do_help.aliases.append('h')
40
41
42     def get_optparser(self):
43         """this is the parser for "global" options (not specific to subcommand)"""
44
45         optparser = cmdln.CmdlnOptionParser(self, version=get_osc_version())
46         optparser.add_option('--debugger', action='store_true',
47                       help='jump into the debugger before executing anything')
48         optparser.add_option('--post-mortem', action='store_true',
49                       help='jump into the debugger in case of errors')
50         optparser.add_option('-t', '--traceback', action='store_true',
51                       help='print call trace in case of errors')
52         optparser.add_option('-H', '--http-debug', action='store_true',
53                       help='debug HTTP traffic')
54         optparser.add_option('-d', '--debug', action='store_true',
55                       help='print info useful for debugging')
56         optparser.add_option('-A', '--apiurl', dest='apiurl',
57                       metavar='URL',
58                       help='specify URL to access API server at')
59         optparser.add_option('-c', '--config', dest='conffile',
60                       metavar='FILE',
61                       help='specify alternate configuration file')
62         return optparser
63
64
65     def postoptparse(self, try_again = True):
66         """merge commandline options into the config"""
67         try:
68             conf.get_config(override_conffile = self.options.conffile,
69                             override_apiurl = self.options.apiurl,
70                             override_debug = self.options.debug,
71                             override_http_debug = self.options.http_debug,
72                             override_traceback = self.options.traceback,
73                             override_post_mortem = self.options.post_mortem)
74         except oscerr.NoConfigfile, e:
75             print >>sys.stderr, e.msg
76             print >>sys.stderr, 'Creating osc configuration file %s ...' % e.file
77             import getpass
78             config = {}
79             config['user'] = raw_input('Username: ')
80             config['pass'] = getpass.getpass()
81             if self.options.apiurl:
82                 config['apiurl'] = self.options.apiurl
83
84             conf.write_initial_config(e.file, config)
85             print >>sys.stderr, 'done'
86             if try_again: self.postoptparse(try_again = False)
87         except oscerr.ConfigMissingApiurl, e:
88             print >>sys.stderr, e.msg
89             import getpass
90             user = raw_input('Username: ')
91             passwd = getpass.getpass()
92             cp = conf.get_configParser()
93             cp.add_section(e.url)
94             cp.set(e.url, 'user', user)
95             cp.set(e.url, 'pass', passwd)
96             file = open(e.file, 'w')
97             cp.write(file, True)
98             if file: file.close()
99             if try_again: self.postoptparse(try_again = False)
100
101         self.conf = conf
102
103
104     def get_cmd_help(self, cmdname):
105         doc = self._get_cmd_handler(cmdname).__doc__
106         doc = self._help_reindent(doc)
107         doc = self._help_preprocess(doc, cmdname)
108         doc = doc.rstrip() + '\n' # trim down trailing space
109         return self._str(doc)
110
111
112     def do_init(self, subcmd, opts, project, package=None):
113         """${cmd_name}: Initialize a directory as working copy 
114
115         Initialize an existing directory to be a working copy of an
116         (already existing) buildservice project/package. 
117
118         (This is the same as checking out a package and then copying sources
119         into the directory. It does NOT create a new package. To create a
120         package, use 'osc meta pkg ... ...')
121
122         You wouldn't normally use this command.
123
124         To get a working copy of a package (e.g. for building it or working on
125         it, you would normally use the checkout command. Use "osc help
126         checkout" to get help for it.
127         
128         usage: 
129             osc init PRJ
130             osc init PRJ PAC
131         ${cmd_option_list}
132         """
133         
134         if not package:
135             init_project_dir(conf.config['apiurl'], os.curdir, project)
136             print 'Initializing %s (Project: %s)' % (os.curdir, project)
137         else:
138             init_package_dir(conf.config['apiurl'], project, package, os.path.curdir)
139             print 'Initializing %s (Project: %s, Package: %s)' % (os.curdir, project, package)
140
141
142     @cmdln.alias('ls')
143     @cmdln.option('-a', '--arch', metavar='ARCH',
144                         help='specify architecture')
145     @cmdln.option('-r', '--repo', metavar='REPO',
146                         help='specify repository')
147     @cmdln.option('-b', '--binaries', action='store_true',
148                         help='list built binaries, instead of sources')
149     @cmdln.option('-v', '--verbose', action='store_true',
150                         help='print extra information')
151     def do_list(self, subcmd, opts, *args):
152         """${cmd_name}: List existing content on the server
153
154         This command is used to list sources, or binaries (when used with the
155         --binaries option). To use the --binary option, --repo and --arch are
156         also required.
157
158         Examples:
159            ls                         # list all projects
160            ls Apache                  # list packages in a project
161            ls -b Apache               # list all binaries of a project
162            ls Apache apache2          # list source files of package of a project
163            ls Apache apache2 <file>   # list <file> if this file exists
164            ls -v Apache apache2       # verbosely list source files of package 
165
166         With --verbose, the following fields will be shown for each item:
167            MD5 hash of file
168            Revision number of the last commit
169            Size (in bytes)
170            Date and time of the last commit
171
172         ${cmd_usage}
173         ${cmd_option_list}
174         """
175
176         args = slash_split(args)
177
178         if len(args) == 1:
179             project = args[0]
180         elif len(args) == 2 or len(args) == 3:
181             project = args[0]
182             package = args[1]
183             fname = None
184             if len(args) == 3:
185                 fname = args[2]
186         elif len(args) > 3:
187             raise oscerr.WrongArgs('Too many arguments')
188
189         if opts.binaries and (not opts.repo or not opts.arch):
190             raise oscerr.WrongOptions('Sorry, -r <repo> -a <arch> missing\n'
191                      'You can list repositories with: \'osc platforms <project>\'')
192
193         # list binaries
194         if opts.binaries:
195             if not args:
196                 raise oscerr.WrongArgs('There are no binaries to list above project level.')
197
198             elif len(args) == 1:
199                 #if opts.verbose:
200                 #    sys.exit('The verbose option is not implemented for projects.')
201                 r = get_binarylist(conf.config['apiurl'], project, opts.repo, opts.arch)
202                 print '\n'.join(r)
203
204             elif len(args) == 2:
205                 r = get_binarylist(conf.config['apiurl'], project, opts.repo, opts.arch, package=package)
206                 print '\n'.join(r)
207
208         # list sources
209         elif not opts.binaries:
210             if not args:
211                 print '\n'.join(meta_get_project_list(conf.config['apiurl']))
212
213             elif len(args) == 1:
214                 if opts.verbose:
215                     raise oscerr.WrongOptions('Sorry, the --verbose option is not implemented for projects.')
216
217                 print '\n'.join(meta_get_packagelist(conf.config['apiurl'], project))
218
219             elif len(args) == 2 or len(args) == 3:
220                 l = meta_get_filelist(conf.config['apiurl'], 
221                                       project, 
222                                       package,
223                                       verbose=opts.verbose)
224                 if opts.verbose:
225                     out = [ '%s %7d %9d %s %s' % (i.md5, i.rev, i.size, shorttime(i.mtime), i.name) \
226                             for i in l if not fname or fname == i.name ]
227                     if len(out) == 0:
228                         if fname:
229                             print 'file \'%s\' does not exist' % fname
230                     else:
231                         print '\n'.join(out)
232                 else:
233                     if fname:
234                         if fname in l:
235                             print fname
236                         else:    
237                             print 'file \'%s\' does not exist' % fname
238                     else:
239                         print '\n'.join(l)
240
241
242     @cmdln.option('-F', '--file', metavar='FILE',
243                         help='read metadata from FILE, instead of opening an editor. '
244                         '\'-\' denotes standard input. ')
245     @cmdln.option('-e', '--edit', action='store_true',
246                         help='edit metadata')
247     @cmdln.option('--delete', action='store_true',
248                         help='delete a pattern file')
249     def do_meta(self, subcmd, opts, *args):
250         """${cmd_name}: Show meta information, or edit it
251
252         Show or edit build service metadata of type <prj|pkg|prjconf|user|pattern>.
253
254         This command displays metadata on buildservice objects like projects,
255         packages, or users. The type of metadata is specified by the word after
256         "meta", like e.g. "meta prj".
257
258         prj denotes metadata of a buildservice project.
259         prjconf denotes the (build) configuration of a project.
260         pkg denotes metadata of a buildservice package.
261         user denotes the metadata of a user.
262         pattern denotes installation patterns defined for a project.
263
264         To list patterns, use 'osc meta pattern PRJ'. An additional argument
265         will be the pattern file to view or edit.
266
267         With the --edit switch, the metadata can be edited. Per default, osc
268         opens the program specified by the environmental variable EDITOR with a
269         temporary file. Alternatively, content to be saved can be supplied via
270         the --file switch. If the argument is '-', input is taken from stdin:
271         osc meta prjconf home:user | sed ... | osc meta prjconf home:user -F -
272
273         When trying to edit a non-existing resource, it is created implicitely.
274
275
276         Examples:
277             osc meta prj PRJ
278             osc meta pkg PRJ PKG
279             osc meta pkg PRJ PKG -e
280
281         Usage:
282             osc meta <prj|pkg|prjconf|user|pattern> ARGS...
283             osc meta <prj|pkg|prjconf|user|pattern> -e|--edit ARGS...
284             osc meta <prj|pkg|prjconf|user|pattern> -F|--file ARGS...
285             osc meta pattern --delete PRJ PATTERN
286         ${cmd_option_list}
287         """
288
289         args = slash_split(args)
290
291         if not args or args[0] not in metatypes.keys():
292             raise oscerr.WrongArgs('Unknown meta type. Choose one of %s.' \
293                                                % ', '.join(metatypes))
294
295         cmd = args[0]
296         del args[0]
297
298         if cmd in ['pkg']:
299             min_args, max_args = 2, 2
300         elif cmd in ['pattern']:
301             min_args, max_args = 1, 2
302         else:
303             min_args, max_args = 1, 1
304         if len(args) < min_args:
305             raise oscerr.WrongArgs('Too few arguments.')
306         if len(args) > max_args:
307             raise oscerr.WrongArgs('Too many arguments.')
308
309         # specific arguments
310         if cmd == 'prj':
311             project = args[0]
312         elif cmd == 'pkg':
313             project, package = args[0:2]
314         elif cmd == 'prjconf':
315             project = args[0]
316         elif cmd == 'user':
317             user = args[0]
318         elif cmd == 'pattern':
319             project = args[0]
320             if len(args) > 1:
321                 pattern = args[1]
322             else:
323                 pattern = None
324                 # enforce pattern argument if needed
325                 if opts.edit or opts.file:
326                     raise oscerr.WrongArgs('A pattern file argument is required.')
327
328         # show 
329         if not opts.edit and not opts.file and not opts.delete:
330             if cmd == 'prj':
331                 sys.stdout.write(''.join(show_project_meta(conf.config['apiurl'], project)))
332             elif cmd == 'pkg':
333                 sys.stdout.write(''.join(show_package_meta(conf.config['apiurl'], project, package)))
334             elif cmd == 'prjconf':
335                 sys.stdout.write(''.join(show_project_conf(conf.config['apiurl'], project)))
336             elif cmd == 'user':
337                 r = get_user_meta(conf.config['apiurl'], user)
338                 if r:
339                     sys.stdout.write(''.join(r))
340             elif cmd == 'pattern':
341                 if pattern:
342                     r = show_pattern_meta(conf.config['apiurl'], project, pattern)
343                     if r:
344                         sys.stdout.write(''.join(r))
345                 else:
346                     r = show_pattern_metalist(conf.config['apiurl'], project)
347                     if r:
348                         sys.stdout.write('\n'.join(r) + '\n')
349
350         # edit
351         if opts.edit and not opts.file:
352             if cmd == 'prj':
353                 edit_meta(metatype='prj', 
354                           edit=True,
355                           path_args=quote_plus(project),
356                           template_args=({
357                                   'name': project,
358                                   'user': conf.config['user']}))
359             elif cmd == 'pkg':
360                 edit_meta(metatype='pkg', 
361                           edit=True,
362                           path_args=(quote_plus(project), quote_plus(package)),
363                           template_args=({
364                                   'name': package,
365                                   'user': conf.config['user']}))
366             elif cmd == 'prjconf':
367                 edit_meta(metatype='prjconf', 
368                           edit=True,
369                           path_args=quote_plus(project),
370                           template_args=None)
371             elif cmd == 'user':
372                 edit_meta(metatype='user', 
373                           edit=True,
374                           path_args=(quote_plus(user)),
375                           template_args=({'user': user}))
376             elif cmd == 'pattern':
377                 edit_meta(metatype='pattern', 
378                           edit=True,
379                           path_args=(project, pattern),
380                           template_args=None)
381
382         # upload file
383         if opts.file:
384
385             if opts.file == '-':
386                 f = sys.stdin.read()
387             else:
388                 try:
389                     f = open(opts.file).read()
390                 except:
391                     sys.exit('could not open file \'%s\'.' % opts.file)
392
393             if cmd == 'prj':
394                 edit_meta(metatype='prj', 
395                           data=f,
396                           edit=opts.edit,
397                           path_args=quote_plus(project))
398             elif cmd == 'pkg':
399                 edit_meta(metatype='pkg', 
400                           data=f,
401                           edit=opts.edit,
402                           path_args=(quote_plus(project), quote_plus(package)))
403             elif cmd == 'prjconf':
404                 edit_meta(metatype='prjconf', 
405                           data=f,
406                           edit=opts.edit,
407                           path_args=quote_plus(project))
408             elif cmd == 'user':
409                 edit_meta(metatype='user', 
410                           data=f,
411                           edit=opts.edit,
412                           path_args=(quote_plus(user)))
413             elif cmd == 'pattern':
414                 edit_meta(metatype='pattern', 
415                           data=f,
416                           edit=opts.edit,
417                           path_args=(project, pattern))
418
419
420         # delete
421         if opts.delete:
422             path = metatypes[cmd]['path']
423             if cmd == 'pattern':
424                 path = path % (project, pattern)
425                 u = makeurl(conf.config['apiurl'], [path])
426                 http_DELETE(u)
427             else:
428                 sys.exit('The --delete switch is only for pattern metadata.')
429
430
431
432     @cmdln.option('-d', '--diff', action='store_true',
433                   help='generate a diff')
434     @cmdln.option('-u', '--unified', action='store_true',
435                   help='output the diff in the unified diff format')
436     @cmdln.option('-m', '--message', metavar='TEXT',
437                   help='specify message TEXT')
438     @cmdln.option('-r', '--revision', metavar='REV',
439                   help='for "create", specify a certain source revision ID (the md5 sum)')
440     @cmdln.option('--nodevelproject', action='store_true',
441                   help='do not follow a defined devel project ' \
442                        '(primary project where a package is developed)')
443     @cmdln.option('-s', '--state', default='new',
444                         help='only list requests in one of the comma separated given states [default=new]')
445     @cmdln.alias("sr")
446     @cmdln.alias("submitrequest")
447     def do_submitreq(self, subcmd, opts, *args):
448         """${cmd_name}: Handle requests to submit a package into another project
449
450         [See http://en.opensuse.org/Build_Service/Collaboration for information
451         on this topic.]
452
453         For "create", there are two ways to use it. Either with a working copy
454         or without. If called with no arguments, osc will guess what to submit 
455         where. If you don't have a working copy, you can give the respective
456         arguments on the commandline (see below for an example).
457         Then, the DESTPAC name is optional; the source packages' name will be
458         used if DESTPAC is omitted.
459         With --message, a message can be attached.
460         With --revision, a revision MD5 of a package can be specified which is
461         to be submitted. The default is to request submission of the currently
462         checked in revision.
463
464         "list" lists open requests attached to a project or package.
465
466         "log" will show the history of the given ID
467
468         "show" will show the request itself, and generate a diff for review, if
469         used with the --diff option.
470
471         "decline" will change the request state to "declined" and append a
472         message that you specify with the --message option.
473
474         "delete" will permanently delete a request and append a
475         message that you specify with the --message option.
476
477         "revoke" will set the request state to "revoked" and append a
478         message that you specify with the --message option.
479
480         "accept" will change the request state to "accepted" and will trigger
481         the actual submit process. That would normally be a server-side copy of
482         the source package to the target package.
483
484
485         usage:
486             osc submitreq create [-m TEXT] 
487             osc submitreq create [-m TEXT] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
488             osc submitreq list [PRJ [PKG]]
489             osc submitreq log ID
490             osc submitreq show [-d] ID
491             osc submitreq accept [-m TEXT] ID
492             osc submitreq decline [-m TEXT] ID
493             osc submitreq delete [-m TEXT] ID
494             osc submitreq revoke [-m TEXT] ID
495         ${cmd_option_list}
496         """
497
498         args = slash_split(args)
499
500         cmds = ['create', 'list', 'log', 'show', 'decline', 'accept', 'delete', 'revoke']
501         if not args or args[0] not in cmds:
502             raise oscerr.WrongArgs('Unknown submitreq action. Choose one of %s.' \
503                                                % ', '.join(cmds))
504
505         cmd = args[0]
506         del args[0]
507
508         if cmd in ['create']:
509             min_args, max_args = 0, 4
510         elif cmd in ['list']:
511             min_args, max_args = 0, 2
512         else:
513             min_args, max_args = 1, 1
514         if len(args) < min_args:
515             raise oscerr.WrongArgs('Too few arguments.')
516         if len(args) > max_args:
517             raise oscerr.WrongArgs('Too many arguments.')
518
519         apiurl = conf.config['apiurl']
520
521         # collect specific arguments
522         if cmd == 'create':
523             if len(args) == 0:
524                 # try using the working copy at hand
525                 p = findpacs(os.curdir)[0]
526                 src_project = p.prjname
527                 src_package = p.name
528                 if p.islink():
529                     dst_project = p.linkinfo.project
530                     dst_package = p.linkinfo.package
531                     apiurl = p.apiurl
532                 else:
533                     sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
534                              'Please provide it the target via commandline arguments.' % p.name)
535
536             elif len(args) >= 3:
537                 # get the arguments from the commandline
538                 src_project, src_package, dst_project = args[0:3]
539                 if len(args) == 4:
540                     dst_package = args[3]
541                 else:
542                     dst_package = src_package
543             else:
544                 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
545                       + self.get_cmd_help('submitreq'))
546
547         elif cmd == 'list':
548             package = None
549             project = None
550             if len(args) > 0:
551                 project = args[0]
552             else:
553                 project = store_read_project(os.curdir)
554                 apiurl = store_read_apiurl(os.curdir)
555                 try:
556                     package = store_read_package(os.curdir)
557                 except oscerr.NoWorkingCopy:
558                     pass
559
560             if len(args) > 1:
561                 package = args[1]
562         elif cmd in ['log', 'show', 'decline', 'accept', 'delete', 'revoke']:
563             reqid = args[0]
564
565
566         # create
567         if cmd == 'create':
568             if not opts.nodevelproject:
569                 devloc = None
570                 try:
571                     devloc = show_develproject(apiurl, dst_project, dst_package)
572                 except urllib2.HTTPError:
573                     print >>sys.stderr, """\
574 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
575                         % (dst_project, dst_package)
576                     pass
577
578                 if devloc \
579                       and dst_project != devloc \
580                       and src_project != devloc:
581                           print """\
582 Sorry, but a different project, %s, is defined as the place where development
583 of the package %s primarily takes place.
584 Please submit there instead, or use --nodevelproject to force direct submission.""" \
585                     % (devloc, dst_package)
586                           sys.exit(1)
587             reqs = get_submit_request_list(apiurl, dst_project, dst_package)
588             user = conf.get_apiurl_usr(apiurl)
589             myreqs = [ i for i in reqs if i.state.who == user ]
590             repl = ''
591             if len(myreqs) > 0:
592                 print 'You already created the following submitrequests: %s.' % \
593                       ', '.join([str(i.reqid) for i in myreqs ])
594                 repl = raw_input('Revoke the old requests? (y/N) ')
595
596             result = create_submit_request(apiurl, 
597                                           src_project, src_package,
598                                           dst_project, dst_package,
599                                           opts.message, orev=opts.revision)
600             if repl == 'y':
601                 for req in myreqs:
602                     change_submit_request_state(apiurl, str(req.reqid), 'revoked',
603                                                 'superseded by %s' % result)
604
605             print 'created request id', result
606
607
608         # list
609         elif cmd == 'list':
610             state_list = opts.state.split(',')
611
612             results = get_submit_request_list(apiurl,
613                                              project, package, state_list)
614
615             results.sort(reverse=True)
616
617             for result in results:
618                 print result.list_view()
619
620         elif cmd == 'log':
621             for l in get_submit_request_log(conf.config['apiurl'], reqid):
622                 print l
623
624
625         # show
626         elif cmd == 'show':
627             r = get_submit_request(conf.config['apiurl'], reqid)
628             print r
629             # fixme: will inevitably fail if the given target doesn't exist
630             if opts.diff:
631                 try:
632                     print server_diff(conf.config['apiurl'],
633                                       r.dst_project, r.dst_package, None,
634                                       r.src_project, r.src_package, r.src_md5, opts.unified)
635                 except urllib2.HTTPError, e:
636                     e.osc_msg = 'Diff not possible'
637                     raise
638
639
640         # decline
641         elif cmd == 'decline':
642             r = change_submit_request_state(conf.config['apiurl'], 
643                     reqid, 'declined', opts.message or '')
644             print r
645
646         # accept
647         elif cmd == 'accept':
648             r = change_submit_request_state(conf.config['apiurl'], 
649                     reqid, 'accepted', opts.message or '')
650             print r
651         # delete
652         elif cmd == 'delete':
653             r = change_submit_request_state(conf.config['apiurl'],
654                     reqid, 'deleted', opts.message or '')
655             print r
656         # revoke
657         elif cmd == 'revoke':
658             r = change_submit_request_state(conf.config['apiurl'],
659                     reqid, 'revoked', opts.message or '')
660             print r
661
662     # editmeta and its aliases are all depracated
663     @cmdln.alias("editprj")
664     @cmdln.alias("createprj")
665     @cmdln.alias("editpac")
666     @cmdln.alias("createpac")
667     @cmdln.alias("edituser")
668     @cmdln.alias("usermeta")
669     def do_editmeta(self, subcmd, opts, *args):
670         """${cmd_name}: 
671         
672         Obsolete command to edit metadata. Use 'meta' now.
673
674         See the help output of 'meta'.
675
676         """
677
678         print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
679         print >>sys.stderr, 'See \'osc help meta\'.'
680         #self.do_help([None, 'meta'])
681         return 2
682
683
684     @cmdln.option('-r', '--revision', metavar='rev',
685                   help='link the specified revision.')
686     def do_linkpac(self, subcmd, opts, *args):
687         """${cmd_name}: "Link" a package to another package
688         
689         A linked package is a clone of another package, but plus local
690         modifications. It can be cross-project.
691
692         The DESTPAC name is optional; the source packages' name will be used if
693         DESTPAC is omitted.
694
695         Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
696
697         To add a patch, add the patch as file and add it to the _link file.
698         You can also specify text which will be inserted at the top of the spec file.
699
700         See the examples in the _link file.
701
702         usage: 
703             osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
704         ${cmd_option_list}
705         """
706
707         args = slash_split(args)
708
709         if not args or len(args) < 3:
710             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
711                   + self.get_cmd_help('linkpac'))
712
713         rev, dummy = parseRevisionOption(opts.revision)
714
715         src_project = args[0]
716         src_package = args[1]
717         dst_project = args[2]
718         if len(args) > 3:
719             dst_package = args[3]
720         else:
721             dst_package = src_package
722
723         if src_project == dst_project and src_package == dst_package:
724             print >>sys.stderr, 'Error: source and destination are the same.'
725             return 1
726
727         if rev and not checkRevision(src_project, src_package, rev):
728             print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
729             sys.exit(1)
730
731         link_pac(src_project, src_package, dst_project, dst_package, rev)
732
733     def do_aggregatepac(self, subcmd, opts, *args):
734         """${cmd_name}: "Aggregate" a package to another package
735         
736         Aggregation of a package means that the build results (binaries) of a
737         package are basically copied into another project.
738         This can be used to make packages available from building that are
739         needed in a project but available only in a different project. Note
740         that this is done at the expense of disk space. See 
741         http://en.opensuse.org/Build_Service/Tips_and_Tricks#_link_and__aggregate
742         for more information.
743
744         The DESTPAC name is optional; the source packages' name will be used if
745         DESTPAC is omitted.
746
747         usage: 
748             osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
749         ${cmd_option_list}
750         """
751
752         args = slash_split(args)
753
754         if not args or len(args) < 3:
755             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
756                   + self.get_cmd_help('aggregatepac'))
757
758         src_project = args[0]
759         src_package = args[1]
760         dst_project = args[2]
761         if len(args) > 3:
762             dst_package = args[3]
763         else:
764             dst_package = src_package
765
766         if src_project == dst_project and src_package == dst_package:
767             print >>sys.stderr, 'Error: source and destination are the same.'
768             return 1
769         aggregate_pac(src_project, src_package, dst_project, dst_package)
770
771
772     @cmdln.option('-c', '--client-side-copy', action='store_true',
773                         help='do a (slower) client-side copy')
774     @cmdln.option('-k', '--keep-maintainers', action='store_true',
775                         help='keep original maintainers. Default is remove all and replace with the one calling the script.')
776     @cmdln.option('-t', '--to-apiurl', metavar='URL',
777                         help='URL of destination api server. Default is the source api server.')
778     def do_copypac(self, subcmd, opts, *args):
779         """${cmd_name}: Copy a package
780
781         A way to copy package to somewhere else. 
782         
783         It can be done across buildservice instances, if the -t option is used.
784         In that case, a client-side copy is implied.
785
786         Using --client-side-copy always involves downloading all files, and
787         uploading them to the target.
788
789         The DESTPAC name is optional; the source packages' name will be used if
790         DESTPAC is omitted.
791
792         usage: 
793             osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
794         ${cmd_option_list}
795         """
796
797         args = slash_split(args)
798
799         if not args or len(args) < 3:
800             raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
801                   + self.get_cmd_help('copypac'))
802
803         src_project = args[0]
804         src_package = args[1]
805         dst_project = args[2]
806         if len(args) > 3:
807             dst_package = args[3]
808         else:
809             dst_package = src_package
810
811         src_apiurl = conf.config['apiurl']
812         if opts.to_apiurl:
813             dst_apiurl = opts.to_apiurl
814         else:
815             dst_apiurl = src_apiurl
816
817         if src_project == dst_project and \
818            src_package == dst_package and \
819            src_apiurl == dst_apiurl:
820                 raise oscerr.WrongArgs('Source and destination are the same.')
821
822         if src_apiurl != dst_apiurl:
823             opts.client_side_copy = True
824
825         r = copy_pac(src_apiurl, src_project, src_package, 
826                      dst_apiurl, dst_project, dst_package,
827                      client_side_copy=opts.client_side_copy,
828                      keep_maintainers=opts.keep_maintainers)
829         print r
830
831
832     @cmdln.option('--nodevelproject', action='store_true',
833                         help='do not follow a defined devel project ' \
834                              '(primary project where a package is developed)')
835     @cmdln.option('-r', '--revision', metavar='rev',
836                         help='branch against a specific revision')
837     def do_branch(self, subcmd, opts, *args):
838         """${cmd_name}: Branch a package
839
840         [See http://en.opensuse.org/Build_Service/Collaboration for information
841         on this topic.]
842
843         Create a source link from a package of an existing project to a new
844         subproject of the requesters home project (home:branches:)
845
846         The branched package will live in
847             home:USERNAME:branches:PROJECT/PACKAGE
848         
849         usage: 
850             osc branch SOURCEPRJ SOURCEPKG
851         ${cmd_option_list}
852         """
853
854         args = slash_split(args)
855         if len(args) != 2:
856             raise oscerr.WrongArgs('Wrong number of arguments.')
857
858         r = branch_pkg(conf.config['apiurl'], args[0], args[1],
859                        nodevelproject=opts.nodevelproject, rev=opts.revision)
860
861         expected = 'home:%s:branches:%s' % (conf.config['user'], args[0])
862         if r != expected:
863             devloc = r.split('branches:')[1]
864             print '\nNote: The branch has been created of a different project,\n' \
865                   '              %s,\n' \
866                   '      which is the primary location of where development for\n' \
867                   '      that package takes place.\n' \
868                   '      That\'s also where you would normally make changes against.\n' \
869                   '      A direct branch of the specified package can be forced\n' \
870                   '      with the --nodevelproject option.\n' % devloc
871
872         print 'A working copy of the branched package can be checked out with:\n\n' \
873               'osc co %s/%s' \
874                       % (r, args[1])
875
876
877     def do_deletepac(self, subcmd, opts, *args):
878         """${cmd_name}: Delete packages on the repository server
879
880         usage:
881             osc deletepac PROJECT PACKAGE [PACKAGE ...]
882         ${cmd_option_list}
883         """
884
885         args = slash_split(args)
886         pkgs = args[1:]
887         if not pkgs:
888             raise oscerr.WrongArgs('Missing argument.')
889
890         for pkg in pkgs:
891             # careful: if pkg is an empty string, the package delete request results
892             # into a project delete request - which works recursively...
893             if pkg:
894                 delete_package(conf.config['apiurl'], args[0], pkg)
895
896
897     @cmdln.option('-f', '--force', action='store_true',
898                         help='deletes a project and its packages')
899     def do_deleteprj(self, subcmd, opts, project):
900         """${cmd_name}: Delete a project on the repository server
901
902         As a safety measure, project must be empty (i.e., you need to delete all
903         packages first). If you are sure that you want to remove this project and all
904         its packages use \'--force\' switch.
905
906
907         ${cmd_usage}
908         ${cmd_option_list}
909         """
910
911         if len(meta_get_packagelist(conf.config['apiurl'], project)) >= 1 and not opts.force:
912             print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
913                                 'If you are sure that you want to remove this project and all its ' \
914                                 'packages use the \'--force\' switch'
915             sys.exit(1)
916         else:
917             delete_project(conf.config['apiurl'], project)
918
919
920     @cmdln.alias('metafromspec')
921     @cmdln.option('', '--specfile', metavar='FILE',
922                       help='Path to specfile. (if you pass more than working copy this option is ignored)')
923     def do_updatepacmetafromspec(self, subcmd, opts, *args):
924         """${cmd_name}: Update package meta information from a specfile
925
926         ARG, if specified, is a package working copy.
927
928         ${cmd_usage}
929         ${cmd_option_list}
930         """
931
932         args = parseargs(args)
933         if opts.specfile and (len(args) == 1):
934             specfile = opts.specfile
935         else:
936             specfile = None
937         pacs = findpacs(args)
938         for p in pacs:
939             p.read_meta_from_spec(specfile)
940             p.update_package_meta()
941
942
943     @cmdln.alias('di')
944     @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
945                         help='If rev1 is specified it will compare your working copy against '
946                              'the revision (rev1) on the server. '
947                              'If rev1 and rev2 are specified it will compare rev1 against rev2 '
948                              '(NOTE: changes in your working copy are ignored in this case)')
949     @cmdln.option('-p', '--pretty', action='store_true',
950                         help='output the diff in the pretty diff format')
951     def do_diff(self, subcmd, opts, *args):
952         """${cmd_name}: Generates a diff
953
954         Generates a diff, comparing local changes against the repository
955         server.
956         
957         ARG, specified, is a filename to include in the diff.
958
959         ${cmd_usage}
960         ${cmd_option_list}
961         """
962
963         args = parseargs(args)
964         pacs = findpacs(args)
965         
966         rev1, rev2 = parseRevisionOption(opts.revision)
967         diff = ''
968         for pac in pacs:
969             if not rev2:
970                 diff += ''.join(make_diff(pac, rev1))
971             else:
972                 diff += server_diff(pac.apiurl, pac.prjname, pac.name, rev1,
973                                     pac.prjname, pac.name, rev2, not opts.pretty)
974         if len(diff) > 0:
975             print diff
976
977
978     @cmdln.option('--oldprj', metavar='OLDPRJ',
979                   help='project to compare against')
980     @cmdln.option('--oldpkg', metavar='OLDPKG',
981                   help='package to compare against')
982     @cmdln.option('-r', '--revision', metavar='N[:M]',
983                   help='revision id, where N = old revision and M = new revision')
984     @cmdln.option('-u', '--unified', action='store_true',
985                   help='output the diff in the unified diff format')
986     def do_rdiff(self, subcmd, opts, new_project, new_package):
987         """${cmd_name}: Server-side "pretty" diff of two packages
988
989         If neither OLDPRJ nor OLDPKG are specified, the diff is against the
990         last revision, thus showing the latest change.
991
992         Note that this command doesn't reply a "normal" diff which can be
993         applied as patch, but a pretty diff, which also compares the content of
994         tarballs.
995
996
997         ${cmd_usage}
998         ${cmd_option_list}
999         """
1000
1001         old_revision = None
1002         new_revision = None
1003         if opts.revision:
1004             old_revision, new_revision = parseRevisionOption(opts.revision)
1005
1006         rdiff = server_diff(conf.config['apiurl'],
1007                             opts.oldprj, opts.oldpkg, old_revision,
1008                             new_project, new_package, new_revision, opts.unified)
1009
1010         print rdiff
1011
1012
1013     def do_repourls(self, subcmd, opts, *args):
1014         """${cmd_name}: Shows URLs of .repo files 
1015
1016         Shows URLs on which to access the project .repos files (yum-style
1017         metadata) on download.opensuse.org.
1018
1019         ARG, if specified, is a package working copy.
1020
1021         ${cmd_usage}
1022         ${cmd_option_list}
1023         """
1024
1025         args = parseargs(args)
1026         pacs = findpacs(args)
1027
1028         url_tmpl = 'http://download.opensuse.org/repositories/%s/%s/%s.repo'
1029         for p in pacs:
1030             platforms = get_platforms_of_project(p.apiurl, p.prjname)
1031             for platform in platforms:
1032                 print url_tmpl % (p.prjname.replace(':', ':/'), platform, p.prjname)
1033
1034
1035
1036     @cmdln.option('-r', '--revision', metavar='rev',
1037                         help='checkout the specified revision. '
1038                              'NOTE: if you checkout the complete project '
1039                              'this option is ignored!')
1040     @cmdln.option('-e', '--expand-link', action='store_true',
1041                         help='if a package is a link, check out the expanded ' 
1042                              'sources (no-op, since this became the default)')
1043     @cmdln.option('-u', '--unexpand-link', action='store_true',
1044                         help='if a package is a link, check out the _link file ' \
1045                              'instead of the expanded sources')
1046     @cmdln.alias('co')
1047     def do_checkout(self, subcmd, opts, *args):
1048         """${cmd_name}: Check out content from the repository
1049         
1050         Check out content from the repository server, creating a local working
1051         copy.
1052
1053         When checking out a single package, the option --revision can be used
1054         to specify a revions of the package to be checked out.
1055
1056         When a package is a source link, then it will be checked out in 
1057         expanded form. If --unexpand-link option is used, the checkout will 
1058         instead produce the raw _link file plus patches.
1059         
1060
1061         examples:
1062             osc co Apache                    # entire project
1063             osc co Apache apache2            # a package
1064             osc co Apache apache2 foo        # single file -> to current dir
1065
1066         usage: 
1067             osc co PROJECT [PACKAGE] [FILE]
1068         ${cmd_option_list}
1069         """
1070
1071         if opts.unexpand_link: expand_link = False
1072         else: expand_link = True
1073
1074         args = slash_split(args)
1075         project = package = filename = None
1076         try: 
1077             project = args[0]
1078             package = args[1]
1079             filename = args[2]
1080         except: 
1081             pass
1082
1083
1084         rev, dummy = parseRevisionOption(opts.revision)
1085
1086         if rev and not checkRevision(project, package, rev):
1087             print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
1088             sys.exit(1)
1089
1090         if filename:
1091             get_source_file(conf.config['apiurl'], project, package, filename, revision=rev)
1092
1093         elif package:
1094             checkout_package(conf.config['apiurl'], project, package, 
1095                              rev, expand_link=expand_link, prj_dir=project)
1096
1097         elif project:
1098             if os.path.exists(project):
1099                 sys.exit('osc: project \'%s\' already exists' % project)
1100
1101             # check if the project does exist (show_project_meta will throw an exception)
1102             show_project_meta(conf.config['apiurl'], project)
1103
1104             init_project_dir(conf.config['apiurl'], project, project)
1105             print statfrmt('A', project)
1106
1107             # all packages
1108             for package in meta_get_packagelist(conf.config['apiurl'], project):
1109                 try:
1110                     checkout_package(conf.config['apiurl'], project, package, 
1111                                      expand_link=expand_link, prj_dir=project)
1112                 except oscerr.LinkExpandError, e:
1113                     print >>sys.stderr, 'Link cannot be expanded:\n', e
1114                     # check out in unexpanded form at least
1115                     checkout_package(conf.config['apiurl'], project, package, 
1116                                      expand_link=False, prj_dir=project)
1117
1118         else:
1119             raise oscerr.WrongArgs('Missing argument.\n\n' \
1120                   + self.get_cmd_help('checkout'))
1121
1122
1123     @cmdln.option('-q', '--quiet', action='store_true',
1124                         help='print as little as possible')
1125     @cmdln.option('-v', '--verbose', action='store_true',
1126                         help='print extra information')
1127     @cmdln.alias('st')
1128     def do_status(self, subcmd, opts, *args):
1129         """${cmd_name}: Show status of files in working copy
1130
1131         Show the status of files in a local working copy, indicating whether
1132         files have been changed locally, deleted, added, ...
1133
1134         The first column in the output specifies the status and is one of the
1135         following characters:
1136           ' ' no modifications
1137           'A' Added
1138           'C' Conflicted
1139           'D' Deleted
1140           'M' Modified
1141           '?' item is not under version control
1142           '!' item is missing (removed by non-svn command) or incomplete
1143
1144         examples:
1145           osc st
1146           osc st <directory>
1147           osc st file1 file2 ...
1148
1149         usage: 
1150             osc status [OPTS] [PATH...]
1151         ${cmd_option_list}
1152         """
1153
1154         args = parseargs(args)
1155
1156         # storage for single Package() objects
1157         pacpaths = []
1158         # storage for a project dir ( { prj_instance : [ package objects ] } )
1159         prjpacs = {}
1160         for arg in args:
1161             # when 'status' is run inside a project dir, it should
1162             # stat all packages existing in the wc
1163             if is_project_dir(arg):
1164                 prj = Project(arg, False)
1165
1166                 if conf.config['do_package_tracking']:
1167                     prjpacs[prj] = []
1168                     for pac in prj.pacs_have:
1169                         # we cannot create package objects if the dir does not exist
1170                         if not pac in prj.pacs_broken:
1171                             prjpacs[prj].append(os.path.join(arg, pac))
1172                 else:
1173                     pacpaths += [arg + '/' + n for n in prj.pacs_have]
1174             elif is_package_dir(arg):
1175                 pacpaths.append(arg)
1176             elif os.path.isfile(arg):
1177                 pacpaths.append(arg)
1178             else:
1179                 msg = '\'%s\' is neither a project or a package directory' % arg
1180                 raise oscerr.NoWorkingCopy, msg
1181         lines = []
1182         # process single packages
1183         lines = getStatus(findpacs(pacpaths), None, opts.verbose, opts.quiet)
1184         # process project dirs
1185         for prj, pacs in prjpacs.iteritems():
1186             lines += getStatus(findpacs(pacs), prj, opts.verbose, opts.quiet)
1187         if lines:
1188             print '\n'.join(lines)
1189
1190
1191     def do_add(self, subcmd, opts, *args):
1192         """${cmd_name}: Mark files to be added upon the next commit
1193
1194         usage: 
1195             osc add FILE [FILE...]
1196         ${cmd_option_list}
1197         """
1198         if not args:
1199             raise oscerr.WrongArgs('Missing argument.\n\n' \
1200                   + self.get_cmd_help('add'))
1201
1202         filenames = parseargs(args)
1203         addFiles(filenames)
1204
1205     
1206     def do_mkpac(self, subcmd, opts, *args):
1207         """${cmd_name}: Create a new package under version control
1208
1209         usage:
1210             osc mkpac new_package
1211         ${cmd_option_list}
1212         """
1213         if not conf.config['do_package_tracking']:
1214             print >>sys.stderr, "to use this feature you have to enable \'do_package_tracking\' " \
1215                                 "in the [general] section in the configuration file"
1216             sys.exit(1)
1217
1218         if len(args) != 1:
1219             raise oscerr.WrongArgs('Wrong number of arguments.')
1220
1221         createPackageDir(args[0])
1222
1223     @cmdln.option('-r', '--recursive', action='store_true',
1224                         help='If CWD is a project dir then scan all package dirs as well')
1225     def do_addremove(self, subcmd, opts, *args):
1226         """${cmd_name}: Adds new files, removes disappeared files
1227
1228         Adds all files new in the local copy, and removes all disappeared files.
1229
1230         ARG, if specified, is a package working copy.
1231
1232         ${cmd_usage}
1233         ${cmd_option_list}
1234         """
1235
1236         args = parseargs(args)
1237         arg_list = args[:]
1238         for arg in arg_list:
1239             if is_project_dir(arg):
1240                 prj = Project(arg, False)
1241                 for pac in prj.pacs_unvers:
1242                     pac_dir = getTransActPath(os.path.join(prj.dir, pac))
1243                     if os.path.isdir(pac_dir):
1244                         addFiles([pac_dir], prj)
1245                 for pac in prj.pacs_broken:
1246                     if prj.get_state(pac) != 'D':
1247                         prj.set_state(pac, 'D')
1248                         print statfrmt('D', getTransActPath(os.path.join(prj.dir, pac)))
1249                 if opts.recursive:
1250                     for pac in prj.pacs_have:
1251                         state = prj.get_state(pac)
1252                         if state != None and state != 'D':
1253                             pac_dir = getTransActPath(os.path.join(prj.dir, pac))
1254                             args.append(pac_dir)
1255                 args.remove(arg)
1256                 prj.write_packages()
1257
1258         pacs = findpacs(args)
1259         for p in pacs:
1260
1261             p.todo = p.filenamelist + p.filenamelist_unvers
1262
1263             for filename in p.todo:
1264                 if os.path.isdir(filename):
1265                     continue
1266                 # ignore foo.rXX, foo.mine for files which are in 'C' state
1267                 if os.path.splitext(filename)[0] in p.in_conflict:
1268                     continue
1269                 state = p.status(filename)
1270                 if state == '?':
1271                     p.addfile(filename)
1272                     print statfrmt('A', getTransActPath(os.path.join(p.dir, filename)))
1273                 elif state == '!':
1274                     p.put_on_deletelist(filename)
1275                     p.write_deletelist()
1276                     os.unlink(os.path.join(p.storedir, filename))
1277                     print statfrmt('D', getTransActPath(os.path.join(p.dir, filename)))
1278
1279
1280
1281     @cmdln.alias('ci')
1282     @cmdln.alias('checkin')
1283     @cmdln.option('-m', '--message', metavar='TEXT',
1284                   help='specify log message TEXT')
1285     @cmdln.option('-F', '--file', metavar='FILE',
1286                   help='read log message from FILE')
1287     def do_commit(self, subcmd, opts, *args):
1288         """${cmd_name}: Upload content to the repository server
1289
1290         Upload content which is changed in your working copy, to the repository
1291         server.
1292
1293         examples: 
1294            osc ci                   # current dir
1295            osc ci <dir>
1296            osc ci file1 file2 ...
1297
1298         ${cmd_usage}
1299         ${cmd_option_list}
1300         """
1301
1302         args = parseargs(args)
1303
1304         msg = ''
1305         if opts.message:
1306             msg = opts.message
1307         elif opts.file:
1308             try:
1309                 msg = open(opts.file).read()
1310             except:
1311                 sys.exit('could not open file \'%s\'.' % opts.file)
1312
1313         arg_list = args[:]
1314         for arg in arg_list:
1315             if conf.config['do_package_tracking'] and is_project_dir(arg):
1316                 Project(arg).commit(msg=msg)
1317                 args.remove(arg)
1318
1319         pacs = findpacs(args)
1320
1321         if not msg:
1322             # open editor for commit message
1323             # but first, produce status and diff to append to the template
1324             footer = diffs = []
1325             for pac in pacs:
1326                 changed = getStatus([pac], quiet=True)
1327                 if changed:
1328                     footer += changed
1329                     diffs += ['\nDiff for working copy: %s' % pac.dir]
1330                     diffs += make_diff(pac, 0)
1331             # if footer is empty, there is nothing to commit, and no edit needed.
1332             if footer:
1333                 msg = edit_message(footer='\n'.join(footer))
1334
1335         if conf.config['do_package_tracking'] and len(pacs) > 0:
1336             prj_paths = {}
1337             single_paths = []
1338             files = {}
1339             # it is possible to commit packages from different projects at the same
1340             # time: iterate over all pacs and put each pac to the right project in the dict
1341             for pac in pacs:
1342                 path = os.path.normpath(os.path.join(pac.dir, os.pardir))
1343                 if is_project_dir(path):
1344                     pac_path = os.path.basename(os.path.normpath(pac.absdir))
1345                     prj_paths.setdefault(path, []).append(pac_path)
1346                     files[pac_path] = pac.todo
1347                 else:
1348                     single_paths.append(pac.dir)
1349             for prj, packages in prj_paths.iteritems():
1350                 Project(prj).commit(tuple(packages), msg, files)
1351             for pac in single_paths:
1352                 Package(pac).commit(msg)
1353         else:
1354             for p in pacs:
1355                 p.commit(msg)
1356
1357
1358     @cmdln.option('-r', '--revision', metavar='REV',
1359                         help='update to specified revision (this option will be ignored '
1360                              'if you are going to update the complete project or more than '
1361                              'one package)')
1362     @cmdln.option('-u', '--unexpand-link', action='store_true',
1363                         help='if a package is an expanded link, update to the raw _link file')
1364     @cmdln.option('-e', '--expand-link', action='store_true',
1365                         help='if a package is a link, update to the expanded sources')
1366     @cmdln.alias('up')
1367     def do_update(self, subcmd, opts, *args):
1368         """${cmd_name}: Update a working copy
1369
1370         examples: 
1371
1372         1. osc up
1373                 If the current working directory is a package, update it.
1374                 If the directory is a project directory, update all contained
1375                 packages, AND check out newly added packages.
1376
1377                 To update only checked out packages, without checking out new
1378                 ones, you might want to use "osc up *" from within the project
1379                 dir.
1380
1381         2. osc up PAC
1382                 Update the packages specified by the path argument(s)
1383
1384         When --expand-link is used with source link packages, the expanded
1385         sources will be checked out. Without this option, the _link file and
1386         patches will be checked out. The option --unexpand-link can be used to
1387         switch back to the "raw" source with a _link file plus patch(es).
1388
1389         ${cmd_usage}
1390         ${cmd_option_list}
1391         """
1392
1393         args = parseargs(args)
1394         arg_list = args[:]
1395
1396         for arg in arg_list:
1397
1398             if is_project_dir(arg):
1399                 prj = Project(arg)
1400
1401                 if conf.config['do_package_tracking']:
1402                     prj.update(expand_link=opts.expand_link, 
1403                                unexpand_link=opts.unexpand_link)
1404                     args.remove(arg)
1405                 else:   
1406                     # if not tracking package, and 'update' is run inside a project dir, 
1407                     # it should do the following:
1408                     # (a) update all packages
1409                     args += prj.pacs_have
1410                     # (b) fetch new packages
1411                     prj.checkout_missing_pacs()
1412                     args.remove(arg)
1413
1414
1415         pacs = findpacs(args)
1416
1417         if (opts.expand_link and opts.unexpand_link) \
1418             or (opts.expand_link and opts.revision) \
1419             or (opts.unexpand_link and opts.revision):
1420             raise oscerr.WrongOptions('Sorry, the options --expand-link, --unexpand-link and ' 
1421                      '--revision are mutually exclusive.')
1422
1423         if opts.revision and ( len(args) == 1):
1424             rev, dummy = parseRevisionOption(opts.revision)
1425             if not checkRevision(pacs[0].prjname, pacs[0].name, rev, pacs[0].apiurl):
1426                 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
1427                 sys.exit(1)
1428         else:
1429             rev = None
1430
1431         for p in pacs:
1432             if len(pacs) > 1:
1433                 print 'Updating %s' % p.name
1434
1435             if not rev:
1436                 if opts.expand_link and p.islink() and not p.isexpanded():
1437                     print 'Expanding to rev', p.linkinfo.xsrcmd5
1438                     rev = p.linkinfo.xsrcmd5
1439                 elif opts.unexpand_link and p.islink() and p.isexpanded():
1440                     print 'Unexpanding to rev', p.linkinfo.lsrcmd5
1441                     rev = p.linkinfo.lsrcmd5
1442                 elif p.islink() and p.isexpanded():
1443                     rev = show_upstream_xsrcmd5(p.apiurl,
1444                                                 p.prjname, p.name)
1445
1446             # FIXME: ugly workaround for #399247
1447             if opts.expand_link or opts.unexpand_link:
1448                 if [ i for i in p.filenamelist+p.filenamelist_unvers if p.status(i) != ' ' ]:
1449                     print >>sys.stderr, 'osc: cannot expand/unexpand because your working ' \
1450                                         'copy has local modifications. Please remove them ' \
1451                                         'and try again'
1452                     sys.exit(1)
1453             p.update(rev)
1454             rev = None
1455                    
1456
1457     @cmdln.option('-f', '--force', action='store_true',
1458                         help='forces removal of package')
1459     @cmdln.alias('rm')
1460     @cmdln.alias('del')
1461     @cmdln.alias('remove')
1462     def do_delete(self, subcmd, opts, *args):
1463         """${cmd_name}: Mark files to be deleted upon the next 'checkin'
1464
1465         usage: 
1466             osc rm FILE [FILE...]
1467         ${cmd_option_list}
1468         """
1469
1470         if not args:
1471             raise oscerr.WrongArgs('Missing argument.\n\n' \
1472                   + self.get_cmd_help('delete'))
1473
1474         args = parseargs(args)
1475         # check if args contains a package which was removed by
1476         # a non-osc command and mark it with the 'D'-state
1477         arg_list = args[:]
1478         for i in arg_list:
1479             if not os.path.exists(i):
1480                 prj_dir, pac_dir = getPrjPacPaths(i)
1481                 if is_project_dir(prj_dir):
1482                     prj = Project(prj_dir, False)
1483                     if i in prj.pacs_broken:
1484                         if prj.get_state(i) != 'A':
1485                             prj.set_state(pac_dir, 'D')
1486                         else:
1487                             prj.del_package_node(i)
1488                         print statfrmt('D', getTransActPath(i))
1489                         args.remove(i)
1490                         prj.write_packages()
1491         pacs = findpacs(args)
1492
1493         for p in pacs:
1494             if not p.todo:
1495                 prj_dir, pac_dir = getPrjPacPaths(p.absdir)
1496                 if conf.config['do_package_tracking'] and is_project_dir(prj_dir):
1497                     prj = Project(prj_dir, False)
1498                     prj.delPackage(p, opts.force)
1499             else:
1500                 pathn = getTransActPath(p.dir)
1501                 for filename in p.todo:
1502                     if filename not in p.filenamelist:
1503                         sys.exit('\'%s\' is not under version control' % filename)
1504                     p.put_on_deletelist(filename)
1505                     p.write_deletelist()
1506                     p.delete_source_file(filename)
1507                     print statfrmt('D', os.path.join(pathn, filename))
1508
1509
1510     def do_resolved(self, subcmd, opts, *args):
1511         """${cmd_name}: Remove 'conflicted' state on working copy files
1512         
1513         If an upstream change can't be merged automatically, a file is put into
1514         in 'conflicted' ('C') state. Within the file, conflicts are marked with
1515         special <<<<<<< as well as ======== and >>>>>>> lines.
1516         
1517         After manually resolving all conflicting parts, use this command to
1518         remove the 'conflicted' state.
1519
1520         Note:  this subcommand does not semantically resolve conflicts or
1521         remove conflict markers; it merely removes the conflict-related
1522         artifact files and allows PATH to be committed again.
1523
1524         usage: 
1525             osc resolved FILE [FILE...]
1526         ${cmd_option_list}
1527         """
1528
1529         if not args:
1530             raise oscerr.WrongArgs('Missing argument.\n\n' \
1531                   + self.get_cmd_help('resolved'))
1532
1533         args = parseargs(args)
1534         pacs = findpacs(args)
1535
1536         for p in pacs:
1537
1538             for filename in p.todo:
1539                 print 'Resolved conflicted state of "%s"' % filename
1540                 p.clear_from_conflictlist(filename)
1541
1542
1543     def do_platforms(self, subcmd, opts, *args):
1544         """${cmd_name}: Shows available platforms
1545         
1546         Examples:
1547         1. osc platforms
1548                 Shows all available platforms/build targets
1549
1550         2. osc platforms <project>
1551                 Shows the configured platforms/build targets of a project
1552
1553         ${cmd_usage}
1554         ${cmd_option_list}
1555         """
1556
1557         if args:
1558             project = args[0]
1559             print '\n'.join(get_platforms_of_project(conf.config['apiurl'], project))
1560         else:
1561             print '\n'.join(get_platforms(conf.config['apiurl']))
1562
1563
1564     def do_results_meta(self, subcmd, opts, *args):
1565         """${cmd_name}: Shows raw build results of a package
1566
1567         Shows the build results of the package in raw XML.
1568
1569         ARG, if specified, is the working copy of a package.
1570
1571         ${cmd_usage}
1572         ${cmd_option_list}
1573         """
1574
1575         args = parseargs(args)
1576         pacs = findpacs(args)
1577
1578         for pac in pacs:
1579             print ''.join(show_results_meta(pac.apiurl, pac.prjname, package=pac.name))
1580
1581                 
1582     @cmdln.alias('r')
1583     @cmdln.option('-l', '--last-build', action='store_true',
1584                         help='show last build results (succeeded/failed/unknown)')
1585     def do_results(self, subcmd, opts, *args):
1586         """${cmd_name}: Shows the build results of a package
1587
1588         ARG, if specified, is the working copy of a package.
1589
1590         ${cmd_usage}
1591         ${cmd_option_list}
1592         """
1593
1594         args = parseargs(args)
1595         pacs = findpacs(args)
1596
1597         for pac in pacs:
1598             print '\n'.join(get_results(pac.apiurl, pac.prjname, pac.name, opts.last_build))
1599
1600
1601     @cmdln.option('-l', '--last-build', action='store_true',
1602                         help='show last build results (succeeded/failed/unknown)')
1603     def do_rresults(self, subcmd, opts, prj, pkg):
1604         """${cmd_name}: Shows the build results of a remote package
1605
1606         Examples:
1607
1608         osc rresults <remote project name> <remote package name>
1609
1610         ${cmd_usage}
1611         ${cmd_option_list}
1612         """
1613
1614         apiurl = conf.config['apiurl']
1615         print '\n'.join(get_results(apiurl, prj, pkg, opts.last_build))
1616
1617                 
1618     @cmdln.option('-q', '--hide-legend', action='store_true',
1619                         help='hide the legend')
1620     @cmdln.option('-c', '--csv', action='store_true',
1621                         help='csv output')
1622     @cmdln.option('-s', '--status-filter', metavar='STATUS',
1623                         help='show only packages with buildstatus STATUS (see legend)')
1624     @cmdln.option('-n', '--name-filter', metavar='EXPR',
1625                         help='show only packages whose names match EXPR')
1626     @cmdln.option('-p', '--project', metavar='PROJECT',
1627                         help='show packages in project PROJECT')
1628     
1629     @cmdln.alias('pr')
1630     def do_prjresults(self, subcmd, opts, *args):
1631         """${cmd_name}: Shows project-wide build results
1632         
1633         Examples:
1634
1635         1. osc prjresults <dir>
1636                 dir is a project or package directory
1637
1638         2. osc prjresults
1639                 the project is guessed from the current dir
1640
1641         3. osc prjresults --project=<project>
1642                 the project is specified from the command line
1643
1644         ${cmd_usage}
1645         ${cmd_option_list}
1646         """
1647
1648         if args and len(args) > 1:
1649             print >>sys.stderr, 'getting results for more than one project is not supported'
1650             return 2
1651             
1652         if opts.project:
1653             project = opts.project
1654             apiurl = conf.config['apiurl']
1655         else:
1656             if args:
1657                 wd = args[0]
1658             else:
1659                 wd = os.curdir
1660
1661             project = store_read_project(wd)
1662             apiurl = store_read_apiurl(wd)
1663
1664         print '\n'.join(get_prj_results(apiurl, project, hide_legend=opts.hide_legend, csv=opts.csv, status_filter=opts.status_filter, name_filter=opts.name_filter))
1665
1666
1667     @cmdln.option('-q', '--hide-legend', action='store_true',
1668                         help='hide the legend')
1669     @cmdln.option('-c', '--csv', action='store_true',
1670                         help='csv output')
1671     @cmdln.option('-s', '--status-filter', metavar='STATUS',
1672                         help='show only packages with buildstatus STATUS (see legend)')
1673     @cmdln.option('-n', '--name-filter', metavar='EXPR',
1674                         help='show only packages whose names match EXPR')
1675
1676     def do_rprjresults(self, subcmd, opts, prj):
1677         """${cmd_name}: Shows project-wide build results of remote Projects
1678         
1679         Examples:
1680
1681         osc rprjresults <remote project name>
1682
1683         ${cmd_usage}
1684         ${cmd_option_list}
1685         """
1686
1687         apiurl = conf.config['apiurl']
1688
1689         print '\n'.join(get_prj_results(apiurl, prj, hide_legend=opts.hide_legend, csv=opts.csv, status_filter=opts.status_filter, name_filter=opts.name_filter))
1690
1691                 
1692     @cmdln.alias('bl')
1693     def do_buildlog(self, subcmd, opts, platform, arch):
1694         """${cmd_name}: Shows the build log of a package
1695
1696         Shows the log file of the build of a package. Can be used to follow the
1697         log while it is being written.
1698         Needs to be called from within a package directory.
1699
1700         The arguments PLATFORM and ARCH are the first two columns in the 'osc
1701         results' output.
1702
1703         ${cmd_usage}
1704         ${cmd_option_list}
1705         """
1706
1707         wd = os.curdir
1708         package = store_read_package(wd)
1709         project = store_read_project(wd)
1710         apiurl = store_read_apiurl(wd)
1711
1712         print_buildlog(apiurl, project, package, platform, arch)
1713
1714
1715     @cmdln.alias('rbl')
1716     def do_remotebuildlog(self, subcmd, opts, *args):
1717         """${cmd_name}: Shows the build log of a package
1718
1719         Shows the log file of the build of a package. Can be used to follow the
1720         log while it is being written.
1721
1722         usage:
1723             osc remotebuildlog project package platform arch
1724             or
1725             osc remotebuildlog project/package/platform/arch
1726         ${cmd_option_list}
1727         """
1728         args = slash_split(args)
1729         if len(args) < 4:
1730             raise oscerr.WrongArgs('Too few arguments.')
1731         elif len(args) > 4:
1732             raise oscerr.WrongArgs('Too many arguments.')
1733
1734         print_buildlog(conf.config['apiurl'], *args)
1735
1736
1737     @cmdln.option('-x', '--extra-pkgs', metavar='PAC', action='append',
1738                   help='Add this package when computing the buildinfo')
1739     def do_buildinfo(self, subcmd, opts, *args):
1740         """${cmd_name}: Shows the build info
1741
1742         Shows the build "info" which is used in building a package.
1743         This command is mostly used internally by the 'build' subcommand.
1744         It needs to be called from within a package directory.
1745
1746         The BUILD_DESCR argument is optional. BUILD_DESCR is a local RPM specfile
1747         or Debian "dsc" file. If specified, it is sent to the server, and the
1748         buildinfo will be based on it. If the argument is not supplied, the
1749         buildinfo is derived from the specfile which is currently on the source
1750         repository server.
1751
1752         The returned data is XML and contains a list of the packages used in
1753         building, their source, and the expanded BuildRequires.
1754
1755         The arguments PLATFORM and ARCH can be taken from first two columns
1756         of the 'osc repos' output.
1757
1758         usage: 
1759             osc buildinfo PLATFORM ARCH [BUILD_DESCR]
1760         ${cmd_option_list}
1761         """
1762
1763         wd = os.curdir
1764         package = store_read_package(wd)
1765         project = store_read_project(wd)
1766         apiurl = store_read_apiurl(wd)
1767
1768         if args is None or len(args) < 2:
1769             print 'Valid arguments for this package are:'
1770             print 
1771             self.do_repos(None, None)
1772             print
1773             raise oscerr.WrongArgs('Missing argument')
1774             
1775         platform = args[0]
1776         arch = args[1]
1777
1778         # were we given a specfile (third argument)?
1779         try:
1780             spec = open(args[2]).read()
1781         except IndexError:
1782             spec = None
1783         except IOError, e:
1784             print >>sys.stderr, e
1785             return 1
1786
1787         print ''.join(get_buildinfo(apiurl, 
1788                                     project, package, platform, arch, 
1789                                     specfile=spec, 
1790                                     addlist=opts.extra_pkgs))
1791
1792
1793     def do_buildconfig(self, subcmd, opts, platform, arch):
1794         """${cmd_name}: Shows the build config
1795
1796         Shows the build configuration which is used in building a package.
1797         This command is mostly used internally by the 'build' command.
1798         It needs to be called from inside a package directory.
1799
1800         The returned data is the project-wide build configuration in a format
1801         which is directly readable by the build script. It contains RPM macros
1802         and BuildRequires expansions, for example.
1803
1804         The arguments PLATFORM and ARCH can be taken first two columns in the
1805         'osc repos' output.
1806
1807         ${cmd_usage}
1808         ${cmd_option_list}
1809         """
1810
1811         wd = os.curdir
1812         package = store_read_package(wd)
1813         project = store_read_project(wd)
1814         apiurl = store_read_apiurl(wd)
1815
1816         print ''.join(get_buildconfig(apiurl, project, package, platform, arch))
1817
1818
1819     def do_repos(self, subcmd, opts, *args):
1820         """${cmd_name}: Shows the repositories which are defined for a package or a project
1821
1822         ARG, if specified, is a package working copy or a project dir.
1823
1824         examples: 1. osc repos                   # project/package = current dir
1825                   2. osc repos <packagedir>
1826                   3. osc repos <projectdir>
1827
1828         ${cmd_usage}
1829         ${cmd_option_list}
1830         """
1831
1832         args = parseargs(args)
1833
1834         for arg in args:
1835             for platform in get_repos_of_project(store_read_apiurl(arg), store_read_project(arg)):
1836                 print platform
1837
1838
1839     @cmdln.option('--clean', action='store_true',
1840                   help='Delete old build root before initializing it')
1841     @cmdln.option('--no-changelog', action='store_true',
1842                   help='don\'t update the package changelog from a changes file')
1843     @cmdln.option('--noinit', '--no-init', action='store_true',
1844                   help='Skip initialization of build root and start with build immediately.')
1845     @cmdln.option('--nochecks', '--no-checks', action='store_true',
1846                   help='Do not run post build checks on the resulting packages.')
1847     @cmdln.option('--no-verify', action='store_true',
1848                   help='Skip signature verification of packages used for build.')
1849     @cmdln.option('-p', '--prefer-pkgs', metavar='DIR', action='append',
1850                   help='Prefer packages from this directory when installing the build-root')
1851     @cmdln.option('-k', '--keep-pkgs', metavar='DIR', 
1852                   help='Save built packages into this directory')
1853     @cmdln.option('-x', '--extra-pkgs', metavar='PAC', action='append',
1854                   help='Add this package when installing the build-root')
1855     @cmdln.option('-j', '--jobs', metavar='N',
1856                   help='Compile with N jobs')
1857     @cmdln.option('--icecream', metavar='N',
1858                   help='use N parallel build jobs with icecream')
1859     @cmdln.option('--ccache', action='store_true',
1860                   help='use ccache to speed up rebuilds')
1861     @cmdln.option('--with', metavar='X', dest='_with',
1862                   help='enable feature X for build')
1863     @cmdln.option('--without', metavar='X',
1864                   help='disable feature X for build')
1865 # will not work as build.py does not support proper quoting
1866 #    @cmdln.option('--define', metavar='\'X Y\'',
1867 #                  help='define macro X with value Y')
1868     @cmdln.option('--userootforbuild', action='store_true',
1869                   help='Run build as root. The default is to build as '
1870                   'unprivileged user. Note that a line "# norootforbuild" '
1871                   'in the spec file will invalidate this option.')
1872     @cmdln.option('', '--local-package', action='store_true',
1873                   help='build a package which does not exist on the server')
1874     @cmdln.option('', '--alternative-project', metavar='PROJECT',
1875                   help='specify the build target project')
1876     @cmdln.option('-d', '--debuginfo', action='store_true',
1877                   help='also build debuginfo sub-packages')
1878     @cmdln.option('-b', '--baselibs', action='store_true',
1879                   help='Create -32bit/-64bit/-x86 rpms for other architectures')
1880     def do_build(self, subcmd, opts, *args):
1881         """${cmd_name}: Build a package on your local machine
1882
1883         You need to call the command inside a package directory, which should be a
1884         buildsystem checkout. (Local modifications are fine.)
1885
1886         The arguments PLATFORM and ARCH can be taken from first two columns
1887         of the 'osc repos' output. BUILD_DESCR is either a RPM spec file, or a
1888         Debian dsc file.
1889
1890         The command honours packagecachedir and build-root settings in .oscrc,
1891         if present. You may want to set su-wrapper = 'sudo' in .oscrc, and
1892         configure sudo with option NOPASSWD for /usr/bin/build.
1893
1894         If neither --clean nor --noinit is given, build will reuse an existing
1895         build-root again, removing unneeded packages and add missing ones. This
1896         is usually the fastest option.
1897
1898         If the package doesn't exist on the server please use the --local-package
1899         option.
1900         If the project of the package doesn't exist on the server please use the
1901         --alternative-project <alternative-project> option:
1902         Example:
1903             osc build [OPTS] --alternative-project openSUSE:10.3 standard i586 BUILD_DESCR
1904
1905         usage: 
1906             osc build [OPTS] PLATFORM ARCH BUILD_DESCR
1907             osc build [OPTS] PLATFORM (ARCH = hostarch, BUILD_DESCR is detected automatically)
1908             osc build [OPTS] ARCH (PLATFORM = build_platform (config option), BUILD_DESCR is detected automatically)
1909             osc build [OPTS] BUILD_DESCR (PLATFORM = build_platform (config option), ARCH = hostarch)
1910             osc build [OPTS] (PLATFORM = build_platform (config option), ARCH = hostarch, BUILD_DESCR is detected automatically)
1911
1912         # Note: 
1913         # Configuration can be overridden by envvars, e.g.  
1914         # OSC_SU_WRAPPER overrides the setting of su-wrapper. 
1915         # OSC_BUILD_ROOT overrides the setting of build-root.
1916         # OSC_PACKAGECACHEDIR overrides the setting of packagecachedir.
1917
1918         ${cmd_option_list}
1919         """
1920
1921         import osc.build
1922
1923         if not os.path.exists('/usr/lib/build/debtransform') \
1924                 and not os.path.exists('/usr/lib/lbuild/debtransform'):
1925             sys.stderr.write('Error: you need build.rpm with version 2007.3.12 or newer.\n')
1926             sys.stderr.write('See http://download.opensuse.org/repositories/openSUSE:/Tools/\n')
1927             return 1
1928
1929         if len(args) > 3:
1930             raise oscerr.WrongArgs('Too many arguments')
1931         
1932         arg_arch = arg_platform = arg_descr = None
1933         if len(args) < 3:
1934             for arg in args:
1935                 if arg.endswith('.spec') or arg.endswith('.dsc'):
1936                     arg_descr = arg
1937                 else:
1938                     if arg in osc.build.can_also_build.get(osc.build.hostarch, []) or \
1939                        arg in osc.build.hostarch:
1940                         arg_arch = arg
1941                     elif not arg_platform:
1942                         arg_platform = arg
1943                     else:
1944                         raise oscerr.WrongArgs('unexpected argument: \'%s\'' % arg)
1945         else:
1946             arg_platform, arg_arch, arg_descr = args
1947
1948         arg_arch = arg_arch or osc.build.hostarch
1949         arg_platform = arg_platform or conf.config['build_platform']
1950         descr = [ i for i in os.listdir('.') if i.endswith('.spec') or i.endswith('.dsc') ]
1951         # FIXME:
1952         # * request repos from server and select by build type. 
1953         if not arg_descr and len(descr) == 1:
1954             arg_descr = descr[0]
1955         elif not arg_descr:
1956             msg = 'Missing argument: build description (spec, dsc or kiwi file)'
1957             try:
1958                 p = Package('.')
1959                 if p.islink() and not p.isexpanded():
1960                     msg += ' (this package is not expanded - you might want to try osc up --expand)'
1961             except:
1962                 pass
1963             raise oscerr.WrongArgs(msg)
1964
1965         args = (arg_platform, arg_arch, arg_descr)
1966
1967         if opts.prefer_pkgs:
1968             for d in opts.prefer_pkgs:
1969                 if not os.path.isdir(d):
1970                     print >> sys.stderr, 'Preferred package location \'%s\' is not a directory' % d
1971                     return 1
1972
1973         if opts.keep_pkgs:
1974             if not os.path.isdir(opts.keep_pkgs):
1975                 print >> sys.stderr, 'Preferred save location \'%s\' is not a directory' % opts.keep_pkgs
1976                 return 1
1977         
1978         print 'Building %s for %s/%s' % (arg_descr, arg_platform, arg_arch)
1979         return osc.build.main(opts, args)
1980
1981             
1982
1983     @cmdln.alias('buildhist')
1984     def do_buildhistory(self, subcmd, opts, platform, arch):
1985         """${cmd_name}: Shows the build history of a package
1986
1987         The arguments PLATFORM and ARCH can be taken from first two columns
1988         of the 'osc repos' output.
1989
1990         ${cmd_usage}
1991         ${cmd_option_list}
1992         """
1993
1994         wd = os.curdir
1995         package = store_read_package(wd)
1996         project = store_read_project(wd)
1997         apiurl = store_read_apiurl(wd)
1998
1999         print '\n'.join(get_buildhistory(apiurl, project, package, platform, arch))
2000
2001     @cmdln.alias('jobhist')
2002     def do_jobhistory(self, subcmd, opts, platform, arch):
2003         """${cmd_name}: Shows the job history of a project
2004
2005         The arguments PLATFORM and ARCH can be taken from first two columns
2006         of the 'osc repos' output.
2007
2008         ${cmd_usage}
2009         ${cmd_option_list}
2010         """
2011
2012         wd = os.curdir
2013         project = store_read_project(wd)
2014         package = None
2015         try:
2016             package = store_read_package(wd)
2017         except:
2018             pass
2019         apiurl = store_read_apiurl(wd)
2020
2021         print_jobhistory(apiurl, project, package, platform, arch)
2022
2023
2024     @cmdln.option('-r', '--revision', metavar='rev',
2025                         help='show log of the specified revision')
2026     def do_log(self, subcmd, opts):
2027         """${cmd_name}: Shows the commit log of a package
2028
2029         ${cmd_usage}
2030         ${cmd_option_list}
2031         """
2032
2033         wd = os.curdir
2034         package = store_read_package(wd)
2035         project = store_read_project(wd)
2036         apiurl = store_read_apiurl(wd)
2037         rev, dummy = parseRevisionOption(opts.revision)
2038         if rev and not checkRevision(project, package, rev, apiurl):
2039             print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2040             sys.exit(1)
2041
2042         print '\n'.join(get_commitlog(apiurl, project, package, rev))
2043
2044
2045     @cmdln.option('-r', '--revision', metavar='rev',
2046                         help='show log of the specified revision')
2047     def do_rlog(self, subcmd, opts, prj, pkg):
2048         """${cmd_name}: Shows the commit log of a remote package
2049
2050         ${cmd_usage}
2051         ${cmd_option_list}
2052         """
2053
2054         apiurl = conf.config['apiurl']
2055         rev, dummy = parseRevisionOption(opts.revision)
2056         if rev and not checkRevision(prj, pkg, rev, apiurl):
2057             print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2058             sys.exit(1)
2059
2060         print '\n'.join(get_commitlog(apiurl, prj, pkg, rev))
2061
2062
2063     @cmdln.option('-f', '--failed', action='store_true',
2064                   help='rebuild all failed packages')
2065     def do_rebuildpac(self, subcmd, opts, *args):
2066         """${cmd_name}: Triggers package rebuilds
2067
2068         With the optional <repo> and <arch> arguments, the rebuild can be limited
2069         to a certain repository or architecture.
2070
2071         Note that it is normally NOT needed to kick off rebuilds like this, because
2072         they principally happen in a fully automatic way, triggered by source
2073         check-ins. In particular, the order in which packages are built is handled
2074         by the build service.
2075
2076         Note the --failed option, which can be used to rebuild all failed
2077         packages.
2078
2079         The arguments PLATFORM and ARCH are as in the first two columns of the
2080         'osc repos' output.
2081
2082         usage: 
2083             osc rebuildpac PROJECT [PACKAGE [PLATFORM [ARCH]]]
2084         ${cmd_option_list}
2085         """
2086
2087         args = slash_split(args)
2088
2089         if len(args) < 1:
2090             raise oscerr.WrongArgs('Missing argument.')
2091
2092         package = repo = arch = code = None
2093         project = args[0]
2094         if len(args) > 1:
2095             package = args[1]
2096         if len(args) > 2:
2097             repo = args[2]
2098         if len(args) > 3:
2099             arch = args[3]
2100
2101         if opts.failed:
2102             code = 'failed'
2103
2104         print rebuild(conf.config['apiurl'], project, package, repo, arch, code)
2105
2106
2107     def do_info(self, subcmd, opts, *args):
2108         """${cmd_name}: Print information about a working copy
2109
2110         Print information about each ARG (default: '.')
2111         ARG is a working-copy path.
2112
2113         ${cmd_usage}
2114         ${cmd_option_list}
2115         """
2116
2117         args = parseargs(args)
2118         pacs = findpacs(args)
2119
2120
2121         for p in pacs:
2122             print p.info()
2123
2124
2125     @cmdln.option('-a', '--arch', metavar='ARCH',
2126                         help='Abort builds for a specific architecture')
2127     @cmdln.option('-r', '--repo', metavar='REPO',
2128                         help='Abort builds for a specific repository')
2129     def do_abortbuild(self, subcmd, opts, *args):
2130         """${cmd_name}: Aborts the build of a certain project/package
2131         
2132         With the optional argument <package> you can specify a certain package
2133         otherwise all builds in the project will be cancelled.
2134         
2135         usage: 
2136             osc abortbuild [OPTS] PROJECT [PACKAGE]
2137         ${cmd_option_list}
2138         """
2139
2140         if len(args) < 1:
2141             raise oscerr.WrongArgs('Missing <project> argument.')
2142
2143         if len(args) == 2:
2144             package = args[1]
2145         else:
2146             package = None
2147
2148         print abortbuild(conf.config['apiurl'], args[0], package, opts.arch, opts.repo)
2149
2150
2151     @cmdln.option('-a', '--arch', metavar='ARCH',
2152                         help='Delete all binary packages for a specific architecture')
2153     @cmdln.option('-r', '--repo', metavar='REPO',
2154                         help='Delete all binary packages for a specific repository')
2155     @cmdln.option('--build-disabled', action='store_true',
2156                         help='Delete all binaries of packages for which the build is disabled')
2157     @cmdln.option('--build-failed', action='store_true',
2158                         help='Delete all binaries of packages for which the build failed')
2159     @cmdln.option('--broken', action='store_true',
2160                         help='Delete all binaries of packages for which the package source is bad')
2161     @cmdln.option('--expansion', action='store_true',
2162                         help='Delete all binaries of packages which have expansion errors')
2163     def do_wipebinaries(self, subcmd, opts, *args):
2164         """${cmd_name}: Delete all binary packages of a certain project/package
2165
2166         With the optional argument <package> you can specify a certain package
2167         otherwise all binary packages in the project will be deleted.
2168
2169         usage: 
2170             osc wipebinaries [OPTS] PROJECT [PACKAGE]
2171         ${cmd_option_list}
2172         """
2173         
2174         args = slash_split(args)
2175
2176         if len(args) < 1:
2177             raise oscerr.WrongArgs('Missing <project> argument.')
2178         if len(args) > 2:
2179             raise oscerr.WrongArgs('Wrong number of arguments.')
2180
2181         if len(args) == 2:
2182             package = args[1]
2183         else:
2184             package = None
2185
2186         codes = []
2187         if opts.build_disabled:
2188             codes.append('disabled')
2189         if opts.build_failed:
2190             codes.append('failed')
2191         if opts.broken:
2192             codes.append('broken')
2193         if opts.expansion:
2194             codes.append('expansion error')
2195
2196         if len(codes) == 0:
2197             codes.append(None)
2198
2199         # make a new request for each code= parameter
2200         for code in codes:
2201             print wipebinaries(conf.config['apiurl'], args[0], package, opts.arch, opts.repo, code)
2202
2203
2204     @cmdln.option('-q', '--quiet', action='store_true',
2205                   help='do not show downloading progress')
2206     @cmdln.option('-d', '--destdir', default='.', metavar='DIR',
2207                   help='destination directory')
2208     @cmdln.option('--sources', action="store_true",
2209                   help='also fetch source packages')
2210     def do_getbinaries(self, subcmd, opts, project, package, repository, architecture):
2211         """${cmd_name}: Download binaries to a local directory
2212
2213         This command downloads packages directly from the api server. 
2214         Thus, it directly accesses the packages that are used for building
2215         others even when they are not "published" yet.
2216
2217         ${cmd_usage}
2218         ${cmd_option_list}
2219         """
2220
2221         # Get package list
2222         binaries = get_binarylist(conf.config['apiurl'], 
2223                                    project, repository, architecture, 
2224                                    package = package, verbose=True)
2225
2226         if not os.path.isdir(opts.destdir):
2227             print "Creating %s" % opts.destdir
2228             os.makedirs(opts.destdir, 0755)
2229
2230         if binaries == [ ]:
2231             sys.exit('no binaries found. Either the package does not '
2232                      'exist or no binaries have been built.')
2233
2234         for binary in binaries:
2235
2236             # skip source rpms
2237             if not opts.sources and binary.name.endswith('.src.rpm'):
2238                 continue
2239
2240             target_filename = '%s/%s' % (opts.destdir, binary.name)
2241
2242             if os.path.exists(target_filename):
2243                 st = os.stat(target_filename)
2244                 if st.st_mtime == binary.mtime and st.st_size == binary.size:
2245                     continue
2246
2247             get_binary_file(conf.config['apiurl'], 
2248                             project, 
2249                             repository, architecture, 
2250                             binary.name, 
2251                             package = package,
2252                             target_filename = target_filename,
2253                             target_mtime = binary.mtime,
2254                             progress_meter = not opts.quiet)
2255
2256
2257
2258     @cmdln.option('--repos-baseurl', action='store_true',
2259                         help='show base URLs of download repositories')
2260     @cmdln.option('-e', '--enable-exact', action='store_true',
2261                         help='show only exact matches')
2262     @cmdln.option('--package', action='store_true',
2263                         help='search for a package')
2264     @cmdln.option('--project', action='store_true',
2265                         help='search for a project')
2266     @cmdln.option('--title', action='store_true',
2267                         help='search for matches in the \'title\' element')
2268     @cmdln.option('--description', action='store_true',
2269                         help='search for matches in the \'description\' element')
2270     @cmdln.option('-v', '--verbose', action='store_true',
2271                         help='show more information')
2272     @cmdln.option('-i', '--involved', action='store_true',
2273                         help='show involved projects/packages')
2274     def do_search(self, subcmd, opts, *args):
2275         """${cmd_name}: Search for a project and/or package.
2276
2277         If no option is specified osc will search for projects and
2278         packages which contains the \'search term\' in their name,
2279         title or description.
2280
2281         usage:
2282             osc search \'search term\' <options>
2283         ${cmd_option_list}
2284         """
2285
2286         search_term = None
2287         if len(args) > 1:
2288             raise oscerr.WrongArgs('Too many arguments.')
2289         elif len(args) < 1 and not opts.involved:
2290             raise oscerr.WrongArgs('Too few arguments.')
2291         elif len(args) == 1:
2292             search_term = args[0]
2293
2294         if (opts.title or opts.description) and opts.involved:
2295             raise oscerr.WrongArgs('Sorry, the options \'--title\' and/or \'--description\' ' \
2296                                    'plus \'--involved\' are mutual exclusive')
2297
2298         search_list = []
2299         search_for = []
2300         if opts.title:
2301             search_list.append('title')
2302         if opts.description:
2303             search_list.append('description')
2304         if opts.package:
2305             search_list.append('@name')
2306             search_for.append('package')
2307         if opts.project:
2308             search_list.append('@name')
2309             search_for.append('project')
2310         if opts.involved:
2311             search_list = [ 'person/@userid' ]
2312             search_term = search_term or conf.get_apiurl_usr(conf.config['apiurl'])
2313             opts.enable_exact = True
2314
2315         if not search_list:
2316             search_list = ['title', 'description', '@name']
2317         if not search_for:
2318             search_for = [ 'project', 'package' ]
2319         for kind in search_for:
2320             result = search(conf.config['apiurl'], set(search_list), kind, search_term, opts.verbose, opts.enable_exact, opts.repos_baseurl)
2321             if result:
2322                 if kind == 'package':
2323                     headline = [ '# Package', '# Project' ]
2324                 else:
2325                     headline = [ '# Project' ]
2326                 if opts.verbose:
2327                     headline.append('# Title')
2328                 if opts.repos_baseurl:
2329                     headline.append('# URL')
2330                 if len(search_for) > 1:
2331                     print '#' * 68
2332                 print 'matches for \'%s\' in %ss:\n' % (search_term, kind)
2333                 for row in build_table(len(headline), result, headline, 2):
2334                     print row
2335             else:
2336                print 'No matches found for \'%s\' in %ss' % (search_term, kind)
2337
2338
2339     @cmdln.option('-p', '--project', metavar='project',
2340                         help='specify a project name')
2341     @cmdln.option('-n', '--name', metavar='name',
2342                         help='specify a package name')
2343     @cmdln.option('-t', '--title', metavar='title',
2344                         help='set a title')
2345     @cmdln.option('-d', '--description', metavar='description',
2346                         help='set the description of the package')
2347     @cmdln.option('',   '--delete-old-files', action='store_true',
2348                         help='delete existing files from the server')
2349     @cmdln.option('-c',   '--commit', action='store_true',
2350                         help='commit the new files')
2351     def do_importsrcpkg(self, subcmd, opts, srpm):
2352         """${cmd_name}: Import a new package from a src.rpm
2353
2354         A new package dir will be created inside the project dir
2355         (if no project is specified and the current working dir is a
2356         project dir the package will be created in this project). If
2357         the package does not exist on the server it will be created
2358         too otherwise the meta data of the existing package will be
2359         updated (<title /> and <description />).
2360         The src.rpm will be extracted into the package dir. The files
2361         won't be committed unless you explicitly pass the --commit switch.
2362
2363         SRPM is the path of the src.rpm in the local filesystem,
2364         or an URL.
2365
2366         ${cmd_usage}
2367         ${cmd_option_list}
2368         """
2369         import glob
2370
2371         if opts.delete_old_files and conf.config['do_package_tracking']:
2372             # IMHO the --delete-old-files option doesn't really fit into our
2373             # package tracking strategy
2374             print >>sys.stderr, '--delete-old-files is not supported anymore'
2375             print >>sys.stderr, 'when do_package_tracking is enabled'
2376             sys.exit(1)
2377
2378         if '://' in srpm:
2379             print 'trying to fetch', srpm
2380             import urlgrabber
2381             urlgrabber.urlgrab(srpm)
2382             srpm = os.path.basename(srpm)
2383
2384         srpm = os.path.abspath(srpm)
2385         if not os.path.isfile(srpm):
2386             print >>sys.stderr, 'file \'%s\' does not exist' % srpm
2387             sys.exit(1)
2388
2389         if opts.project:
2390             project_dir = opts.project
2391         else:
2392             project_dir = os.curdir
2393
2394         if conf.config['do_package_tracking']:
2395             project = Project(project_dir)
2396         else:
2397             project = store_read_project(project_dir)
2398
2399         rpm_data = data_from_rpm(srpm, 'Name:', 'Summary:', '%description', 'Url:')
2400         if rpm_data:
2401             title, pac, descr, url = ( v for k, v in rpm_data.iteritems() )
2402         else:
2403             title = pac = descr = url = ''
2404
2405         if opts.title:
2406             title = opts.title
2407         if opts.name:
2408             pac = opts.name
2409         if opts.description:
2410             descr = opts.description
2411         
2412         # title and description can be empty
2413         if not pac:
2414             print >>sys.stderr, 'please specify a package name with the \'--name\' option. ' \
2415                                 'The automatic detection failed'
2416             sys.exit(1)
2417
2418         olddir = os.getcwd()
2419         if conf.config['do_package_tracking']:
2420             if createPackageDir(os.path.join(project.dir, pac), project):
2421                 os.chdir(os.path.join(project.dir, pac))
2422             else:
2423                 sys.exit(1)
2424         else:
2425             if not os.path.exists(os.path.join(project_dir, pac)):
2426                 apiurl = store_read_apiurl(project_dir)
2427                 user = conf.get_apiurl_usr(apiurl)
2428                 data = meta_exists(metatype='pkg',
2429                                    path_args=(quote_plus(project), quote_plus(pac)),
2430                                    template_args=({
2431                                        'name': pac,
2432                                        'user': user}), apiurl=apiurl)
2433                 if data:
2434                     data = ET.fromstring(''.join(data))
2435                     data.find('title').text = title
2436                     data.find('description').text = ''.join(descr)
2437                     data.find('url').text = url
2438                     data = ET.tostring(data)
2439                 else:
2440                     print >>sys.stderr, 'error - cannot get meta data'
2441                     sys.exit(1)
2442                 edit_meta(metatype='pkg',
2443                           path_args=(quote_plus(project), quote_plus(pac)),
2444                           data = data, apiurl=apiurl)
2445                 os.mkdir(os.path.join(project_dir, pac))
2446                 os.chdir(os.path.join(project_dir, pac))
2447                 init_package_dir(apiurl, project, pac, os.path.join(project, pac))
2448             else:
2449                 print >>sys.stderr, 'error - local package already exists'
2450                 sys.exit(1)
2451
2452         unpack_srcrpm(srpm, os.getcwd())
2453         p = Package(os.getcwd())
2454         if len(p.filenamelist) == 0 and opts.commit:
2455             print 'Adding files to working copy...'
2456             addFiles(glob.glob('*'))
2457             if conf.config['do_package_tracking']:
2458                 os.chdir(olddir)
2459                 project.commit((pac, ))
2460             else:
2461                 p.update_datastructs()
2462                 p.commit()
2463         elif opts.commit and opts.delete_old_files:
2464             for file in p.filenamelist:
2465                 p.delete_remote_source_file(file)
2466             p.update_local_filesmeta()
2467             print 'Adding files to working copy...'
2468             addFiles(glob.glob('*'))
2469             p.update_datastructs()
2470             p.commit()
2471         else:
2472             print 'No files were committed to the server. Please ' \
2473                   'commit them manually.'
2474             print 'Package \'%s\' only imported locally' % pac
2475             sys.exit(1)
2476
2477         print 'Package \'%s\' imported successfully' % pac
2478
2479
2480     @cmdln.option('-m', '--method', default='GET', metavar='HTTP_METHOD',
2481                         help='specify HTTP method to use (GET|PUT|DELETE|POST)')
2482     @cmdln.option('-d', '--data', default=None, metavar='STRING',
2483                         help='specify string data for e.g. POST')
2484     @cmdln.option('-f', '--file', default=None, metavar='FILE',
2485                         help='specify filename for e.g. PUT or DELETE')
2486     @cmdln.option('-a', '--add-header', default=None, metavar='NAME STRING', 
2487                         nargs=2, action='append', dest='headers',
2488                         help='add the specified header to the request')
2489     def do_req(self, subcmd, opts, url):
2490         """${cmd_name}: Issue an arbitrary request to the API
2491
2492         Useful for testing.
2493
2494         URL can be specified either partially (only the path component), or fully
2495         with URL scheme and hostname ('http://...').
2496
2497         Note the global -A and -H options (see osc help).
2498
2499         Examples:
2500           osc req /source/home:user
2501           osc req -m PUT -f /etc/fstab source/home:user/test5/myfstab
2502
2503         ${cmd_usage}
2504         ${cmd_option_list}
2505         """
2506
2507         if not opts.method in ['GET', 'PUT', 'POST', 'DELETE']:
2508             sys.exit('unknown method %s' % opts.method)
2509
2510         if not url.startswith('http'):
2511             if not url.startswith('/'):
2512                 url = '/' + url
2513             url = conf.config['apiurl'] + url
2514
2515         if opts.headers:
2516             opts.headers = dict(opts.headers)
2517
2518         r = http_request(opts.method, 
2519                          url, 
2520                          data=opts.data, 
2521                          file=opts.file,
2522                          headers=opts.headers) 
2523
2524         out = r.read()
2525         sys.stdout.write(out)
2526
2527
2528     @cmdln.option('-e', '--email', action='store_true',
2529                   help='show email addresses instead of user names')
2530     @cmdln.option('-v', '--verbose', action='store_true',
2531                   help='show more information')
2532     @cmdln.option('-D', '--devel-project', metavar='devel_project',
2533                   help='define the project where this package is primarily developed')
2534     @cmdln.option('-a', '--add', metavar='user',
2535                   help='add a new maintainer')
2536     @cmdln.option('-d', '--delete', metavar='user',
2537                   help='delete a maintainer from a project or package')
2538     def do_maintainer(self, subcmd, opts, *args):
2539         """${cmd_name}: Show maintainers of a project/package
2540     
2541         To be used like this:
2542     
2543             osc maintainer PRJ <options>
2544         or 
2545             osc maintainer PRJ PKG <options>
2546     
2547         ${cmd_usage}
2548         ${cmd_option_list}
2549         """
2550     
2551         pac = None
2552         if len(args) == 1:
2553             m = show_project_meta(conf.config['apiurl'], args[0])
2554             prj = args[0]
2555         elif len(args) == 2:
2556             m = show_package_meta(conf.config['apiurl'], args[0], args[1])
2557             prj = args[0]
2558             pac = args[1]
2559         else:
2560             raise oscerr.WrongArgs('I need at least one argument.')
2561     
2562         maintainers = []
2563     
2564         tree = ET.parse(StringIO(''.join(m)))
2565         for person in tree.findall('person'):
2566             maintainers.append(person.get('userid'))
2567     
2568         if opts.email:
2569             emails = []
2570             for maintainer in maintainers:
2571                 user = get_user_data(conf.config['apiurl'], maintainer, 'email')
2572                 if user != None:
2573                     emails.append(''.join(user))
2574             print ', '.join(emails)
2575         elif opts.verbose:
2576             userdata = []
2577             for maintainer in maintainers:
2578                 user = get_user_data(conf.config['apiurl'], maintainer, 'realname', 'login', 'email')
2579                 if user != None:
2580                     for itm in user:
2581                         userdata.append(itm)
2582             for row in build_table(3, userdata, ['realname', 'userid', 'email\n']):
2583                 print row
2584         elif opts.add:
2585             addMaintainer(conf.config['apiurl'], prj, pac, opts.add)
2586         elif opts.delete:
2587             delMaintainer(conf.config['apiurl'], prj, pac, opts.delete)
2588         elif opts.devel_project:
2589             addDevelProject(conf.config['apiurl'], prj, pac, opts.devel_project)
2590
2591         else:
2592             print ', '.join(maintainers)
2593
2594
2595     @cmdln.option('-r', '--revision', metavar='rev',
2596                   help='print out the specified revision')
2597     def do_cat(self, subcmd, opts, *args):
2598         """${cmd_name}: Output the content of a file to standard output
2599
2600         Examples:
2601             osc cat project package file
2602             osc cat project/package/file
2603
2604         ${cmd_usage}
2605         ${cmd_option_list}
2606         """
2607
2608         args = slash_split(args)
2609         if len(args) != 3:
2610             raise oscerr.WrongArgs('Wrong number of arguments.')
2611         rev, dummy = parseRevisionOption(opts.revision)
2612
2613         import tempfile
2614         (fd, filename) = tempfile.mkstemp(prefix = 'osc_%s.' % args[2], dir = '/tmp')
2615
2616         get_source_file(conf.config['apiurl'], args[0], args[1], args[2],
2617                         targetfilename=filename, revision=rev)
2618
2619         if binary_file(filename):
2620             print >>sys.stderr, 'error - cannot display binary file \'%s\'' % args[2]
2621         else:
2622             for line in open(filename):
2623                 print line.rstrip('\n')
2624
2625         try:
2626             os.unlink(filename)
2627         except:
2628             pass
2629 # fini!
2630 ###############################################################################
2631         
2632     # load subcommands plugged-in locally
2633     plugin_dirs = ['/var/lib/osc-plugins', os.path.expanduser('~/.osc-plugins')]
2634     for plugin_dir in plugin_dirs:
2635         if os.path.isdir(plugin_dir):
2636             for extfile in os.listdir(plugin_dir):
2637                 if not extfile.endswith('.py'):
2638                     continue
2639                 exec open(os.path.join(plugin_dir, extfile))
2640
2641