1 # Copyright (C) 2006 Novell Inc. All rights reserved.
2 # This program is free software; it may be used, copied, modified
3 # and distributed under the terms of the GNU General Public Licence,
4 # either version 2, or version 3 (at your option).
11 import urlgrabber.progress
12 from optparse import SUPPRESS_HELP
14 MAN_HEADER = r""".TH %(ucname)s "1" "%(date)s" "%(name)s %(version)s" "User Commands"
16 %(name)s \- openSUSE build service command-line tool.
19 [\fIGLOBALOPTS\fR] \fISUBCOMMAND \fR[\fIOPTS\fR] [\fIARGS\fR...]
24 openSUSE build service command-line tool.
28 Type 'osc help <subcommand>' for more detailed help on a specific subcommand.
30 For additional information, see
31 * http://en.opensuse.org/Build_Service_Tutorial
32 * http://en.opensuse.org/Build_Service/CLI
34 You can modify osc commands, or roll you own, via the plugin API:
35 * http://en.opensuse.org/Build_Service/osc_plugins
37 osc was written by several authors. This man page is automatically generated.
40 class Osc(cmdln.Cmdln):
41 """Usage: osc [GLOBALOPTS] SUBCOMMAND [OPTS] [ARGS...]
42 or: osc help SUBCOMMAND
44 openSUSE build service command-line tool.
45 Type 'osc help <subcommand>' for help on a specific subcommand.
50 For additional information, see
51 * http://en.opensuse.org/Build_Service_Tutorial
52 * http://en.opensuse.org/Build_Service/CLI
54 You can modify osc commands, or roll you own, via the plugin API:
55 * http://en.opensuse.org/Build_Service/osc_plugins
60 man_header = MAN_HEADER
61 man_footer = MAN_FOOTER
63 def __init__(self, *args, **kwargs):
64 cmdln.Cmdln.__init__(self, *args, **kwargs)
65 cmdln.Cmdln.do_help.aliases.append('h')
67 def get_version(self):
68 return get_osc_version()
70 def get_optparser(self):
71 """this is the parser for "global" options (not specific to subcommand)"""
73 optparser = cmdln.CmdlnOptionParser(self, version=get_osc_version())
74 optparser.add_option('--debugger', action='store_true',
75 help='jump into the debugger before executing anything')
76 optparser.add_option('--post-mortem', action='store_true',
77 help='jump into the debugger in case of errors')
78 optparser.add_option('-t', '--traceback', action='store_true',
79 help='print call trace in case of errors')
80 optparser.add_option('-H', '--http-debug', action='store_true',
81 help='debug HTTP traffic')
82 optparser.add_option('-d', '--debug', action='store_true',
83 help='print info useful for debugging')
84 optparser.add_option('-A', '--apiurl', dest='apiurl',
86 help='specify URL to access API server at or an alias')
87 optparser.add_option('-c', '--config', dest='conffile',
89 help='specify alternate configuration file')
90 optparser.add_option('--no-keyring', action='store_true',
91 help='disable usage of desktop keyring system')
92 optparser.add_option('--no-gnome-keyring', action='store_true',
93 help='disable usage of GNOME Keyring')
94 optparser.add_option('-v', '--verbose', dest='verbose', action='count', default=0,
95 help='increase verbosity')
96 optparser.add_option('-q', '--quiet', dest='verbose', action='store_const', const=-1,
97 help='be quiet, not verbose')
101 def postoptparse(self, try_again = True):
102 """merge commandline options into the config"""
104 conf.get_config(override_conffile = self.options.conffile,
105 override_apiurl = self.options.apiurl,
106 override_debug = self.options.debug,
107 override_http_debug = self.options.http_debug,
108 override_traceback = self.options.traceback,
109 override_post_mortem = self.options.post_mortem,
110 override_no_keyring = self.options.no_keyring,
111 override_no_gnome_keyring = self.options.no_gnome_keyring,
112 override_verbose = self.options.verbose)
113 except oscerr.NoConfigfile, e:
114 print >>sys.stderr, e.msg
115 print >>sys.stderr, 'Creating osc configuration file %s ...' % e.file
118 config['user'] = raw_input('Username: ')
119 config['pass'] = getpass.getpass()
120 if self.options.no_keyring:
121 config['use_keyring'] = '0'
122 if self.options.no_gnome_keyring:
123 config['gnome_keyring'] = '0'
124 if self.options.apiurl:
125 config['apiurl'] = self.options.apiurl
127 conf.write_initial_config(e.file, config)
128 print >>sys.stderr, 'done'
129 if try_again: self.postoptparse(try_again = False)
130 except oscerr.ConfigMissingApiurl, e:
131 print >>sys.stderr, e.msg
133 user = raw_input('Username: ')
134 passwd = getpass.getpass()
135 conf.add_section(e.file, e.url, user, passwd)
136 if try_again: self.postoptparse(try_again = False)
138 self.options.verbose = conf.config['verbose']
139 self.download_progress = None
140 if conf.config.get('show_download_progress', False):
141 from meter import TextMeter
142 self.download_progress = TextMeter()
145 def get_cmd_help(self, cmdname):
146 doc = self._get_cmd_handler(cmdname).__doc__
147 doc = self._help_reindent(doc)
148 doc = self._help_preprocess(doc, cmdname)
149 doc = doc.rstrip() + '\n' # trim down trailing space
150 return self._str(doc)
153 # overridden from class Cmdln() to use config variables in help texts
154 def _help_preprocess(self, help, cmdname):
155 help = cmdln.Cmdln._help_preprocess(self, help, cmdname)
156 return help % conf.config
159 def do_init(self, subcmd, opts, project, package=None):
160 """${cmd_name}: Initialize a directory as working copy
162 Initialize an existing directory to be a working copy of an
163 (already existing) buildservice project/package.
165 (This is the same as checking out a package and then copying sources
166 into the directory. It does NOT create a new package. To create a
167 package, use 'osc meta pkg ... ...')
169 You wouldn't normally use this command.
171 To get a working copy of a package (e.g. for building it or working on
172 it, you would normally use the checkout command. Use "osc help
173 checkout" to get help for it.
182 init_project_dir(conf.config['apiurl'], os.curdir, project)
183 print 'Initializing %s (Project: %s)' % (os.curdir, project)
185 init_package_dir(conf.config['apiurl'], project, package, os.path.curdir)
186 print 'Initializing %s (Project: %s, Package: %s)' % (os.curdir, project, package)
192 @cmdln.option('-a', '--arch', metavar='ARCH',
193 help='specify architecture (only for binaries)')
194 @cmdln.option('-r', '--repo', metavar='REPO',
195 help='specify repository (only for binaries)')
196 @cmdln.option('-b', '--binaries', action='store_true',
197 help='list built binaries instead of sources')
198 @cmdln.option('-R', '--revision', metavar='REVISION',
199 help='specify revision (only for sources)')
200 @cmdln.option('-e', '--expand', action='store_true',
201 help='expand linked package (only for sources)')
202 @cmdln.option('-u', '--unexpand', action='store_true',
203 help='always work with unexpanded (source) packages')
204 @cmdln.option('-v', '--verbose', action='store_true',
205 help='print extra information')
206 @cmdln.option('-l', '--long', action='store_true', dest='verbose',
207 help='print extra information')
208 def do_list(self, subcmd, opts, *args):
209 """${cmd_name}: List sources or binaries on the server
211 Examples for listing sources:
212 ls # list all projects
213 ls PROJECT # list packages in a project
214 ls PROJECT PACKAGE # list source files of package of a project
215 ls PROJECT PACKAGE <file> # list <file> if this file exists
216 ls -v PROJECT PACKAGE # verbosely list source files of package
217 ls -l PROJECT PACKAGE # verbosely list source files of package
218 ll PROJECT PACKAGE # verbosely list source files of package
219 LL PROJECT PACKAGE # verbosely list source files of expanded link
221 With --verbose, the following fields will be shown for each item:
223 Revision number of the last commit
225 Date and time of the last commit
227 Examples for listing binaries:
228 ls -b PROJECT # list all binaries of a project
229 ls -b PROJECT -a ARCH # list ARCH binaries of a project
230 ls -b PROJECT -r REPO # list binaries in REPO
231 ls -b PROJECT PACKAGE REPO ARCH
234 ${cmd_name} [PROJECT [PACKAGE]]
235 ${cmd_name} -b [PROJECT [PACKAGE [REPO [ARCH]]]]
239 apiurl = conf.config['apiurl']
240 args = slash_split(args)
243 if subcmd == 'lL' or subcmd == 'LL':
257 if opts.repo != args[2]:
258 raise oscerr.WrongArgs("conflicting repos specified ('%s' vs '%s')"%(opts.repo, args[2]))
265 if not opts.binaries:
266 raise oscerr.WrongArgs('Too many arguments')
268 if opts.arch != args[3]:
269 raise oscerr.WrongArgs("conflicting archs specified ('%s' vs '%s')"%(opts.arch, args[3]))
274 if opts.binaries and opts.expand:
275 raise oscerr.WrongOptions('Sorry, --binaries and --expand are mutual exclusive.')
279 # ls -b toplevel doesn't make sense, so use info from
280 # current dir if available
283 if is_project_dir(dir):
284 project = store_read_project(dir)
285 apiurl = store_read_apiurl(dir)
286 elif is_package_dir(dir):
287 project = store_read_project(dir)
288 package = store_read_package(dir)
289 apiurl = store_read_apiurl(dir)
292 raise oscerr.WrongArgs('There are no binaries to list above project level.')
294 raise oscerr.WrongOptions('Sorry, the --revision option is not supported for binaries.')
298 if opts.repo and opts.arch:
299 repos.append(Repo(opts.repo, opts.arch))
300 elif opts.repo and not opts.arch:
301 for repo in get_repos_of_project(apiurl, project):
302 if repo.name == opts.repo:
304 elif opts.arch and not opts.repo:
305 for repo in get_repos_of_project(apiurl, project):
306 if repo.arch == opts.arch:
309 repos = get_repos_of_project(apiurl, project)
313 results.append((repo, get_binarylist(apiurl, project, repo.name, repo.arch, package=package, verbose=opts.verbose)))
315 for result in results:
318 print '%s/%s' % (result[0].name, result[0].arch)
323 print "%9d %s %-40s" % (f.size, shorttime(f.mtime), f.name)
329 elif not opts.binaries:
331 print '\n'.join(meta_get_project_list(conf.config['apiurl']))
335 if self.options.verbose:
336 print >>sys.stderr, 'Sorry, the --verbose option is not implemented for projects.'
338 raise oscerr.WrongOptions('Sorry, the --expand option is not implemented for projects.')
340 print '\n'.join(meta_get_packagelist(conf.config['apiurl'], project))
342 elif len(args) == 2 or len(args) == 3:
344 print_not_found = True
346 l = meta_get_filelist(conf.config['apiurl'],
349 verbose=opts.verbose,
351 revision=opts.revision)
354 if i.name == '_link':
356 out = [ '%s %7s %9d %s %s' % (i.md5, i.rev, i.size, shorttime(i.mtime), i.name) \
357 for i in l if not fname or fname == i.name ]
359 print_not_found = False
367 print_not_found = False
370 if opts.expand or opts.unexpand or link_seen == 0: break
371 m = show_files_meta(conf.config['apiurl'], project, package)
372 xml = ET.fromstring(''.join(m)).find('linkinfo')
373 print "# -> %s %s" % (xml.get('project'), xml.get('package'))
375 if fname and print_not_found:
376 print 'file \'%s\' does not exist' % fname
379 @cmdln.option('-f', '--force', action='store_true',
380 help='force generation of new patchinfo file')
381 @cmdln.option('--force-update', action='store_true',
382 help='drops away collected packages from an already built patch and let it collect again')
383 def do_patchinfo(self, subcmd, opts, *args):
384 """${cmd_name}: Generate and edit a patchinfo file.
386 A patchinfo file describes the packages for an update and the kind of
391 osc patchinfo PATCH_NAME
395 project_dir = localdir = os.getcwd()
396 if is_project_dir(localdir):
397 project = store_read_project(localdir)
398 apiurl = store_read_apiurl(localdir)
400 sys.exit('This command must be called in a checked out project.')
402 for p in meta_get_packagelist(apiurl, project):
403 if p.startswith("_patchinfo:"):
406 if opts.force or not patchinfo:
407 print "Creating initial patchinfo..."
408 query='cmd=createpatchinfo'
410 query += "&name=" + args[0]
411 url = makeurl(apiurl, ['source', project], query=query)
413 for p in meta_get_packagelist(apiurl, project):
414 if p.startswith("_patchinfo:"):
417 if not os.path.exists(project_dir + "/" + patchinfo):
418 checkout_package(apiurl, project, patchinfo, prj_dir=project_dir)
420 if sys.platform[:3] != 'win':
421 editor = os.getenv('EDITOR', default='vim')
423 editor = os.getenv('EDITOR', default='notepad')
424 subprocess.call('%s %s' % (editor, project_dir + "/" + patchinfo + "/_patchinfo"), shell=True)
427 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
428 help='affect only a given attribute')
429 @cmdln.option('--attribute-defaults', action='store_true',
430 help='include defined attribute defaults')
431 @cmdln.option('--attribute-project', action='store_true',
432 help='include project values, if missing in packages ')
433 @cmdln.option('-F', '--file', metavar='FILE',
434 help='read metadata from FILE, instead of opening an editor. '
435 '\'-\' denotes standard input. ')
436 @cmdln.option('-e', '--edit', action='store_true',
437 help='edit metadata')
438 @cmdln.option('-c', '--create', action='store_true',
439 help='create attribute without values')
440 @cmdln.option('-s', '--set', metavar='ATTRIBUTE_VALUES',
441 help='set attribute values')
442 @cmdln.option('--delete', action='store_true',
443 help='delete a pattern or attribute')
444 def do_meta(self, subcmd, opts, *args):
445 """${cmd_name}: Show meta information, or edit it
447 Show or edit build service metadata of type <prj|pkg|prjconf|user|pattern>.
449 This command displays metadata on buildservice objects like projects,
450 packages, or users. The type of metadata is specified by the word after
451 "meta", like e.g. "meta prj".
453 prj denotes metadata of a buildservice project.
454 prjconf denotes the (build) configuration of a project.
455 pkg denotes metadata of a buildservice package.
456 user denotes the metadata of a user.
457 pattern denotes installation patterns defined for a project.
459 To list patterns, use 'osc meta pattern PRJ'. An additional argument
460 will be the pattern file to view or edit.
462 With the --edit switch, the metadata can be edited. Per default, osc
463 opens the program specified by the environmental variable EDITOR with a
464 temporary file. Alternatively, content to be saved can be supplied via
465 the --file switch. If the argument is '-', input is taken from stdin:
466 osc meta prjconf home:user | sed ... | osc meta prjconf home:user -F -
468 When trying to edit a non-existing resource, it is created implicitly.
474 osc meta pkg PRJ PKG -e
475 osc meta attribute PRJ [PKG [SUBPACKAGE]] [--attribute ATTRIBUTE] [--create|--delete|--set [value_list]]
478 osc meta <prj|pkg|prjconf|user|pattern|attribute> ARGS...
479 osc meta <prj|pkg|prjconf|user|pattern|attribute> -e|--edit ARGS...
480 osc meta <prj|pkg|prjconf|user|pattern|attribute> -F|--file ARGS...
481 osc meta pattern --delete PRJ PATTERN
485 args = slash_split(args)
487 if not args or args[0] not in metatypes.keys():
488 raise oscerr.WrongArgs('Unknown meta type. Choose one of %s.' \
489 % ', '.join(metatypes))
495 min_args, max_args = 2, 2
496 elif cmd in ['pattern']:
497 min_args, max_args = 1, 2
498 elif cmd in ['attribute']:
499 min_args, max_args = 1, 3
501 min_args, max_args = 1, 1
502 if len(args) < min_args:
503 raise oscerr.WrongArgs('Too few arguments.')
504 if len(args) > max_args:
505 raise oscerr.WrongArgs('Too many arguments.')
512 project, package = args[0:2]
513 elif cmd == 'attribute':
519 if opts.attribute_project:
520 raise oscerr.WrongOptions('--attribute-project works only when also a package is given')
525 attributepath.append('source')
526 attributepath.append(project)
528 attributepath.append(package)
530 attributepath.append(subpackage)
531 attributepath.append('_attribute')
532 elif cmd == 'prjconf':
536 elif cmd == 'pattern':
542 # enforce pattern argument if needed
543 if opts.edit or opts.file:
544 raise oscerr.WrongArgs('A pattern file argument is required.')
547 if not opts.edit and not opts.file and not opts.delete and not opts.create and not opts.set:
549 sys.stdout.write(''.join(show_project_meta(conf.config['apiurl'], project)))
551 sys.stdout.write(''.join(show_package_meta(conf.config['apiurl'], project, package)))
552 elif cmd == 'attribute':
553 sys.stdout.write(''.join(show_attribute_meta(conf.config['apiurl'], project, package, subpackage, opts.attribute, opts.attribute_defaults, opts.attribute_project)))
554 elif cmd == 'prjconf':
555 sys.stdout.write(''.join(show_project_conf(conf.config['apiurl'], project)))
557 r = get_user_meta(conf.config['apiurl'], user)
559 sys.stdout.write(''.join(r))
560 elif cmd == 'pattern':
562 r = show_pattern_meta(conf.config['apiurl'], project, pattern)
564 sys.stdout.write(''.join(r))
566 r = show_pattern_metalist(conf.config['apiurl'], project)
568 sys.stdout.write('\n'.join(r) + '\n')
571 if opts.edit and not opts.file:
573 edit_meta(metatype='prj',
575 path_args=quote_plus(project),
578 'user': conf.config['user']}))
580 edit_meta(metatype='pkg',
582 path_args=(quote_plus(project), quote_plus(package)),
585 'user': conf.config['user']}))
586 elif cmd == 'prjconf':
587 edit_meta(metatype='prjconf',
589 path_args=quote_plus(project),
592 edit_meta(metatype='user',
594 path_args=(quote_plus(user)),
595 template_args=({'user': user}))
596 elif cmd == 'pattern':
597 edit_meta(metatype='pattern',
599 path_args=(project, pattern),
602 # create attribute entry
603 if (opts.create or opts.set) and cmd == 'attribute':
604 if not opts.attribute:
605 raise oscerr.WrongOptions('no attribute given to create')
608 opts.set = opts.set.replace('&', '&').replace('<', '<').replace('>', '>')
609 for i in opts.set.split(','):
610 values += '<value>%s</value>' % i
611 aname = opts.attribute.split(":")
612 d = '<attributes><attribute namespace=\'%s\' name=\'%s\' >%s</attribute></attributes>' % (aname[0], aname[1], values)
613 url = makeurl(conf.config['apiurl'], attributepath)
614 for data in streamfile(url, http_POST, data=d):
615 sys.stdout.write(data)
624 f = open(opts.file).read()
626 sys.exit('could not open file \'%s\'.' % opts.file)
629 edit_meta(metatype='prj',
632 path_args=quote_plus(project))
634 edit_meta(metatype='pkg',
637 path_args=(quote_plus(project), quote_plus(package)))
638 elif cmd == 'prjconf':
639 edit_meta(metatype='prjconf',
642 path_args=quote_plus(project))
644 edit_meta(metatype='user',
647 path_args=(quote_plus(user)))
648 elif cmd == 'pattern':
649 edit_meta(metatype='pattern',
652 path_args=(project, pattern))
657 path = metatypes[cmd]['path']
659 path = path % (project, pattern)
660 u = makeurl(conf.config['apiurl'], [path])
662 elif cmd == 'attribute':
663 if not opts.attribute:
664 raise oscerr.WrongOptions('no attribute given to create')
665 attributepath.append(opts.attribute)
666 u = makeurl(conf.config['apiurl'], attributepath)
667 for data in streamfile(u, http_DELETE):
668 sys.stdout.write(data)
670 raise oscerr.WrongOptions('The --delete switch is only for pattern metadata or attributes.')
673 @cmdln.option('-m', '--message', metavar='TEXT',
674 help='specify message TEXT')
675 @cmdln.option('-r', '--revision', metavar='REV',
676 help='for "create", specify a certain source revision ID (the md5 sum)')
677 @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
678 help='Superseding another request by this one')
679 @cmdln.option('--nodevelproject', action='store_true',
680 help='do not follow a defined devel project ' \
681 '(primary project where a package is developed)')
682 @cmdln.option('--cleanup', action='store_true',
683 help='remove package if submission gets accepted (default for home:<id>:branch projects)')
684 @cmdln.option('--no-cleanup', action='store_true',
685 help='never remove source package on accept, but update its content')
686 @cmdln.option('--no-update', action='store_true',
687 help='never touch source package on accept (will break source links)')
688 @cmdln.option('-d', '--diff', action='store_true',
689 help='show diff only instead of creating the actual request')
690 @cmdln.option('--yes', action='store_true',
691 help='proceed without asking.')
693 @cmdln.alias("submitreq")
694 @cmdln.alias("submitpac")
695 def do_submitrequest(self, subcmd, opts, *args):
696 """${cmd_name}: Create request to submit source into another Project
698 [See http://en.opensuse.org/Build_Service/Collaboration for information
701 See the "request" command for showing and modifing existing requests.
704 osc submitreq [OPTIONS]
705 osc submitreq [OPTIONS] DESTPRJ [DESTPKG]
706 osc submitreq [OPTIONS] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
710 src_update = conf.config['submitrequest_on_accept_action'] or None
711 # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
713 src_update = "cleanup"
714 elif opts.no_cleanup:
715 src_update = "update"
717 src_update = "noupdate"
719 args = slash_split(args)
721 # remove this block later again
722 oldcmds = ['create', 'list', 'log', 'show', 'decline', 'accept', 'delete', 'revoke']
723 if args and args[0] in oldcmds:
724 print "************************************************************************"
725 print "* WARNING: It looks that you are using this command with a *"
726 print "* deprecated syntax. *"
727 print "* Please run \"osc sr --help\" and \"osc rq --help\" *"
728 print "* to see the new syntax. *"
729 print "************************************************************************"
730 if args[0] == 'create':
736 raise oscerr.WrongArgs('Too many arguments.')
738 if len(args) > 0 and len(args) <= 2 and is_project_dir(os.getcwd()):
739 sys.exit('osc submitrequest from project directory is only working without target specs and for source linked files\n')
741 apiurl = conf.config['apiurl']
743 if len(args) == 0 and is_project_dir(os.getcwd()):
745 # submit requests for multiple packages are currently handled via multiple requests
746 # They could be also one request with multiple actions, but that avoids to accepts parts of it.
747 project = store_read_project(os.curdir)
748 apiurl = store_read_apiurl(os.curdir)
754 # loop via all packages for checking their state
755 for p in meta_get_packagelist(apiurl, project):
756 if p.startswith("_patchinfo:"):
759 # get _link info from server, that knows about the local state ...
760 u = makeurl(apiurl, ['source', project, p])
762 root = ET.parse(f).getroot()
763 linkinfo = root.find('linkinfo')
765 print "Package ", p, " is not a source link."
766 sys.exit("This is currently not supported.")
767 if linkinfo.get('error'):
768 print "Package ", p, " is a broken source link."
769 sys.exit("Please fix this first")
770 t = linkinfo.get('project')
772 if len(root.findall('entry')) > 1: # This is not really correct, but should work mostly
773 # Real fix is to ask the api if sources are modificated
774 # but there is no such call yet.
775 targetprojects.append(t)
777 print "Submitting package ", p
779 print " Skipping package ", p
781 print "Skipping package ", p, " since it is a source link pointing inside the project."
785 print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
786 print "\nEverything fine? Can we create the requests ? [y/n]"
787 if sys.stdin.read(1) != "y":
788 sys.exit("Aborted...")
790 # loop via all packages to do the action
792 result = create_submit_request(apiurl, project, p)
795 sys.exit("submit request creation failed")
796 sr_ids.append(result)
798 # create submit requests for all found patchinfos
802 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
805 for t in targetprojects:
806 s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \
807 (project, p, t, p, options_block)
811 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
812 (actionxml, cgi.escape(opts.message or ""))
813 u = makeurl(apiurl, ['request'], query='cmd=create')
814 f = http_POST(u, data=xml)
816 root = ET.parse(f).getroot()
817 sr_ids.append(root.get('id'))
819 print "Requests created: ",
822 sys.exit('Successfull finished')
825 # try using the working copy at hand
826 p = findpacs(os.curdir)[0]
827 src_project = p.prjname
830 if len(args) == 0 and p.islink():
831 dst_project = p.linkinfo.project
832 dst_package = p.linkinfo.package
834 dst_project = args[0]
836 dst_package = args[1]
838 dst_package = src_package
840 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
841 'Please provide it the target via commandline arguments.' % p.name)
843 modified = [i for i in p.filenamelist if p.status(i) != ' ' and p.status(i) != '?']
844 if len(modified) > 0:
845 print 'Your working copy has local modifications.'
846 repl = raw_input('Proceed without committing the local changes? (y|N) ')
850 # get the arguments from the commandline
851 src_project, src_package, dst_project = args[0:3]
853 dst_package = args[3]
855 dst_package = src_package
857 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
858 + self.get_cmd_help('request'))
860 if not opts.nodevelproject:
863 devloc = show_develproject(apiurl, dst_project, dst_package)
864 except urllib2.HTTPError:
865 print >>sys.stderr, """\
866 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
867 % (dst_project, dst_package)
871 dst_project != devloc and \
872 src_project != devloc:
874 A different project, %s, is defined as the place where development
875 of the package %s primarily takes place.
876 Please submit there instead, or use --nodevelproject to force direct submission.""" \
877 % (devloc, dst_package)
882 if opts.diff or not opts.message:
884 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
885 rdiff += server_diff(apiurl,
886 dst_project, dst_package, opts.revision,
887 src_project, src_package, None, True)
893 reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit')
894 user = conf.get_apiurl_usr(apiurl)
895 myreqs = [ i for i in reqs if i.state.who == user ]
898 print 'You already created the following submit request: %s.' % \
899 ', '.join([str(i.reqid) for i in myreqs ])
900 repl = raw_input('Supersede the old requests? (y/n/c) ')
901 if repl.lower() == 'c':
902 print >>sys.stderr, 'Aborting'
908 changes_re = re.compile(r'^--- .*\.changes ')
909 for line in rdiff.split('\n'):
910 if line.startswith('--- '):
911 if changes_re.match(line):
916 difflines.append(line)
917 opts.message = edit_message(footer=rdiff, template='\n'.join(parse_diff_for_commit_message('\n'.join(difflines))))
919 result = create_submit_request(apiurl,
920 src_project, src_package,
921 dst_project, dst_package,
922 opts.message, orev=opts.revision, src_update=src_update)
923 if repl.lower() == 'y':
925 change_request_state(apiurl, str(req.reqid), 'superseded',
926 'superseded by %s' % result, result)
929 r = change_request_state(conf.config['apiurl'],
930 opts.supersede, 'superseded', opts.message or '', result)
932 print 'created request id', result
935 @cmdln.option('-m', '--message', metavar='TEXT',
936 help='specify message TEXT')
938 @cmdln.alias("deletereq")
939 def do_deleterequest(self, subcmd, opts, *args):
940 """${cmd_name}: Create request to delete a package or project
944 osc deletereq [-m TEXT] PROJECT [PACKAGE]
948 args = slash_split(args)
951 raise oscerr.WrongArgs('Please specify at least a project.')
953 raise oscerr.WrongArgs('Too many arguments.')
955 apiurl = conf.config['apiurl']
963 opts.message = edit_message()
965 result = create_delete_request(apiurl, project, package, opts.message)
969 @cmdln.option('-m', '--message', metavar='TEXT',
970 help='specify message TEXT')
972 @cmdln.alias("changedevelreq")
973 def do_changedevelrequest(self, subcmd, opts, *args):
974 """${cmd_name}: Create request to change the devel package definition.
976 [See http://en.opensuse.org/Build_Service/Collaboration for information
979 See the "request" command for showing and modifing existing requests.
981 osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
985 raise oscerr.WrongArgs('Too many arguments.')
987 if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
989 devel_project = store_read_project(wd)
990 devel_package = package = store_read_package(wd)
991 apiurl = store_read_apiurl(wd)
992 project = conf.config['getpac_default_project']
995 raise oscerr.WrongArgs('Too few arguments.')
997 apiurl = conf.config['apiurl']
999 devel_project = args[2]
1002 devel_package = package
1004 devel_package = args[3]
1006 if not opts.message:
1008 footer=textwrap.TextWrapper(width = 66).fill(
1009 'please explain why you like to change the devel project of %s/%s to %s/%s'
1010 % (project,package,devel_project,devel_package))
1011 opts.message = edit_message(footer)
1013 result = create_change_devel_request(apiurl,
1014 devel_project, devel_package,
1020 @cmdln.option('-d', '--diff', action='store_true',
1021 help='generate a diff')
1022 @cmdln.option('-u', '--unified', action='store_true',
1023 help='output the diff in the unified diff format')
1024 @cmdln.option('-m', '--message', metavar='TEXT',
1025 help='specify message TEXT')
1026 @cmdln.option('-t', '--type', metavar='TYPE',
1027 help='limit to requests which contain a given action type (submit/delete/change_devel)')
1028 @cmdln.option('-a', '--all', action='store_true',
1029 help='all states. Same as\'-s all\'')
1030 @cmdln.option('-s', '--state', default='', # default is 'all' if no args given, 'new' otherwise
1031 help='only list requests in one of the comma separated given states (new/accepted/revoked/declined) or "all" [default=new, or all, if no args given]')
1032 @cmdln.option('-D', '--days', metavar='DAYS',
1033 help='only list requests in state "new" or changed in the last DAYS. [default=%(request_list_days)s]')
1034 @cmdln.option('-U', '--user', metavar='USER',
1035 help='same as -M, but for the specified USER')
1036 @cmdln.option('-b', '--brief', action='store_true', default=False,
1037 help='print output in list view as list subcommand')
1038 @cmdln.option('-M', '--mine', action='store_true',
1039 help='only show requests created by yourself')
1040 @cmdln.option('-B', '--bugowner', action='store_true',
1041 help='also show requests about packages where I am bugowner')
1042 @cmdln.option('-i', '--interactive', action='store_true',
1043 help='interactive review of request')
1044 @cmdln.option('--non-interactive', action='store_true',
1045 help='non-interactive review of request')
1046 @cmdln.option('--exclude-target-project', action='append',
1047 help='exclude target project from request list')
1048 @cmdln.option('--involved-projects', action='store_true',
1049 help='show all requests for project/packages where USER is involved')
1051 @cmdln.alias("review")
1052 def do_request(self, subcmd, opts, *args):
1053 """${cmd_name}: Show and modify requests
1055 [See http://en.opensuse.org/Build_Service/Collaboration for information
1058 This command shows and modifies existing requests. To create new requests
1059 you need to call one of the following:
1062 osc changedevelrequest
1063 To send low level requests to the buildservice API, use:
1066 This command has the following sub commands:
1068 "list" lists open requests attached to a project or package or person.
1069 Uses the project/package of the current directory if none of
1070 -M, -U USER, project/package are given.
1072 "log" will show the history of the given ID
1074 "show" will show the request itself, and generate a diff for review, if
1075 used with the --diff option. The keyword show can be omitted if the ID is numeric.
1077 "decline" will change the request state to "declined" and append a
1078 message that you specify with the --message option.
1080 "wipe" will permanently delete a request.
1082 "revoke" will set the request state to "revoked" and append a
1083 message that you specify with the --message option.
1085 "accept" will change the request state to "accepted" and will trigger
1086 the actual submit process. That would normally be a server-side copy of
1087 the source package to the target package.
1089 "checkout" will checkout the request's source package. This only works for "submit" requests.
1092 osc request list [-M] [-U USER] [-s state] [-D DAYS] [-t type] [-B] [PRJ [PKG]]
1094 osc request [show] [-d] [-b] ID
1095 osc request accept [-m TEXT] ID
1096 osc request decline [-m TEXT] ID
1097 osc request revoke [-m TEXT] ID
1099 osc request checkout/co ID
1100 osc review accept [-m TEXT] ID
1101 osc review decline [-m TEXT] ID
1105 args = slash_split(args)
1107 if opts.all and opts.state:
1108 raise oscerr.WrongOptions('Sorry, the options \'--all\' and \'--state\' ' \
1109 'are mutually exclusive.')
1110 if opts.mine and opts.user:
1111 raise oscerr.WrongOptions('Sorry, the options \'--user\' and \'--mine\' ' \
1112 'are mutually exclusive.')
1113 if opts.interactive and opts.non_interactive:
1114 raise oscerr.WrongOptions('Sorry, the options \'--interactive\' and ' \
1115 '\'--non-interactive\' are mutually exclusive')
1120 if opts.state == '':
1123 if opts.state == '':
1126 cmds = ['list', 'log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co', 'help']
1127 if not args or args[0] not in cmds:
1128 raise oscerr.WrongArgs('Unknown request action %s. Choose one of %s.' \
1129 % (args[0],', '.join(cmds)))
1135 return self.do_help(['help', 'request'])
1138 min_args, max_args = 1, 1
1139 elif cmd in ['list']:
1140 min_args, max_args = 0, 2
1142 min_args, max_args = 1, 1
1143 if len(args) < min_args:
1144 raise oscerr.WrongArgs('Too few arguments.')
1145 if len(args) > max_args:
1146 raise oscerr.WrongArgs('Too many arguments.')
1148 apiurl = conf.config['apiurl']
1155 elif not opts.mine and not opts.user:
1157 project = store_read_project(os.curdir)
1158 apiurl = store_read_apiurl(os.curdir)
1159 package = store_read_package(os.curdir)
1160 except oscerr.NoWorkingCopy:
1165 elif cmd in ['log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co']:
1170 states = ('new', 'accepted', 'revoked', 'declined')
1171 state_list = opts.state.split(',')
1172 if opts.state == 'all':
1173 state_list = ['all']
1175 for s in state_list:
1177 raise oscerr.WrongArgs('Unknown state \'%s\', try one of %s' % (s, ','.join(states)))
1180 who = conf.get_apiurl_usr(apiurl)
1184 state_list = ['all']
1186 ## FIXME -B not implemented!
1188 if (self.options.debug):
1189 print 'list: option --bugowner ignored: not impl.'
1191 if opts.involved_projects:
1192 who = who or conf.get_apiurl_usr(apiurl)
1193 results = get_user_projpkgs_request_list(apiurl, who, req_state=state_list,
1194 req_type=opts.type, exclude_projects=opts.exclude_target_project or [])
1196 results = get_request_list(apiurl, project, package, who,
1197 state_list, opts.type, opts.exclude_target_project or [])
1198 results.sort(reverse=True)
1200 days = opts.days or conf.config['request_list_days']
1207 since = time.strftime('%Y-%m-%dT%H:%M:%S',time.localtime(time.time()-days*24*3600))
1210 ## bs has received 2009-09-20 a new xquery compare() function
1211 ## which allows us to limit the list inside of get_request_list
1212 ## That would be much faster for coolo. But counting the remainder
1213 ## would not be possible with current xquery implementation.
1214 ## Workaround: fetch all, and filter on client side.
1216 ## FIXME: date filtering should become implemented on server side
1217 for result in results:
1218 if days == 0 or result.state.when > since or result.state.name == 'new':
1219 print result.list_view()
1223 print "There are %d requests older than %s days.\n" % (skipped, days)
1226 for l in get_request_log(conf.config['apiurl'], reqid):
1231 r = get_request(conf.config['apiurl'], reqid)
1234 elif (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive:
1235 return request_interactive_review(conf.config['apiurl'], r)
1238 # fixme: will inevitably fail if the given target doesn't exist
1241 print server_diff(conf.config['apiurl'],
1242 r.actions[0].dst_project, r.actions[0].dst_package, None,
1243 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified)
1244 except urllib2.HTTPError, e:
1245 e.osc_msg = 'Diff not possible'
1249 elif cmd == 'checkout' or cmd == 'co':
1250 r = get_request(conf.config['apiurl'], reqid)
1251 submits = [ i for i in r.actions if i.type == 'submit' ]
1252 if not len(submits):
1253 raise oscerr.WrongArgs('\'checkout\' only works for \'submit\' requests')
1254 checkout_package(conf.config['apiurl'], submits[0].src_project, submits[0].src_package, \
1255 submits[0].src_rev, expand_link=True, prj_dir=submits[0].src_project)
1258 if not opts.message:
1259 opts.message = edit_message()
1260 state_map = {'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked'}
1261 # Change review state only
1262 if subcmd == 'review':
1263 if cmd in ['accept', 'decline']:
1264 r = change_review_state(conf.config['apiurl'],
1265 reqid, state_map[cmd], conf.config['user'], '', opts.message or '')
1267 # Change state of entire request
1268 elif cmd in ['accept', 'decline', 'wipe', 'revoke']:
1269 r = change_request_state(conf.config['apiurl'],
1270 reqid, state_map[cmd], opts.message or '')
1273 # editmeta and its aliases are all depracated
1274 @cmdln.alias("editprj")
1275 @cmdln.alias("createprj")
1276 @cmdln.alias("editpac")
1277 @cmdln.alias("createpac")
1278 @cmdln.alias("edituser")
1279 @cmdln.alias("usermeta")
1281 def do_editmeta(self, subcmd, opts, *args):
1284 Obsolete command to edit metadata. Use 'meta' now.
1286 See the help output of 'meta'.
1290 print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
1291 print >>sys.stderr, 'See \'osc help meta\'.'
1292 #self.do_help([None, 'meta'])
1296 @cmdln.option('-r', '--revision', metavar='rev',
1297 help='use the specified revision.')
1298 @cmdln.option('-u', '--unset', action='store_true',
1299 help='remove revision in link, it will point always to latest revision')
1300 def do_setlinkrev(self, subcmd, opts, *args):
1301 """${cmd_name}: Updates a revision number in a source link.
1303 This command adds or updates a specified revision number in a source link.
1304 The current revision of the source is used, if no revision number is specified.
1308 osc setlinkrev PROJECT [PACKAGE]
1312 args = slash_split(args)
1313 apiurl = conf.config['apiurl']
1316 p = findpacs(os.curdir)[0]
1321 sys.exit('Local directory is no checked out source link package, aborting')
1322 elif len(args) == 2:
1325 elif len(args) == 1:
1328 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1329 + self.get_cmd_help('setlinkrev'))
1332 packages = [ package ]
1334 packages = meta_get_packagelist(apiurl, project)
1337 print "setting revision for package", p
1341 rev, dummy = parseRevisionOption(opts.revision)
1342 set_link_rev(apiurl, project, p, rev)
1345 def do_linktobranch(self, subcmd, opts, *args):
1346 """${cmd_name}: Convert a package containing a classic link with patch to a branch
1348 This command tells the server to convert a _link with or without a project.diff
1349 to a branch. This is a full copy with a _link file pointing to the branched place.
1352 osc linktobranch # can be used in checked out package
1353 osc linktobranch PROJECT PACKAGE
1357 args = slash_split(args)
1360 project = store_read_project(wd)
1361 package = store_read_package(wd)
1362 apiurl = store_read_apiurl(wd)
1363 update_local_dir = True
1365 raise oscerr.WrongArgs('Too few arguments (required none or two)')
1367 raise oscerr.WrongArgs('Too many arguments (required none or two)')
1369 apiurl = conf.config['apiurl']
1372 update_local_dir = False
1375 link_to_branch(apiurl, project, package)
1376 if update_local_dir:
1378 pac.update(rev=pac.latest_rev())
1381 @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'],
1382 help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.')
1383 @cmdln.option('-c', '--current', action='store_true',
1384 help='link fixed against current revision.')
1385 @cmdln.option('-r', '--revision', metavar='rev',
1386 help='link the specified revision.')
1387 @cmdln.option('-f', '--force', action='store_true',
1388 help='overwrite an existing link file if it is there.')
1389 @cmdln.option('-d', '--disable-publish', action='store_true',
1390 help='disable publishing of the linked package')
1391 def do_linkpac(self, subcmd, opts, *args):
1392 """${cmd_name}: "Link" a package to another package
1394 A linked package is a clone of another package, but plus local
1395 modifications. It can be cross-project.
1397 The DESTPAC name is optional; the source packages' name will be used if
1400 Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
1402 To add a patch, add the patch as file and add it to the _link file.
1403 You can also specify text which will be inserted at the top of the spec file.
1405 See the examples in the _link file.
1408 osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1412 args = slash_split(args)
1414 if not args or len(args) < 3:
1415 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1416 + self.get_cmd_help('linkpac'))
1418 rev, dummy = parseRevisionOption(opts.revision)
1420 src_project = args[0]
1421 src_package = args[1]
1422 dst_project = args[2]
1424 dst_package = args[3]
1426 dst_package = src_package
1428 if src_project == dst_project and src_package == dst_package:
1429 raise oscerr.WrongArgs('Error: source and destination are the same.')
1431 if src_project == dst_project and not opts.cicount:
1432 # in this case, the user usually wants to build different spec
1433 # files from the same source
1434 opts.cicount = "copy"
1437 rev = show_upstream_rev(conf.config['apiurl'], src_project, src_package)
1439 if rev and not checkRevision(src_project, src_package, rev):
1440 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
1443 link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish)
1445 @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]',
1446 help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]')
1447 @cmdln.option('-d', '--disable-publish', action='store_true',
1448 help='disable publishing of the aggregated package')
1449 def do_aggregatepac(self, subcmd, opts, *args):
1450 """${cmd_name}: "Aggregate" a package to another package
1452 Aggregation of a package means that the build results (binaries) of a
1453 package are basically copied into another project.
1454 This can be used to make packages available from building that are
1455 needed in a project but available only in a different project. Note
1456 that this is done at the expense of disk space. See
1457 http://en.opensuse.org/Build_Service/Tips_and_Tricks#_link_and__aggregate
1458 for more information.
1460 The DESTPAC name is optional; the source packages' name will be used if
1464 osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1468 args = slash_split(args)
1470 if not args or len(args) < 3:
1471 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1472 + self.get_cmd_help('aggregatepac'))
1474 src_project = args[0]
1475 src_package = args[1]
1476 dst_project = args[2]
1478 dst_package = args[3]
1480 dst_package = src_package
1482 if src_project == dst_project and src_package == dst_package:
1483 raise oscerr.WrongArgs('Error: source and destination are the same.')
1487 for pair in opts.map_repo.split(','):
1488 src_tgt = pair.split('=')
1489 if len(src_tgt) != 2:
1490 raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo)
1491 repo_map[src_tgt[0]] = src_tgt[1]
1493 aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish)
1496 @cmdln.option('-c', '--client-side-copy', action='store_true',
1497 help='do a (slower) client-side copy')
1498 @cmdln.option('-k', '--keep-maintainers', action='store_true',
1499 help='keep original maintainers. Default is remove all and replace with the one calling the script.')
1500 @cmdln.option('-d', '--keep-develproject', action='store_true',
1501 help='keep develproject tag in the package metadata')
1502 @cmdln.option('-r', '--revision', metavar='rev',
1503 help='link the specified revision.')
1504 @cmdln.option('-t', '--to-apiurl', metavar='URL',
1505 help='URL of destination api server. Default is the source api server.')
1506 @cmdln.option('-m', '--message', metavar='TEXT',
1507 help='specify message TEXT')
1508 @cmdln.option('-e', '--expand', action='store_true',
1509 help='if the source package is a link then copy the expanded version of the link')
1510 def do_copypac(self, subcmd, opts, *args):
1511 """${cmd_name}: Copy a package
1513 A way to copy package to somewhere else.
1515 It can be done across buildservice instances, if the -t option is used.
1516 In that case, a client-side copy is implied.
1518 Using --client-side-copy always involves downloading all files, and
1519 uploading them to the target.
1521 The DESTPAC name is optional; the source packages' name will be used if
1525 osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1529 args = slash_split(args)
1531 if not args or len(args) < 3:
1532 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1533 + self.get_cmd_help('copypac'))
1535 src_project = args[0]
1536 src_package = args[1]
1537 dst_project = args[2]
1539 dst_package = args[3]
1541 dst_package = src_package
1543 src_apiurl = conf.config['apiurl']
1545 dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl)
1547 dst_apiurl = src_apiurl
1549 if src_project == dst_project and \
1550 src_package == dst_package and \
1551 src_apiurl == dst_apiurl:
1552 raise oscerr.WrongArgs('Source and destination are the same.')
1554 if src_apiurl != dst_apiurl:
1555 opts.client_side_copy = True
1557 rev, dummy = parseRevisionOption(opts.revision)
1560 comment = opts.message
1563 rev = show_upstream_rev(src_apiurl, src_project, src_package)
1564 comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev )
1566 r = copy_pac(src_apiurl, src_project, src_package,
1567 dst_apiurl, dst_project, dst_package,
1568 client_side_copy=opts.client_side_copy,
1569 keep_maintainers=opts.keep_maintainers,
1570 keep_develproject=opts.keep_develproject,
1577 @cmdln.option('-c', '--checkout', action='store_true',
1578 help='Checkout branched package afterwards ' \
1579 '(\'osc bco\' is a shorthand for this option)' )
1580 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
1581 help='Use this attribute to find affected packages (default is OBS:Maintained)')
1582 @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE',
1583 help='Use this attribute to find update projects (default is OBS:UpdateProject) ')
1584 def do_mbranch(self, subcmd, opts, *args):
1585 """${cmd_name}: Multiple branch of a package
1587 [See http://en.opensuse.org/Build_Service/Concepts/Maintenance for information
1590 This command is used for creating multiple links of defined version of a package
1591 in one project. This is esp. used for maintenance updates.
1593 The branched package will live in
1594 home:USERNAME:branches:ATTRIBUTE:PACKAGE
1595 if nothing else specified.
1598 osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ]
1601 args = slash_split(args)
1604 maintained_attribute = conf.config['maintained_attribute']
1605 maintained_update_project_attribute = conf.config['maintained_update_project_attribute']
1607 if not len(args) or len(args) > 2:
1608 raise oscerr.WrongArgs('Wrong number of arguments.')
1614 r = attribute_branch_pkg(conf.config['apiurl'], maintained_attribute, maintained_update_project_attribute, \
1618 print >>sys.stderr, 'ERROR: Attribute branch call came not back with a project.'
1621 print "Project " + r + " created."
1624 init_project_dir(conf.config['apiurl'], r, r)
1625 print statfrmt('A', r)
1628 for package in meta_get_packagelist(conf.config['apiurl'], r):
1630 checkout_package(conf.config['apiurl'], r, package, expand_link = True, prj_dir = r)
1632 print >>sys.stderr, 'Error while checkout package:\n', package
1634 if conf.config['verbose']:
1635 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1638 @cmdln.alias('branchco')
1640 @cmdln.alias('getpac')
1641 @cmdln.option('--nodevelproject', action='store_true',
1642 help='do not follow a defined devel project ' \
1643 '(primary project where a package is developed)')
1644 @cmdln.option('-c', '--checkout', action='store_true',
1645 help='Checkout branched package afterwards ' \
1646 '(\'osc bco\' is a shorthand for this option)' )
1647 @cmdln.option('-r', '--revision', metavar='rev',
1648 help='branch against a specific revision')
1649 @cmdln.option('-m', '--message', metavar='TEXT',
1650 help='specify message TEXT')
1651 def do_branch(self, subcmd, opts, *args):
1652 """${cmd_name}: Branch a package
1654 [See http://en.opensuse.org/Build_Service/Collaboration for information
1657 Create a source link from a package of an existing project to a new
1658 subproject of the requesters home project (home:branches:)
1660 The branched package will live in
1661 home:USERNAME:branches:PROJECT/PACKAGE
1662 if nothing else specified.
1664 With getpac or bco, the branched package will come from
1665 %(getpac_default_project)s
1666 if nothing else specified.
1669 osc branch SOURCEPROJECT SOURCEPACKAGE
1670 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT
1671 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE
1672 osc getpac SOURCEPACKAGE
1677 if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': opts.checkout = True
1678 args = slash_split(args)
1679 tproject = tpackage = None
1681 if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1:
1682 print >>sys.stderr, 'defaulting to %s/%s' % (conf.config['getpac_default_project'], args[0])
1683 # python has no args.unshift ???
1684 args = [ conf.config['getpac_default_project'] , args[0] ]
1686 if len(args) < 2 or len(args) > 4:
1687 raise oscerr.WrongArgs('Wrong number of arguments.')
1688 expected = 'home:%s:branches:%s' % (conf.config['user'], args[0])
1690 expected = tproject = args[2]
1694 exists, targetprj, targetpkg, srcprj, srcpkg = \
1695 branch_pkg(conf.config['apiurl'], args[0], args[1],
1696 nodevelproject=opts.nodevelproject, rev=opts.revision,
1697 target_project=tproject, target_package=tpackage,
1698 return_existing=opts.checkout, msg=opts.message or '')
1700 print >>sys.stderr, 'Using existing branch project: %s' % targetprj
1703 if not exists and (srcprj is not None and srcprj != args[0] or \
1704 srcprj is None and targetprj != expected):
1705 devloc = srcprj or targetprj
1706 if not srcprj and 'branches:' in targetprj:
1707 devloc = targetprj.split('branches:')[1]
1708 print '\nNote: The branch has been created of a different project,\n' \
1710 ' which is the primary location of where development for\n' \
1711 ' that package takes place.\n' \
1712 ' That\'s also where you would normally make changes against.\n' \
1713 ' A direct branch of the specified package can be forced\n' \
1714 ' with the --nodevelproject option.\n' % devloc
1716 package = tpackage or args[1]
1718 checkout_package(conf.config['apiurl'], targetprj, package,
1719 expand_link=True, prj_dir=targetprj)
1720 if conf.config['verbose']:
1721 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1724 if conf.get_configParser().get('general', 'apiurl') != conf.config['apiurl']:
1725 apiopt = '-A %s ' % conf.config['apiurl']
1726 print 'A working copy of the branched package can be checked out with:\n\n' \
1728 % (apiopt, targetprj, package)
1729 print_request_list(conf.config['apiurl'], args[0], args[1])
1731 print_request_list(conf.config['apiurl'], devloc, args[1])
1735 @cmdln.option('-f', '--force', action='store_true',
1736 help='deletes a package or an empty project')
1737 def do_rdelete(self, subcmd, opts, *args):
1738 """${cmd_name}: Delete a project or packages on the server.
1740 As a safety measure, project must be empty (i.e., you need to delete all
1741 packages first). If you are sure that you want to remove this project and all
1742 its packages use \'--force\' switch.
1745 osc rdelete -f PROJECT
1746 osc rdelete PROJECT PACKAGE [PACKAGE ...]
1751 args = slash_split(args)
1753 raise oscerr.WrongArgs('Missing argument.')
1759 # careful: if pkg is an empty string, the package delete request results
1760 # into a project delete request - which works recursively...
1762 delete_package(conf.config['apiurl'], prj, pkg)
1763 elif len(meta_get_packagelist(conf.config['apiurl'], prj)) >= 1 and not opts.force:
1764 print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
1765 'If you are sure that you want to remove this project and all its ' \
1766 'packages use the \'--force\' switch'
1769 delete_project(conf.config['apiurl'], prj)
1772 def do_deletepac(self, subcmd, opts, *args):
1773 print """${cmd_name} is obsolete !
1776 osc delete for checked out packages or projects
1778 osc rdelete for server side operations."""
1783 @cmdln.option('-f', '--force', action='store_true',
1784 help='deletes a project and its packages')
1785 def do_deleteprj(self, subcmd, opts, project):
1786 """${cmd_name} is obsolete !
1793 @cmdln.alias('metafromspec')
1794 @cmdln.option('', '--specfile', metavar='FILE',
1795 help='Path to specfile. (if you pass more than working copy this option is ignored)')
1796 def do_updatepacmetafromspec(self, subcmd, opts, *args):
1797 """${cmd_name}: Update package meta information from a specfile
1799 ARG, if specified, is a package working copy.
1805 args = parseargs(args)
1806 if opts.specfile and len(args) == 1:
1807 specfile = opts.specfile
1810 pacs = findpacs(args)
1812 p.read_meta_from_spec(specfile)
1813 p.update_package_meta()
1817 @cmdln.option('-c', '--change', metavar='rev',
1818 help='the change made by revision rev (like -r rev-1:rev).'
1819 'If rev is negative this is like -r rev:rev-1.')
1820 @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
1821 help='If rev1 is specified it will compare your working copy against '
1822 'the revision (rev1) on the server. '
1823 'If rev1 and rev2 are specified it will compare rev1 against rev2 '
1824 '(NOTE: changes in your working copy are ignored in this case)')
1825 @cmdln.option('-p', '--plain', action='store_true',
1826 help='output the diff in plain (not unified) diff format')
1827 def do_diff(self, subcmd, opts, *args):
1828 """${cmd_name}: Generates a diff
1830 Generates a diff, comparing local changes against the repository
1833 ARG, specified, is a filename to include in the diff.
1839 args = parseargs(args)
1840 pacs = findpacs(args)
1844 rev = int(opts.change)
1854 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1857 rev1, rev2 = parseRevisionOption(opts.revision)
1861 diff += ''.join(make_diff(pac, rev1))
1863 diff += server_diff(pac.apiurl, pac.prjname, pac.name, rev1,
1864 pac.prjname, pac.name, rev2, not opts.plain)
1869 @cmdln.option('--oldprj', metavar='OLDPRJ',
1870 help='project to compare against'
1871 ' (deprecated, use 3 argument form)')
1872 @cmdln.option('--oldpkg', metavar='OLDPKG',
1873 help='package to compare against'
1874 ' (deprecated, use 3 argument form)')
1875 @cmdln.option('-r', '--revision', metavar='N[:M]',
1876 help='revision id, where N = old revision and M = new revision')
1877 @cmdln.option('-p', '--plain', action='store_true',
1878 help='output the diff in plain (not unified) diff format')
1879 @cmdln.option('-c', '--change', metavar='rev',
1880 help='the change made by revision rev (like -r rev-1:rev). '
1881 'If rev is negative this is like -r rev:rev-1.')
1882 def do_rdiff(self, subcmd, opts, *args):
1883 """${cmd_name}: Server-side "pretty" diff of two packages
1885 Compares two packages (three or four arguments) or shows the
1886 changes of a specified revision of a package (two arguments)
1888 If no revision is specified the latest revision is used.
1890 Note that this command doesn't return a normal diff (which could be
1891 applied as patch), but a "pretty" diff, which also compares the content
1896 osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC]
1897 osc ${cmd_name} PROJECT PACKAGE
1901 args = slash_split(args)
1912 new_project = args[0]
1913 new_package = args[1]
1915 old_project = opts.oldprj
1917 old_package = opts.oldpkg
1918 elif len(args) == 3 or len(args) == 4:
1919 if opts.oldprj or opts.oldpkg:
1920 raise oscerr.WrongArgs('--oldpkg and --oldprj are only valid with two arguments')
1921 old_project = args[0]
1922 new_package = old_package = args[1]
1923 new_project = args[2]
1925 new_package = args[3]
1927 raise oscerr.WrongArgs('Wrong number of arguments')
1932 rev = int(opts.change)
1942 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1946 rev1, rev2 = parseRevisionOption(opts.revision)
1948 rdiff = server_diff(conf.config['apiurl'],
1949 old_project, old_package, rev1,
1950 new_project, new_package, rev2, not opts.plain)
1956 def do_install(self, subcmd, opts, *args):
1957 """${cmd_name}: install a package after build via zypper in -r
1959 Not implemented yet. Use osc repourls,
1960 select the url you best like (standard),
1961 chop off after the last /, this should work with zypper.
1968 args = slash_split(args)
1969 args = expand_proj_pack(args)
1972 ## if there is only one argument, and it ends in .ymp
1973 ## then fetch it, Parse XML to get the first
1974 ## metapackage.group.repositories.repository.url
1975 ## and construct zypper cmd's for all
1976 ## metapackage.group.software.item.name
1978 ## if args[0] is already an url, the use it as is.
1980 cmd = "sudo zypper -p http://download.opensuse.org/repositories/%s/%s --no-refresh -v in %s" % (re.sub(':',':/',args[0]), 'openSUSE_11.1', args[1])
1981 print self.do_install.__doc__
1982 print "Example: \n" + cmd
1985 def do_repourls(self, subcmd, opts, *args):
1986 """${cmd_name}: Shows URLs of .repo files
1988 Shows URLs on which to access the project .repos files (yum-style
1989 metadata) on download.opensuse.org.
1992 osc repourls [PROJECT]
1997 apiurl = conf.config['apiurl']
2001 elif len(args) == 0:
2002 project = store_read_project('.')
2003 apiurl = store_read_apiurl('.')
2005 raise oscerr.WrongArgs('Wrong number of arguments')
2007 # XXX: API should somehow tell that
2008 url_tmpl = 'http://download.opensuse.org/repositories/%s/%s/%s.repo'
2009 repos = get_repositories_of_project(apiurl, project)
2011 print url_tmpl % (project.replace(':', ':/'), repo, project)
2014 @cmdln.option('-r', '--revision', metavar='rev',
2015 help='checkout the specified revision. '
2016 'NOTE: if you checkout the complete project '
2017 'this option is ignored!')
2018 @cmdln.option('-e', '--expand-link', action='store_true',
2019 help='if a package is a link, check out the expanded '
2020 'sources (no-op, since this became the default)')
2021 @cmdln.option('-u', '--unexpand-link', action='store_true',
2022 help='if a package is a link, check out the _link file ' \
2023 'instead of the expanded sources')
2024 @cmdln.option('-c', '--current-dir', action='store_true',
2025 help='place PACKAGE folder in the current directory' \
2026 'instead of a PROJECT/PACKAGE directory')
2027 @cmdln.option('-s', '--source-service-files', action='store_true',
2028 help='server side generated files of source services' \
2029 'gets downloaded as well' )
2031 def do_checkout(self, subcmd, opts, *args):
2032 """${cmd_name}: Check out content from the repository
2034 Check out content from the repository server, creating a local working
2037 When checking out a single package, the option --revision can be used
2038 to specify a revision of the package to be checked out.
2040 When a package is a source link, then it will be checked out in
2041 expanded form. If --unexpand-link option is used, the checkout will
2042 instead produce the raw _link file plus patches.
2045 osc co PROJECT [PACKAGE] [FILE]
2046 osc co PROJECT # entire project
2047 osc co PROJECT PACKAGE # a package
2048 osc co PROJECT PACKAGE FILE # single file -> to current dir
2050 while inside a project directory:
2051 osc co PACKAGE # check out PACKAGE from project
2056 if opts.unexpand_link:
2060 if opts.source_service_files:
2061 service_files = True
2063 service_files = False
2065 args = slash_split(args)
2066 project = package = filename = None
2067 apiurl = conf.config['apiurl']
2069 project = project_dir = args[0]
2075 if args and len(args) == 1:
2076 localdir = os.getcwd()
2077 if is_project_dir(localdir):
2078 project = store_read_project(localdir)
2079 project_dir = localdir
2081 apiurl = store_read_apiurl(localdir)
2083 rev, dummy = parseRevisionOption(opts.revision)
2087 if rev and rev != "latest" and not checkRevision(project, package, rev):
2088 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2092 get_source_file(apiurl, project, package, filename, revision=rev, progress_obj=self.download_progress)
2095 if opts.current_dir:
2097 checkout_package(apiurl, project, package, rev, expand_link=expand_link, \
2098 prj_dir=project_dir, service_files=service_files, progress_obj=self.download_progress)
2099 print_request_list(apiurl, project, package)
2103 if sys.platform[:3] == 'win':
2104 prj_dir = prj_dir.replace(':', ';')
2105 if os.path.exists(prj_dir):
2106 sys.exit('osc: project \'%s\' already exists' % project)
2108 # check if the project does exist (show_project_meta will throw an exception)
2109 show_project_meta(apiurl, project)
2111 init_project_dir(apiurl, prj_dir, project)
2112 print statfrmt('A', prj_dir)
2115 for package in meta_get_packagelist(apiurl, project):
2117 checkout_package(apiurl, project, package, expand_link = expand_link, \
2118 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress)
2119 except oscerr.LinkExpandError, e:
2120 print >>sys.stderr, 'Link cannot be expanded:\n', e
2121 print >>sys.stderr, 'Use "osc repairlink" for fixing merge conflicts:\n'
2122 # check out in unexpanded form at least
2123 checkout_package(apiurl, project, package, expand_link = False, \
2124 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress)
2125 print_request_list(apiurl, project)
2128 raise oscerr.WrongArgs('Missing argument.\n\n' \
2129 + self.get_cmd_help('checkout'))
2132 @cmdln.option('-q', '--quiet', action='store_true',
2133 help='print as little as possible')
2134 @cmdln.option('-v', '--verbose', action='store_true',
2135 help='print extra information')
2137 def do_status(self, subcmd, opts, *args):
2138 """${cmd_name}: Show status of files in working copy
2140 Show the status of files in a local working copy, indicating whether
2141 files have been changed locally, deleted, added, ...
2143 The first column in the output specifies the status and is one of the
2144 following characters:
2145 ' ' no modifications
2150 '?' item is not under version control
2151 '!' item is missing (removed by non-osc command) or incomplete
2156 osc st file1 file2 ...
2159 osc status [OPTS] [PATH...]
2163 args = parseargs(args)
2165 # storage for single Package() objects
2167 # storage for a project dir ( { prj_instance : [ package objects ] } )
2170 # when 'status' is run inside a project dir, it should
2171 # stat all packages existing in the wc
2172 if is_project_dir(arg):
2173 prj = Project(arg, False)
2175 if conf.config['do_package_tracking']:
2177 for pac in prj.pacs_have:
2178 # we cannot create package objects if the dir does not exist
2179 if not pac in prj.pacs_broken:
2180 prjpacs[prj].append(os.path.join(arg, pac))
2182 pacpaths += [arg + '/' + n for n in prj.pacs_have]
2183 elif is_package_dir(arg):
2184 pacpaths.append(arg)
2185 elif os.path.isfile(arg):
2186 pacpaths.append(arg)
2188 msg = '\'%s\' is neither a project or a package directory' % arg
2189 raise oscerr.NoWorkingCopy, msg
2191 # process single packages
2192 lines = getStatus(findpacs(pacpaths), None, opts.verbose, opts.quiet)
2193 # process project dirs
2194 for prj, pacs in prjpacs.iteritems():
2195 lines += getStatus(findpacs(pacs), prj, opts.verbose, opts.quiet)
2197 print '\n'.join(lines)
2200 def do_add(self, subcmd, opts, *args):
2201 """${cmd_name}: Mark files to be added upon the next commit
2204 osc add FILE [FILE...]
2208 raise oscerr.WrongArgs('Missing argument.\n\n' \
2209 + self.get_cmd_help('add'))
2211 filenames = parseargs(args)
2215 def do_mkpac(self, subcmd, opts, *args):
2216 """${cmd_name}: Create a new package under version control
2219 osc mkpac new_package
2222 if not conf.config['do_package_tracking']:
2223 print >>sys.stderr, "to use this feature you have to enable \'do_package_tracking\' " \
2224 "in the [general] section in the configuration file"
2228 raise oscerr.WrongArgs('Wrong number of arguments.')
2230 createPackageDir(args[0])
2232 @cmdln.option('-r', '--recursive', action='store_true',
2233 help='If CWD is a project dir then scan all package dirs as well')
2235 def do_addremove(self, subcmd, opts, *args):
2236 """${cmd_name}: Adds new files, removes disappeared files
2238 Adds all files new in the local copy, and removes all disappeared files.
2240 ARG, if specified, is a package working copy.
2246 args = parseargs(args)
2248 for arg in arg_list:
2249 if is_project_dir(arg) and conf.config['do_package_tracking']:
2250 prj = Project(arg, False)
2251 for pac in prj.pacs_unvers:
2252 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2253 if os.path.isdir(pac_dir):
2254 addFiles([pac_dir], prj)
2255 for pac in prj.pacs_broken:
2256 if prj.get_state(pac) != 'D':
2257 prj.set_state(pac, 'D')
2258 print statfrmt('D', getTransActPath(os.path.join(prj.dir, pac)))
2260 for pac in prj.pacs_have:
2261 state = prj.get_state(pac)
2262 if state != None and state != 'D':
2263 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2264 args.append(pac_dir)
2266 prj.write_packages()
2267 elif is_project_dir(arg):
2268 print >>sys.stderr, 'osc: addremove is not supported in a project dir unless ' \
2269 '\'do_package_tracking\' is enabled in the configuration file'
2272 pacs = findpacs(args)
2274 p.todo = p.filenamelist + p.filenamelist_unvers
2276 for filename in p.todo:
2277 if os.path.isdir(filename):
2279 # ignore foo.rXX, foo.mine for files which are in 'C' state
2280 if os.path.splitext(filename)[0] in p.in_conflict:
2282 state = p.status(filename)
2285 # TODO: should ignore typical backup files suffix ~ or .orig
2287 print statfrmt('A', getTransActPath(os.path.join(p.dir, filename)))
2289 p.put_on_deletelist(filename)
2290 p.write_deletelist()
2291 os.unlink(os.path.join(p.storedir, filename))
2292 print statfrmt('D', getTransActPath(os.path.join(p.dir, filename)))
2297 @cmdln.alias('checkin')
2298 @cmdln.option('-m', '--message', metavar='TEXT',
2299 help='specify log message TEXT')
2300 @cmdln.option('-F', '--file', metavar='FILE',
2301 help='read log message from FILE')
2302 @cmdln.option('-f', '--force', default=False, action="store_true",
2303 help='force commit - do not tests a file list')
2304 def do_commit(self, subcmd, opts, *args):
2305 """${cmd_name}: Upload content to the repository server
2307 Upload content which is changed in your working copy, to the repository
2310 Optionally checks the state of a working copy, if found a file with
2311 unknown state, it requests an user input:
2312 * skip - don't change anything, just move to another file
2313 * remove - remove a file from dir
2314 * edit file list - edit filelist using EDITOR
2315 * commit - don't check anything and commit package
2316 * abort - abort commit - this is default value
2317 This can be supressed by check_filelist config item, or -f/--force
2318 command line option.
2321 osc ci # current dir
2323 osc ci file1 file2 ...
2329 args = parseargs(args)
2336 msg = open(opts.file).read()
2338 sys.exit('could not open file \'%s\'.' % opts.file)
2341 for arg in arg_list:
2342 if conf.config['do_package_tracking'] and is_project_dir(arg):
2343 Project(arg).commit(msg=msg)
2345 msg = edit_message()
2348 pacs = findpacs(args)
2350 if conf.config['check_filelist'] and not opts.force:
2351 check_filelist_before_commit(pacs)
2354 template = store_read_file(os.path.abspath('.'), '_commit_msg')
2355 # open editor for commit message
2356 # but first, produce status and diff to append to the template
2360 changed = getStatus([pac], quiet=True)
2363 diffs += ['\nDiff for working copy: %s' % pac.dir]
2364 diffs += make_diff(pac, 0)
2365 lines.extend(get_commit_message_template(pac))
2366 if template == None:
2367 template='\n'.join(lines)
2368 # if footer is empty, there is nothing to commit, and no edit needed.
2370 msg = edit_message(footer='\n'.join(footer), template=template)
2373 store_write_string(os.path.abspath('.'), '_commit_msg', msg)
2375 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2377 if conf.config['do_package_tracking'] and len(pacs) > 0:
2381 # it is possible to commit packages from different projects at the same
2382 # time: iterate over all pacs and put each pac to the right project in the dict
2384 path = os.path.normpath(os.path.join(pac.dir, os.pardir))
2385 if is_project_dir(path):
2386 pac_path = os.path.basename(os.path.normpath(pac.absdir))
2387 prj_paths.setdefault(path, []).append(pac_path)
2388 files[pac_path] = pac.todo
2390 single_paths.append(pac.dir)
2391 for prj, packages in prj_paths.iteritems():
2392 Project(prj).commit(tuple(packages), msg, files)
2393 for pac in single_paths:
2394 Package(pac).commit(msg)
2399 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2401 @cmdln.option('-r', '--revision', metavar='REV',
2402 help='update to specified revision (this option will be ignored '
2403 'if you are going to update the complete project or more than '
2405 @cmdln.option('-u', '--unexpand-link', action='store_true',
2406 help='if a package is an expanded link, update to the raw _link file')
2407 @cmdln.option('-e', '--expand-link', action='store_true',
2408 help='if a package is a link, update to the expanded sources')
2409 @cmdln.option('-s', '--source-service-files', action='store_true',
2410 help='Use server side generated sources instead of local generation.' )
2412 def do_update(self, subcmd, opts, *args):
2413 """${cmd_name}: Update a working copy
2418 If the current working directory is a package, update it.
2419 If the directory is a project directory, update all contained
2420 packages, AND check out newly added packages.
2422 To update only checked out packages, without checking out new
2423 ones, you might want to use "osc up *" from within the project
2427 Update the packages specified by the path argument(s)
2429 When --expand-link is used with source link packages, the expanded
2430 sources will be checked out. Without this option, the _link file and
2431 patches will be checked out. The option --unexpand-link can be used to
2432 switch back to the "raw" source with a _link file plus patch(es).
2438 if (opts.expand_link and opts.unexpand_link) \
2439 or (opts.expand_link and opts.revision) \
2440 or (opts.unexpand_link and opts.revision):
2441 raise oscerr.WrongOptions('Sorry, the options --expand-link, --unexpand-link and '
2442 '--revision are mutually exclusive.')
2444 if opts.source_service_files: service_files = True
2445 else: service_files = False
2447 args = parseargs(args)
2450 for arg in arg_list:
2451 if is_project_dir(arg):
2452 prj = Project(arg, progress_obj=self.download_progress)
2454 if conf.config['do_package_tracking']:
2455 prj.update(expand_link=opts.expand_link,
2456 unexpand_link=opts.unexpand_link)
2459 # if not tracking package, and 'update' is run inside a project dir,
2460 # it should do the following:
2461 # (a) update all packages
2462 args += prj.pacs_have
2463 # (b) fetch new packages
2464 prj.checkout_missing_pacs(expand_link = not opts.unexpand_link)
2466 print_request_list(prj.apiurl, prj.name)
2469 pacs = findpacs(args, progress_obj=self.download_progress)
2471 if opts.revision and len(args) == 1:
2472 rev, dummy = parseRevisionOption(opts.revision)
2473 if not checkRevision(pacs[0].prjname, pacs[0].name, rev, pacs[0].apiurl):
2474 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2481 print 'Updating %s' % p.name
2483 # FIXME: ugly workaround for #399247
2484 if opts.expand_link or opts.unexpand_link:
2485 if [ i for i in p.filenamelist+p.filenamelist_unvers if p.status(i) != ' ' and p.status(i) != '?']:
2486 print >>sys.stderr, 'osc: cannot expand/unexpand because your working ' \
2487 'copy has local modifications.\nPlease revert/commit them ' \
2492 if opts.expand_link and p.islink() and not p.isexpanded():
2493 if p.haslinkerror():
2495 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev)
2497 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev, linkrev="base")
2500 rev = p.linkinfo.xsrcmd5
2501 print 'Expanding to rev', rev
2502 elif opts.unexpand_link and p.islink() and p.isexpanded():
2503 print 'Unexpanding to rev', p.linkinfo.lsrcmd5
2504 rev = p.linkinfo.lsrcmd5
2505 elif p.islink() and p.isexpanded():
2506 rev = p.latest_rev()
2508 p.update(rev, service_files)
2509 if opts.unexpand_link:
2512 print_request_list(p.apiurl, p.prjname, p.name)
2515 @cmdln.option('-f', '--force', action='store_true',
2516 help='forces removal of entire package and its files')
2519 @cmdln.alias('remove')
2520 def do_delete(self, subcmd, opts, *args):
2521 """${cmd_name}: Mark files or package directories to be deleted upon the next 'checkin'
2524 cd .../PROJECT/PACKAGE
2525 osc delete FILE [...]
2527 osc delete PACKAGE [...]
2529 This command works on check out copies. Use "rdelete" for working on server
2530 side only. This is needed for removing the entire project.
2532 As a safety measure, projects must be empty (i.e., you need to delete all
2535 If you are sure that you want to remove a package and all
2536 its files use \'--force\' switch. Sometimes this also works without --force.
2542 raise oscerr.WrongArgs('Missing argument.\n\n' \
2543 + self.get_cmd_help('delete'))
2545 args = parseargs(args)
2546 # check if args contains a package which was removed by
2547 # a non-osc command and mark it with the 'D'-state
2550 if not os.path.exists(i):
2551 prj_dir, pac_dir = getPrjPacPaths(i)
2552 if is_project_dir(prj_dir):
2553 prj = Project(prj_dir, False)
2554 if i in prj.pacs_broken:
2555 if prj.get_state(i) != 'A':
2556 prj.set_state(pac_dir, 'D')
2558 prj.del_package_node(i)
2559 print statfrmt('D', getTransActPath(i))
2561 prj.write_packages()
2562 pacs = findpacs(args)
2566 prj_dir, pac_dir = getPrjPacPaths(p.absdir)
2567 if is_project_dir(prj_dir):
2568 if conf.config['do_package_tracking']:
2569 prj = Project(prj_dir, False)
2570 prj.delPackage(p, opts.force)
2572 print "WARNING: package tracking is disabled, operation skipped !"
2574 pathn = getTransActPath(p.dir)
2575 for filename in p.todo:
2576 ret, state = p.delete_file(filename, opts.force)
2578 print statfrmt('D', os.path.join(pathn, filename))
2581 sys.exit('\'%s\' is not under version control' % filename)
2582 elif state in ['A', 'M'] and not opts.force:
2583 sys.exit('\'%s\' has local modifications (use --force to remove this file)' % filename)
2586 def do_resolved(self, subcmd, opts, *args):
2587 """${cmd_name}: Remove 'conflicted' state on working copy files
2589 If an upstream change can't be merged automatically, a file is put into
2590 in 'conflicted' ('C') state. Within the file, conflicts are marked with
2591 special <<<<<<< as well as ======== and >>>>>>> lines.
2593 After manually resolving all conflicting parts, use this command to
2594 remove the 'conflicted' state.
2596 Note: this subcommand does not semantically resolve conflicts or
2597 remove conflict markers; it merely removes the conflict-related
2598 artifact files and allows PATH to be committed again.
2601 osc resolved FILE [FILE...]
2606 raise oscerr.WrongArgs('Missing argument.\n\n' \
2607 + self.get_cmd_help('resolved'))
2609 args = parseargs(args)
2610 pacs = findpacs(args)
2613 for filename in p.todo:
2614 print 'Resolved conflicted state of "%s"' % filename
2615 p.clear_from_conflictlist(filename)
2618 @cmdln.alias('platforms')
2619 def do_repositories(self, subcmd, opts, *args):
2620 """${cmd_name}: Shows available repositories
2624 Shows all available repositories/build targets
2626 2. osc repositories <project>
2627 Shows the configured repositories/build targets of a project
2635 print '\n'.join(get_repositories_of_project(conf.config['apiurl'], project))
2637 print '\n'.join(get_repositories(conf.config['apiurl']))
2641 def do_results_meta(self, subcmd, opts, *args):
2642 print "Command results_meta is obsolete. Please use: osc results --xml"
2646 @cmdln.option('-l', '--last-build', action='store_true',
2647 help='show last build results (succeeded/failed/unknown)')
2648 @cmdln.option('-r', '--repo', action='append', default = [],
2649 help='Show results only for specified repo(s)')
2650 @cmdln.option('-a', '--arch', action='append', default = [],
2651 help='Show results only for specified architecture(s)')
2652 @cmdln.option('', '--xml', action='store_true',
2653 help='generate output in XML (former results_meta)')
2654 def do_rresults(self, subcmd, opts, *args):
2655 print "Command rresults is obsolete. Running 'osc results' instead"
2656 self.do_results('results', opts, *args)
2660 @cmdln.option('-f', '--force', action='store_true', default=False,
2661 help="Don't ask and delete files")
2662 def do_rremove(self, subcmd, opts, project, package, *files):
2663 """${cmd_name}: Remove source files from selected package
2670 if not '/' in project:
2671 raise oscerr.WrongArgs("Missing operand, type osc help rremove for help")
2674 project, package = project.split('/')
2678 resp = raw_input("rm: remove source file `%s' from `%s/%s'? (yY|nN) " % (file, project, package))
2679 if resp not in ('y', 'Y'):
2682 delete_files(conf.config['apiurl'], project, package, (file, ))
2683 except urllib2.HTTPError, e:
2685 print >>sys.stderr, e
2687 if e.code in [ 400, 403, 404, 500 ]:
2688 if '<summary>' in body:
2689 msg = body.split('<summary>')[1]
2690 msg = msg.split('</summary>')[0]
2691 print >>sys.stderr, msg
2696 @cmdln.option('-l', '--last-build', action='store_true',
2697 help='show last build results (succeeded/failed/unknown)')
2698 @cmdln.option('-r', '--repo', action='append', default = [],
2699 help='Show results only for specified repo(s)')
2700 @cmdln.option('-a', '--arch', action='append', default = [],
2701 help='Show results only for specified architecture(s)')
2702 @cmdln.option('', '--xml', action='store_true',
2703 help='generate output in XML (former results_meta)')
2704 def do_results(self, subcmd, opts, *args):
2705 """${cmd_name}: Shows the build results of a package
2708 osc results (inside working copy)
2709 osc results remote_project remote_package
2714 args = slash_split(args)
2716 apiurl = conf.config['apiurl']
2719 if is_project_dir(wd):
2723 opts.hide_legend = None
2724 opts.name_filter = None
2725 opts.status_filter = None
2726 opts.vertical = None
2727 self.do_prjresults('prjresults', opts, *args)
2730 project = store_read_project(wd)
2731 package = store_read_package(wd)
2732 apiurl = store_read_apiurl(wd)
2734 raise oscerr.WrongArgs('Too few arguments (required none or two)')
2736 raise oscerr.WrongArgs('Too many arguments (required none or two)')
2745 func = show_results_meta
2748 print delim.join(func(apiurl, project, package, opts.last_build, opts.repo, opts.arch))
2750 # WARNING: this function is also called by do_results. You need to set a default there
2751 # as well when adding a new option!
2752 @cmdln.option('-q', '--hide-legend', action='store_true',
2753 help='hide the legend')
2754 @cmdln.option('-c', '--csv', action='store_true',
2756 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2757 help='show only packages with buildstatus STATUS (see legend)')
2758 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2759 help='show only packages whose names match EXPR')
2760 @cmdln.option('-a', '--arch', metavar='ARCH',
2761 help='show results only for specified architecture(s)')
2762 @cmdln.option('-r', '--repo', metavar='REPO',
2763 help='show results only for specified repo(s)')
2764 @cmdln.option('-V', '--vertical', action='store_true',
2765 help='list packages vertically instead horizontally')
2767 def do_prjresults(self, subcmd, opts, *args):
2768 """${cmd_name}: Shows project-wide build results
2771 osc prjresults (inside working copy)
2772 osc prjresults PROJECT
2778 apiurl = conf.config['apiurl']
2782 raise oscerr.WrongArgs('Wrong number of arguments.')
2785 project = store_read_project(wd)
2786 apiurl = store_read_apiurl(wd)
2788 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, repo=opts.repo, arch=opts.arch, vertical=opts.vertical))
2791 @cmdln.option('-q', '--hide-legend', action='store_true',
2792 help='hide the legend')
2793 @cmdln.option('-c', '--csv', action='store_true',
2795 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2796 help='show only packages with buildstatus STATUS (see legend)')
2797 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2798 help='show only packages whose names match EXPR')
2801 def do_rprjresults(self, subcmd, opts, *args):
2802 print "Command rprjresults is obsolete. Please use 'osc prjresults'"
2806 @cmdln.option('-s', '--start', metavar='START',
2807 help='get log starting from the offset')
2808 def do_buildlog(self, subcmd, opts, *args):
2809 """${cmd_name}: Shows the build log of a package
2811 Shows the log file of the build of a package. Can be used to follow the
2812 log while it is being written.
2813 Needs to be called from within a package directory.
2815 The arguments REPOSITORY and ARCH are the first two columns in the 'osc
2816 results' output. If the buildlog url is used buildlog command has the
2817 same behavior as remotebuildlog.
2819 ${cmd_usage} [REPOSITORY ARCH | BUILDLOGURL]
2823 repository = arch = None
2825 if len(args) == 1 and args[0].startswith('http'):
2826 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2829 package = store_read_package(wd)
2830 project = store_read_project(wd)
2831 apiurl = store_read_apiurl(wd)
2835 offset = int(opts.start)
2837 if not repository or not arch:
2841 repository = args[0]
2844 print_buildlog(apiurl, project, package, repository, arch, offset)
2847 def print_repos(self):
2850 if is_package_dir(wd):
2853 elif is_project_dir(wd):
2858 print 'Valid arguments for this %s are:' % str
2860 self.do_repos(None, None)
2862 raise oscerr.WrongArgs('Missing arguments')
2865 @cmdln.alias('rbuildlog')
2866 @cmdln.option('-s', '--start', metavar='START',
2867 help='get log starting from the offset')
2868 def do_remotebuildlog(self, subcmd, opts, *args):
2869 """${cmd_name}: Shows the build log of a package
2871 Shows the log file of the build of a package. Can be used to follow the
2872 log while it is being written.
2875 osc remotebuildlog project package repository arch
2877 osc remotebuildlog project/package/repository/arch
2879 osc remotebuildlog buildlogurl
2882 if len(args) == 1 and args[0].startswith('http'):
2883 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2885 args = slash_split(args)
2886 apiurl = conf.config['apiurl']
2888 raise oscerr.WrongArgs('Too few arguments.')
2890 raise oscerr.WrongArgs('Too many arguments.')
2892 project, package, repository, arch = args
2896 offset = int(opts.start)
2898 print_buildlog(apiurl, project, package, repository, arch, offset)
2901 @cmdln.option('-s', '--start', metavar='START',
2902 help='get log starting from offset')
2903 def do_localbuildlog(self, subcmd, opts, *args):
2904 """${cmd_name}: Shows the build log of a local buildchroot
2907 osc lbl [REPOSITORY ARCH]
2908 osc lbl # show log of newest last local build
2912 if conf.config['build-type']:
2913 # FIXME: raise Exception instead
2914 print >>sys.stderr, 'Not implemented for VMs'
2918 package = store_read_package('.')
2920 files = glob.glob(os.path.join(os.getcwd(), store, "_buildinfo-*"))
2923 raise oscerr.WrongArgs('No buildconfig found, please specify repo and arch manually.')
2927 if os.stat(f).st_mtime > os.stat(cfg).st_mtime:
2929 root = ET.parse(cfg).getroot()
2930 project = root.get("project")
2931 repo = root.get("repository")
2932 arch = root.find("arch").text
2933 elif len(args) == 2:
2934 project = store_read_project('.')
2935 package = store_read_package('.')
2939 if is_package_dir(os.curdir):
2941 raise oscerr.WrongArgs('Wrong number of arguments.')
2943 buildroot = os.environ.get('OSC_BUILD_ROOT', conf.config['build-root'])
2944 buildroot = buildroot % {'project': project, 'package': package,
2945 'repo': repo, 'arch': arch}
2948 offset = int(opts.start)
2949 logfile = os.path.join(buildroot, '.build.log')
2950 if not os.path.isfile(logfile):
2951 raise oscerr.OscIOError(None, 'logfile \'%s\' does not exist' % logfile)
2952 f = open(logfile, 'r')
2954 data = f.read(BUFSIZE)
2956 sys.stdout.write(data)
2957 data = f.read(BUFSIZE)
2961 def do_triggerreason(self, subcmd, opts, *args):
2962 """${cmd_name}: Show reason why a package got triggered to build
2964 The server decides when a package needs to get rebuild, this command
2965 shows the detailed reason for a package. A brief reason is also stored
2966 in the jobhistory, which can be accessed via "osc jobhistory".
2968 Trigger reasons might be:
2969 - new build (never build yet or rebuild manually forced)
2970 - source change (eg. on updating sources)
2971 - meta change (packages which are used for building have changed)
2972 - rebuild count sync (In case that it is configured to sync release numbers)
2974 usage in package or project directory:
2975 osc reason REPOSITORY ARCH
2976 osc reason PROJECT PACKAGE REPOSITORY ARCH
2981 args = slash_split(args)
2982 project = package = repository = arch = None
2987 if len(args) == 2: # 2
2988 if is_package_dir('.'):
2989 package = store_read_package(wd)
2991 raise oscerr.WrongArgs('package is not specified.')
2992 project = store_read_project(wd)
2993 apiurl = store_read_apiurl(wd)
2994 repository = args[0]
2996 elif len(args) == 4:
2997 apiurl = conf.config['apiurl']
3000 repository = args[2]
3003 raise oscerr.WrongArgs('Too many arguments.')
3005 print apiurl, project, package, repository, arch
3006 xml = show_package_trigger_reason(apiurl, project, package, repository, arch)
3007 root = ET.fromstring(xml)
3008 reason = root.find('explain').text
3010 if reason == "meta change":
3011 print "changed keys:"
3012 for package in root.findall('packagechange'):
3013 print " ", package.get('change'), package.get('key')
3016 # FIXME: the new osc syntax should allow to specify multiple packages
3017 # FIXME: the command should optionally use buildinfo data to show all dependencies
3018 @cmdln.alias('whatdependson')
3019 def do_dependson(self, subcmd, opts, *args):
3020 """${cmd_name}: Show the build dependencies
3022 The command dependson and whatdependson can be used to find out what
3023 will be triggered when a certain package changes.
3024 This is no guarantee, since the new build might have changed dependencies.
3026 dependson shows the build dependencies inside of a project, valid for a
3027 given repository and architecture.
3028 NOTE: to see all binary packages, which can trigger a build you need to
3029 refer the buildinfo, since this command shows only the dependencies
3030 inside of a project.
3032 The arguments REPOSITORY and ARCH can be taken from the first two columns
3033 of the 'osc repos' output.
3035 usage in package or project directory:
3036 osc dependson REPOSITORY ARCH
3037 osc whatdependson REPOSITORY ARCH
3040 osc dependson PROJECT [PACKAGE] REPOSITORY ARCH
3041 osc whatdependson PROJECT [PACKAGE] REPOSITORY ARCH
3046 args = slash_split(args)
3047 project = packages = repository = arch = reverse = None
3049 if len(args) < 2 and (is_package_dir('.') or is_project_dir('.')):
3053 raise oscerr.WrongArgs('Too many arguments.')
3055 if len(args) < 3: # 2
3056 if is_package_dir('.'):
3057 packages = [store_read_package(wd)]
3058 elif not is_project_dir('.'):
3059 raise oscerr.WrongArgs('Project and package is not specified.')
3060 project = store_read_project(wd)
3061 apiurl = store_read_apiurl(wd)
3062 repository = args[0]
3066 apiurl = conf.config['apiurl']
3068 repository = args[1]
3072 apiurl = conf.config['apiurl']
3074 packages = [args[1]]
3075 repository = args[2]
3078 if subcmd == 'whatdependson':
3081 xml = get_dependson(apiurl, project, repository, arch, packages, reverse)
3083 root = ET.fromstring(xml)
3084 for package in root.findall('package'):
3085 print package.get('name'), ":"
3086 for dep in package.findall('pkgdep'):
3090 @cmdln.option('-x', '--extra-pkgs', metavar='PAC', action='append',
3091 help='Add this package when computing the buildinfo')
3092 def do_buildinfo(self, subcmd, opts, *args):
3093 """${cmd_name}: Shows the build info
3095 Shows the build "info" which is used in building a package.
3096 This command is mostly used internally by the 'build' subcommand.
3097 It needs to be called from within a package directory.
3099 The BUILD_DESCR argument is optional. BUILD_DESCR is a local RPM specfile
3100 or Debian "dsc" file. If specified, it is sent to the server, and the
3101 buildinfo will be based on it.