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 repos = [repo for repo in get_repos_of_project(apiurl, project) if repo.name == opts.repo]
302 elif opts.arch and not opts.repo:
303 repos = [repo for repo in get_repos_of_project(apiurl, project) if repo.arch == opts.arch]
305 repos = get_repos_of_project(apiurl, project)
309 results.append((repo, get_binarylist(apiurl, project, repo.name, repo.arch, package=package, verbose=opts.verbose)))
311 for result in results:
314 print '%s/%s' % (result[0].name, result[0].arch)
319 print "%9d %s %-40s" % (f.size, shorttime(f.mtime), f.name)
325 elif not opts.binaries:
327 print '\n'.join(meta_get_project_list(conf.config['apiurl']))
331 if self.options.verbose:
332 print >>sys.stderr, 'Sorry, the --verbose option is not implemented for projects.'
334 raise oscerr.WrongOptions('Sorry, the --expand option is not implemented for projects.')
336 print '\n'.join(meta_get_packagelist(conf.config['apiurl'], project))
338 elif len(args) == 2 or len(args) == 3:
340 print_not_found = True
343 l = meta_get_filelist(conf.config['apiurl'],
346 verbose=opts.verbose,
349 link_seen = '_link' in l
351 out = [ '%s %7s %9d %s %s' % (i.md5, i.rev, i.size, shorttime(i.mtime), i.name) \
352 for i in l if not fname or fname == i.name ]
354 print_not_found = False
359 print_not_found = False
362 if opts.expand or opts.unexpand or not link_seen: break
363 m = show_files_meta(conf.config['apiurl'], project, package)
365 li.read(ET.fromstring(''.join(m)).find('linkinfo'))
367 raise oscerr.LinkExpandError(project, package, li.error)
368 project, package, rev = li.project, li.package, li.rev
370 print '# -> %s %s (%s)' % (project, package, rev)
372 print '# -> %s %s (latest)' % (project, package)
374 if fname and print_not_found:
375 print 'file \'%s\' does not exist' % fname
378 @cmdln.option('-f', '--force', action='store_true',
379 help='force generation of new patchinfo file')
380 @cmdln.option('--force-update', action='store_true',
381 help='drops away collected packages from an already built patch and let it collect again')
382 def do_patchinfo(self, subcmd, opts, *args):
383 """${cmd_name}: Generate and edit a patchinfo file.
385 A patchinfo file describes the packages for an update and the kind of
390 osc patchinfo PATCH_NAME
394 project_dir = localdir = os.getcwd()
395 if is_project_dir(localdir):
396 project = store_read_project(localdir)
397 apiurl = store_read_apiurl(localdir)
399 sys.exit('This command must be called in a checked out project.')
401 for p in meta_get_packagelist(apiurl, project):
402 if p.startswith("_patchinfo:"):
405 if opts.force or not patchinfo:
406 print "Creating initial patchinfo..."
407 query='cmd=createpatchinfo'
409 query += "&name=" + args[0]
410 url = makeurl(apiurl, ['source', project], query=query)
412 for p in meta_get_packagelist(apiurl, project):
413 if p.startswith("_patchinfo:"):
416 if not os.path.exists(project_dir + "/" + patchinfo):
417 checkout_package(apiurl, project, patchinfo, prj_dir=project_dir)
419 if sys.platform[:3] != 'win':
420 editor = os.getenv('EDITOR', default='vim')
422 editor = os.getenv('EDITOR', default='notepad')
423 subprocess.call('%s %s' % (editor, project_dir + "/" + patchinfo + "/_patchinfo"), shell=True)
426 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
427 help='affect only a given attribute')
428 @cmdln.option('--attribute-defaults', action='store_true',
429 help='include defined attribute defaults')
430 @cmdln.option('--attribute-project', action='store_true',
431 help='include project values, if missing in packages ')
432 @cmdln.option('-F', '--file', metavar='FILE',
433 help='read metadata from FILE, instead of opening an editor. '
434 '\'-\' denotes standard input. ')
435 @cmdln.option('-e', '--edit', action='store_true',
436 help='edit metadata')
437 @cmdln.option('-c', '--create', action='store_true',
438 help='create attribute without values')
439 @cmdln.option('-s', '--set', metavar='ATTRIBUTE_VALUES',
440 help='set attribute values')
441 @cmdln.option('--delete', action='store_true',
442 help='delete a pattern or attribute')
443 def do_meta(self, subcmd, opts, *args):
444 """${cmd_name}: Show meta information, or edit it
446 Show or edit build service metadata of type <prj|pkg|prjconf|user|pattern>.
448 This command displays metadata on buildservice objects like projects,
449 packages, or users. The type of metadata is specified by the word after
450 "meta", like e.g. "meta prj".
452 prj denotes metadata of a buildservice project.
453 prjconf denotes the (build) configuration of a project.
454 pkg denotes metadata of a buildservice package.
455 user denotes the metadata of a user.
456 pattern denotes installation patterns defined for a project.
458 To list patterns, use 'osc meta pattern PRJ'. An additional argument
459 will be the pattern file to view or edit.
461 With the --edit switch, the metadata can be edited. Per default, osc
462 opens the program specified by the environmental variable EDITOR with a
463 temporary file. Alternatively, content to be saved can be supplied via
464 the --file switch. If the argument is '-', input is taken from stdin:
465 osc meta prjconf home:user | sed ... | osc meta prjconf home:user -F -
467 When trying to edit a non-existing resource, it is created implicitly.
473 osc meta pkg PRJ PKG -e
474 osc meta attribute PRJ [PKG [SUBPACKAGE]] [--attribute ATTRIBUTE] [--create|--delete|--set [value_list]]
477 osc meta <prj|pkg|prjconf|user|pattern|attribute> ARGS...
478 osc meta <prj|pkg|prjconf|user|pattern|attribute> -e|--edit ARGS...
479 osc meta <prj|pkg|prjconf|user|pattern|attribute> -F|--file ARGS...
480 osc meta pattern --delete PRJ PATTERN
484 args = slash_split(args)
486 if not args or args[0] not in metatypes.keys():
487 raise oscerr.WrongArgs('Unknown meta type. Choose one of %s.' \
488 % ', '.join(metatypes))
494 min_args, max_args = 2, 2
495 elif cmd in ['pattern']:
496 min_args, max_args = 1, 2
497 elif cmd in ['attribute']:
498 min_args, max_args = 1, 3
500 min_args, max_args = 1, 1
501 if len(args) < min_args:
502 raise oscerr.WrongArgs('Too few arguments.')
503 if len(args) > max_args:
504 raise oscerr.WrongArgs('Too many arguments.')
511 project, package = args[0:2]
512 elif cmd == 'attribute':
518 if opts.attribute_project:
519 raise oscerr.WrongOptions('--attribute-project works only when also a package is given')
524 attributepath.append('source')
525 attributepath.append(project)
527 attributepath.append(package)
529 attributepath.append(subpackage)
530 attributepath.append('_attribute')
531 elif cmd == 'prjconf':
535 elif cmd == 'pattern':
541 # enforce pattern argument if needed
542 if opts.edit or opts.file:
543 raise oscerr.WrongArgs('A pattern file argument is required.')
546 if not opts.edit and not opts.file and not opts.delete and not opts.create and not opts.set:
548 sys.stdout.write(''.join(show_project_meta(conf.config['apiurl'], project)))
550 sys.stdout.write(''.join(show_package_meta(conf.config['apiurl'], project, package)))
551 elif cmd == 'attribute':
552 sys.stdout.write(''.join(show_attribute_meta(conf.config['apiurl'], project, package, subpackage, opts.attribute, opts.attribute_defaults, opts.attribute_project)))
553 elif cmd == 'prjconf':
554 sys.stdout.write(''.join(show_project_conf(conf.config['apiurl'], project)))
556 r = get_user_meta(conf.config['apiurl'], user)
558 sys.stdout.write(''.join(r))
559 elif cmd == 'pattern':
561 r = show_pattern_meta(conf.config['apiurl'], project, pattern)
563 sys.stdout.write(''.join(r))
565 r = show_pattern_metalist(conf.config['apiurl'], project)
567 sys.stdout.write('\n'.join(r) + '\n')
570 if opts.edit and not opts.file:
572 edit_meta(metatype='prj',
574 path_args=quote_plus(project),
577 'user': conf.config['user']}))
579 edit_meta(metatype='pkg',
581 path_args=(quote_plus(project), quote_plus(package)),
584 'user': conf.config['user']}))
585 elif cmd == 'prjconf':
586 edit_meta(metatype='prjconf',
588 path_args=quote_plus(project),
591 edit_meta(metatype='user',
593 path_args=(quote_plus(user)),
594 template_args=({'user': user}))
595 elif cmd == 'pattern':
596 edit_meta(metatype='pattern',
598 path_args=(project, pattern),
601 # create attribute entry
602 if (opts.create or opts.set) and cmd == 'attribute':
603 if not opts.attribute:
604 raise oscerr.WrongOptions('no attribute given to create')
607 opts.set = opts.set.replace('&', '&').replace('<', '<').replace('>', '>')
608 for i in opts.set.split(','):
609 values += '<value>%s</value>' % i
610 aname = opts.attribute.split(":")
611 d = '<attributes><attribute namespace=\'%s\' name=\'%s\' >%s</attribute></attributes>' % (aname[0], aname[1], values)
612 url = makeurl(conf.config['apiurl'], attributepath)
613 for data in streamfile(url, http_POST, data=d):
614 sys.stdout.write(data)
623 f = open(opts.file).read()
625 sys.exit('could not open file \'%s\'.' % opts.file)
628 edit_meta(metatype='prj',
631 path_args=quote_plus(project))
633 edit_meta(metatype='pkg',
636 path_args=(quote_plus(project), quote_plus(package)))
637 elif cmd == 'prjconf':
638 edit_meta(metatype='prjconf',
641 path_args=quote_plus(project))
643 edit_meta(metatype='user',
646 path_args=(quote_plus(user)))
647 elif cmd == 'pattern':
648 edit_meta(metatype='pattern',
651 path_args=(project, pattern))
656 path = metatypes[cmd]['path']
658 path = path % (project, pattern)
659 u = makeurl(conf.config['apiurl'], [path])
661 elif cmd == 'attribute':
662 if not opts.attribute:
663 raise oscerr.WrongOptions('no attribute given to create')
664 attributepath.append(opts.attribute)
665 u = makeurl(conf.config['apiurl'], attributepath)
666 for data in streamfile(u, http_DELETE):
667 sys.stdout.write(data)
669 raise oscerr.WrongOptions('The --delete switch is only for pattern metadata or attributes.')
672 @cmdln.option('-m', '--message', metavar='TEXT',
673 help='specify message TEXT')
674 @cmdln.option('-r', '--revision', metavar='REV',
675 help='for "create", specify a certain source revision ID (the md5 sum)')
676 @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
677 help='Superseding another request by this one')
678 @cmdln.option('--nodevelproject', action='store_true',
679 help='do not follow a defined devel project ' \
680 '(primary project where a package is developed)')
681 @cmdln.option('--cleanup', action='store_true',
682 help='remove package if submission gets accepted (default for home:<id>:branch projects)')
683 @cmdln.option('--no-cleanup', action='store_true',
684 help='never remove source package on accept, but update its content')
685 @cmdln.option('--no-update', action='store_true',
686 help='never touch source package on accept (will break source links)')
687 @cmdln.option('-d', '--diff', action='store_true',
688 help='show diff only instead of creating the actual request')
689 @cmdln.option('--yes', action='store_true',
690 help='proceed without asking.')
692 @cmdln.alias("submitreq")
693 @cmdln.alias("submitpac")
694 def do_submitrequest(self, subcmd, opts, *args):
695 """${cmd_name}: Create request to submit source into another Project
697 [See http://en.opensuse.org/Build_Service/Collaboration for information
700 See the "request" command for showing and modifing existing requests.
703 osc submitreq [OPTIONS]
704 osc submitreq [OPTIONS] DESTPRJ [DESTPKG]
705 osc submitreq [OPTIONS] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
709 src_update = conf.config['submitrequest_on_accept_action'] or None
710 # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
712 src_update = "cleanup"
713 elif opts.no_cleanup:
714 src_update = "update"
716 src_update = "noupdate"
718 args = slash_split(args)
720 # remove this block later again
721 oldcmds = ['create', 'list', 'log', 'show', 'decline', 'accept', 'delete', 'revoke']
722 if args and args[0] in oldcmds:
723 print "************************************************************************"
724 print "* WARNING: It looks that you are using this command with a *"
725 print "* deprecated syntax. *"
726 print "* Please run \"osc sr --help\" and \"osc rq --help\" *"
727 print "* to see the new syntax. *"
728 print "************************************************************************"
729 if args[0] == 'create':
735 raise oscerr.WrongArgs('Too many arguments.')
737 if len(args) > 0 and len(args) <= 2 and is_project_dir(os.getcwd()):
738 sys.exit('osc submitrequest from project directory is only working without target specs and for source linked files\n')
740 apiurl = conf.config['apiurl']
742 if len(args) == 0 and is_project_dir(os.getcwd()):
744 # submit requests for multiple packages are currently handled via multiple requests
745 # They could be also one request with multiple actions, but that avoids to accepts parts of it.
746 project = store_read_project(os.curdir)
747 apiurl = store_read_apiurl(os.curdir)
753 # loop via all packages for checking their state
754 for p in meta_get_packagelist(apiurl, project):
755 if p.startswith("_patchinfo:"):
758 # get _link info from server, that knows about the local state ...
759 u = makeurl(apiurl, ['source', project, p])
761 root = ET.parse(f).getroot()
762 linkinfo = root.find('linkinfo')
764 print "Package ", p, " is not a source link."
765 sys.exit("This is currently not supported.")
766 if linkinfo.get('error'):
767 print "Package ", p, " is a broken source link."
768 sys.exit("Please fix this first")
769 t = linkinfo.get('project')
771 if len(root.findall('entry')) > 1: # This is not really correct, but should work mostly
772 # Real fix is to ask the api if sources are modificated
773 # but there is no such call yet.
774 targetprojects.append(t)
776 print "Submitting package ", p
778 print " Skipping package ", p
780 print "Skipping package ", p, " since it is a source link pointing inside the project."
784 print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
785 print "\nEverything fine? Can we create the requests ? [y/n]"
786 if sys.stdin.read(1) != "y":
787 sys.exit("Aborted...")
789 # loop via all packages to do the action
791 result = create_submit_request(apiurl, project, p)
794 sys.exit("submit request creation failed")
795 sr_ids.append(result)
797 # create submit requests for all found patchinfos
801 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
804 for t in targetprojects:
805 s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \
806 (project, p, t, p, options_block)
810 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
811 (actionxml, cgi.escape(opts.message or ""))
812 u = makeurl(apiurl, ['request'], query='cmd=create')
813 f = http_POST(u, data=xml)
815 root = ET.parse(f).getroot()
816 sr_ids.append(root.get('id'))
818 print "Requests created: ",
821 sys.exit('Successfull finished')
824 # try using the working copy at hand
825 p = findpacs(os.curdir)[0]
826 src_project = p.prjname
829 if len(args) == 0 and p.islink():
830 dst_project = p.linkinfo.project
831 dst_package = p.linkinfo.package
833 dst_project = args[0]
835 dst_package = args[1]
837 dst_package = src_package
839 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
840 'Please provide it the target via commandline arguments.' % p.name)
842 modified = [i for i in p.filenamelist if p.status(i) != ' ' and p.status(i) != '?']
843 if len(modified) > 0:
844 print 'Your working copy has local modifications.'
845 repl = raw_input('Proceed without committing the local changes? (y|N) ')
849 # get the arguments from the commandline
850 src_project, src_package, dst_project = args[0:3]
852 dst_package = args[3]
854 dst_package = src_package
856 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
857 + self.get_cmd_help('request'))
859 if not opts.nodevelproject:
862 devloc = show_develproject(apiurl, dst_project, dst_package)
863 except urllib2.HTTPError:
864 print >>sys.stderr, """\
865 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
866 % (dst_project, dst_package)
870 dst_project != devloc and \
871 src_project != devloc:
873 A different project, %s, is defined as the place where development
874 of the package %s primarily takes place.
875 Please submit there instead, or use --nodevelproject to force direct submission.""" \
876 % (devloc, dst_package)
881 if opts.diff or not opts.message:
883 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
884 rdiff += server_diff(apiurl,
885 dst_project, dst_package, opts.revision,
886 src_project, src_package, None, True)
892 reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit')
893 user = conf.get_apiurl_usr(apiurl)
894 myreqs = [ i for i in reqs if i.state.who == user ]
897 print 'You already created the following submit request: %s.' % \
898 ', '.join([str(i.reqid) for i in myreqs ])
899 repl = raw_input('Supersede the old requests? (y/n/c) ')
900 if repl.lower() == 'c':
901 print >>sys.stderr, 'Aborting'
907 changes_re = re.compile(r'^--- .*\.changes ')
908 for line in rdiff.split('\n'):
909 if line.startswith('--- '):
910 if changes_re.match(line):
915 difflines.append(line)
916 opts.message = edit_message(footer=rdiff, template='\n'.join(parse_diff_for_commit_message('\n'.join(difflines))))
918 result = create_submit_request(apiurl,
919 src_project, src_package,
920 dst_project, dst_package,
921 opts.message, orev=opts.revision, src_update=src_update)
922 if repl.lower() == 'y':
924 change_request_state(apiurl, str(req.reqid), 'superseded',
925 'superseded by %s' % result, result)
928 r = change_request_state(conf.config['apiurl'],
929 opts.supersede, 'superseded', opts.message or '', result)
931 print 'created request id', result
934 @cmdln.option('-m', '--message', metavar='TEXT',
935 help='specify message TEXT')
937 @cmdln.alias("deletereq")
938 def do_deleterequest(self, subcmd, opts, *args):
939 """${cmd_name}: Create request to delete a package or project
943 osc deletereq [-m TEXT] PROJECT [PACKAGE]
947 args = slash_split(args)
950 raise oscerr.WrongArgs('Please specify at least a project.')
952 raise oscerr.WrongArgs('Too many arguments.')
954 apiurl = conf.config['apiurl']
962 opts.message = edit_message()
964 result = create_delete_request(apiurl, project, package, opts.message)
968 @cmdln.option('-m', '--message', metavar='TEXT',
969 help='specify message TEXT')
971 @cmdln.alias("changedevelreq")
972 def do_changedevelrequest(self, subcmd, opts, *args):
973 """${cmd_name}: Create request to change the devel package definition.
975 [See http://en.opensuse.org/Build_Service/Collaboration for information
978 See the "request" command for showing and modifing existing requests.
980 osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
984 raise oscerr.WrongArgs('Too many arguments.')
986 if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
988 devel_project = store_read_project(wd)
989 devel_package = package = store_read_package(wd)
990 apiurl = store_read_apiurl(wd)
991 project = conf.config['getpac_default_project']
994 raise oscerr.WrongArgs('Too few arguments.')
996 apiurl = conf.config['apiurl']
998 devel_project = args[2]
1001 devel_package = package
1003 devel_package = args[3]
1005 if not opts.message:
1007 footer=textwrap.TextWrapper(width = 66).fill(
1008 'please explain why you like to change the devel project of %s/%s to %s/%s'
1009 % (project,package,devel_project,devel_package))
1010 opts.message = edit_message(footer)
1012 result = create_change_devel_request(apiurl,
1013 devel_project, devel_package,
1019 @cmdln.option('-d', '--diff', action='store_true',
1020 help='generate a diff')
1021 @cmdln.option('-u', '--unified', action='store_true',
1022 help='output the diff in the unified diff format')
1023 @cmdln.option('-m', '--message', metavar='TEXT',
1024 help='specify message TEXT')
1025 @cmdln.option('-t', '--type', metavar='TYPE',
1026 help='limit to requests which contain a given action type (submit/delete/change_devel)')
1027 @cmdln.option('-a', '--all', action='store_true',
1028 help='all states. Same as\'-s all\'')
1029 @cmdln.option('-s', '--state', default='', # default is 'all' if no args given, 'new' otherwise
1030 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]')
1031 @cmdln.option('-D', '--days', metavar='DAYS',
1032 help='only list requests in state "new" or changed in the last DAYS. [default=%(request_list_days)s]')
1033 @cmdln.option('-U', '--user', metavar='USER',
1034 help='same as -M, but for the specified USER')
1035 @cmdln.option('-b', '--brief', action='store_true', default=False,
1036 help='print output in list view as list subcommand')
1037 @cmdln.option('-M', '--mine', action='store_true',
1038 help='only show requests created by yourself')
1039 @cmdln.option('-B', '--bugowner', action='store_true',
1040 help='also show requests about packages where I am bugowner')
1041 @cmdln.option('-i', '--interactive', action='store_true',
1042 help='interactive review of request')
1043 @cmdln.option('--non-interactive', action='store_true',
1044 help='non-interactive review of request')
1045 @cmdln.option('--exclude-target-project', action='append',
1046 help='exclude target project from request list')
1047 @cmdln.option('--involved-projects', action='store_true',
1048 help='show all requests for project/packages where USER is involved')
1050 @cmdln.alias("review")
1051 def do_request(self, subcmd, opts, *args):
1052 """${cmd_name}: Show and modify requests
1054 [See http://en.opensuse.org/Build_Service/Collaboration for information
1057 This command shows and modifies existing requests. To create new requests
1058 you need to call one of the following:
1061 osc changedevelrequest
1062 To send low level requests to the buildservice API, use:
1065 This command has the following sub commands:
1067 "list" lists open requests attached to a project or package or person.
1068 Uses the project/package of the current directory if none of
1069 -M, -U USER, project/package are given.
1071 "log" will show the history of the given ID
1073 "show" will show the request itself, and generate a diff for review, if
1074 used with the --diff option. The keyword show can be omitted if the ID is numeric.
1076 "decline" will change the request state to "declined" and append a
1077 message that you specify with the --message option.
1079 "wipe" will permanently delete a request.
1081 "revoke" will set the request state to "revoked" and append a
1082 message that you specify with the --message option.
1084 "accept" will change the request state to "accepted" and will trigger
1085 the actual submit process. That would normally be a server-side copy of
1086 the source package to the target package.
1088 "checkout" will checkout the request's source package. This only works for "submit" requests.
1091 osc request list [-M] [-U USER] [-s state] [-D DAYS] [-t type] [-B] [PRJ [PKG]]
1093 osc request [show] [-d] [-b] ID
1094 osc request accept [-m TEXT] ID
1095 osc request decline [-m TEXT] ID
1096 osc request revoke [-m TEXT] ID
1098 osc request checkout/co ID
1099 osc review accept [-m TEXT] ID
1100 osc review decline [-m TEXT] ID
1104 args = slash_split(args)
1106 if opts.all and opts.state:
1107 raise oscerr.WrongOptions('Sorry, the options \'--all\' and \'--state\' ' \
1108 'are mutually exclusive.')
1109 if opts.mine and opts.user:
1110 raise oscerr.WrongOptions('Sorry, the options \'--user\' and \'--mine\' ' \
1111 'are mutually exclusive.')
1112 if opts.interactive and opts.non_interactive:
1113 raise oscerr.WrongOptions('Sorry, the options \'--interactive\' and ' \
1114 '\'--non-interactive\' are mutually exclusive')
1119 if opts.state == '':
1122 if opts.state == '':
1125 cmds = ['list', 'log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co', 'help']
1126 if not args or args[0] not in cmds:
1127 raise oscerr.WrongArgs('Unknown request action %s. Choose one of %s.' \
1128 % (args[0],', '.join(cmds)))
1134 return self.do_help(['help', 'request'])
1137 min_args, max_args = 1, 1
1138 elif cmd in ['list']:
1139 min_args, max_args = 0, 2
1141 min_args, max_args = 1, 1
1142 if len(args) < min_args:
1143 raise oscerr.WrongArgs('Too few arguments.')
1144 if len(args) > max_args:
1145 raise oscerr.WrongArgs('Too many arguments.')
1147 apiurl = conf.config['apiurl']
1154 elif not opts.mine and not opts.user:
1156 project = store_read_project(os.curdir)
1157 apiurl = store_read_apiurl(os.curdir)
1158 package = store_read_package(os.curdir)
1159 except oscerr.NoWorkingCopy:
1164 elif cmd in ['log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co']:
1169 states = ('new', 'accepted', 'revoked', 'declined')
1170 state_list = opts.state.split(',')
1171 if opts.state == 'all':
1172 state_list = ['all']
1174 for s in state_list:
1176 raise oscerr.WrongArgs('Unknown state \'%s\', try one of %s' % (s, ','.join(states)))
1179 who = conf.get_apiurl_usr(apiurl)
1183 state_list = ['all']
1185 ## FIXME -B not implemented!
1187 if (self.options.debug):
1188 print 'list: option --bugowner ignored: not impl.'
1190 if opts.involved_projects:
1191 who = who or conf.get_apiurl_usr(apiurl)
1192 results = get_user_projpkgs_request_list(apiurl, who, req_state=state_list,
1193 req_type=opts.type, exclude_projects=opts.exclude_target_project or [])
1195 results = get_request_list(apiurl, project, package, who,
1196 state_list, opts.type, opts.exclude_target_project or [])
1197 results.sort(reverse=True)
1199 days = opts.days or conf.config['request_list_days']
1206 since = time.strftime('%Y-%m-%dT%H:%M:%S',time.localtime(time.time()-days*24*3600))
1209 ## bs has received 2009-09-20 a new xquery compare() function
1210 ## which allows us to limit the list inside of get_request_list
1211 ## That would be much faster for coolo. But counting the remainder
1212 ## would not be possible with current xquery implementation.
1213 ## Workaround: fetch all, and filter on client side.
1215 ## FIXME: date filtering should become implemented on server side
1216 for result in results:
1217 if days == 0 or result.state.when > since or result.state.name == 'new':
1218 print result.list_view()
1222 print "There are %d requests older than %s days.\n" % (skipped, days)
1225 for l in get_request_log(conf.config['apiurl'], reqid):
1230 r = get_request(conf.config['apiurl'], reqid)
1233 elif (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive:
1234 return request_interactive_review(conf.config['apiurl'], r)
1237 # fixme: will inevitably fail if the given target doesn't exist
1238 if opts.diff and r.actions[0].type != 'submit':
1239 raise oscerr.WrongOptions('\'--diff\' is not possible for request type: \'%s\'' % r.actions[0].type)
1242 print server_diff(conf.config['apiurl'],
1243 r.actions[0].dst_project, r.actions[0].dst_package, None,
1244 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, True)
1245 except urllib2.HTTPError, e:
1247 e.osc_msg = 'Diff not possible'
1249 # backward compatiblity: only a recent api/backend supports the missingok parameter
1251 print server_diff(conf.config['apiurl'],
1252 r.actions[0].dst_project, r.actions[0].dst_package, None,
1253 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, False)
1254 except urllib2.HTTPError, e:
1255 e.osc_msg = 'Diff not possible'
1259 elif cmd == 'checkout' or cmd == 'co':
1260 r = get_request(conf.config['apiurl'], reqid)
1261 submits = [ i for i in r.actions if i.type == 'submit' ]
1262 if not len(submits):
1263 raise oscerr.WrongArgs('\'checkout\' only works for \'submit\' requests')
1264 checkout_package(conf.config['apiurl'], submits[0].src_project, submits[0].src_package, \
1265 submits[0].src_rev, expand_link=True, prj_dir=submits[0].src_project)
1268 if not opts.message:
1269 opts.message = edit_message()
1270 state_map = {'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked'}
1271 # Change review state only
1272 if subcmd == 'review':
1273 if cmd in ['accept', 'decline']:
1274 r = change_review_state(conf.config['apiurl'],
1275 reqid, state_map[cmd], conf.config['user'], '', opts.message or '')
1277 # Change state of entire request
1278 elif cmd in ['accept', 'decline', 'wipe', 'revoke']:
1279 r = change_request_state(conf.config['apiurl'],
1280 reqid, state_map[cmd], opts.message or '')
1283 # editmeta and its aliases are all depracated
1284 @cmdln.alias("editprj")
1285 @cmdln.alias("createprj")
1286 @cmdln.alias("editpac")
1287 @cmdln.alias("createpac")
1288 @cmdln.alias("edituser")
1289 @cmdln.alias("usermeta")
1291 def do_editmeta(self, subcmd, opts, *args):
1294 Obsolete command to edit metadata. Use 'meta' now.
1296 See the help output of 'meta'.
1300 print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
1301 print >>sys.stderr, 'See \'osc help meta\'.'
1302 #self.do_help([None, 'meta'])
1306 @cmdln.option('-r', '--revision', metavar='rev',
1307 help='use the specified revision.')
1308 @cmdln.option('-u', '--unset', action='store_true',
1309 help='remove revision in link, it will point always to latest revision')
1310 def do_setlinkrev(self, subcmd, opts, *args):
1311 """${cmd_name}: Updates a revision number in a source link.
1313 This command adds or updates a specified revision number in a source link.
1314 The current revision of the source is used, if no revision number is specified.
1318 osc setlinkrev PROJECT [PACKAGE]
1322 args = slash_split(args)
1323 apiurl = conf.config['apiurl']
1326 p = findpacs(os.curdir)[0]
1331 sys.exit('Local directory is no checked out source link package, aborting')
1332 elif len(args) == 2:
1335 elif len(args) == 1:
1338 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1339 + self.get_cmd_help('setlinkrev'))
1342 packages = [ package ]
1344 packages = meta_get_packagelist(apiurl, project)
1347 print "setting revision for package", p
1351 rev, dummy = parseRevisionOption(opts.revision)
1352 set_link_rev(apiurl, project, p, rev)
1355 def do_linktobranch(self, subcmd, opts, *args):
1356 """${cmd_name}: Convert a package containing a classic link with patch to a branch
1358 This command tells the server to convert a _link with or without a project.diff
1359 to a branch. This is a full copy with a _link file pointing to the branched place.
1362 osc linktobranch # can be used in checked out package
1363 osc linktobranch PROJECT PACKAGE
1367 args = slash_split(args)
1370 project = store_read_project(wd)
1371 package = store_read_package(wd)
1372 apiurl = store_read_apiurl(wd)
1373 update_local_dir = True
1375 raise oscerr.WrongArgs('Too few arguments (required none or two)')
1377 raise oscerr.WrongArgs('Too many arguments (required none or two)')
1379 apiurl = conf.config['apiurl']
1382 update_local_dir = False
1385 link_to_branch(apiurl, project, package)
1386 if update_local_dir:
1388 pac.update(rev=pac.latest_rev())
1391 @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'],
1392 help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.')
1393 @cmdln.option('-c', '--current', action='store_true',
1394 help='link fixed against current revision.')
1395 @cmdln.option('-r', '--revision', metavar='rev',
1396 help='link the specified revision.')
1397 @cmdln.option('-f', '--force', action='store_true',
1398 help='overwrite an existing link file if it is there.')
1399 @cmdln.option('-d', '--disable-publish', action='store_true',
1400 help='disable publishing of the linked package')
1401 def do_linkpac(self, subcmd, opts, *args):
1402 """${cmd_name}: "Link" a package to another package
1404 A linked package is a clone of another package, but plus local
1405 modifications. It can be cross-project.
1407 The DESTPAC name is optional; the source packages' name will be used if
1410 Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
1412 To add a patch, add the patch as file and add it to the _link file.
1413 You can also specify text which will be inserted at the top of the spec file.
1415 See the examples in the _link file.
1418 osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1422 args = slash_split(args)
1424 if not args or len(args) < 3:
1425 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1426 + self.get_cmd_help('linkpac'))
1428 rev, dummy = parseRevisionOption(opts.revision)
1430 src_project = args[0]
1431 src_package = args[1]
1432 dst_project = args[2]
1434 dst_package = args[3]
1436 dst_package = src_package
1438 if src_project == dst_project and src_package == dst_package:
1439 raise oscerr.WrongArgs('Error: source and destination are the same.')
1441 if src_project == dst_project and not opts.cicount:
1442 # in this case, the user usually wants to build different spec
1443 # files from the same source
1444 opts.cicount = "copy"
1447 rev = show_upstream_rev(conf.config['apiurl'], src_project, src_package)
1449 if rev and not checkRevision(src_project, src_package, rev):
1450 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
1453 link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish)
1455 @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]',
1456 help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]')
1457 @cmdln.option('-d', '--disable-publish', action='store_true',
1458 help='disable publishing of the aggregated package')
1459 def do_aggregatepac(self, subcmd, opts, *args):
1460 """${cmd_name}: "Aggregate" a package to another package
1462 Aggregation of a package means that the build results (binaries) of a
1463 package are basically copied into another project.
1464 This can be used to make packages available from building that are
1465 needed in a project but available only in a different project. Note
1466 that this is done at the expense of disk space. See
1467 http://en.opensuse.org/Build_Service/Tips_and_Tricks#_link_and__aggregate
1468 for more information.
1470 The DESTPAC name is optional; the source packages' name will be used if
1474 osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1478 args = slash_split(args)
1480 if not args or len(args) < 3:
1481 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1482 + self.get_cmd_help('aggregatepac'))
1484 src_project = args[0]
1485 src_package = args[1]
1486 dst_project = args[2]
1488 dst_package = args[3]
1490 dst_package = src_package
1492 if src_project == dst_project and src_package == dst_package:
1493 raise oscerr.WrongArgs('Error: source and destination are the same.')
1497 for pair in opts.map_repo.split(','):
1498 src_tgt = pair.split('=')
1499 if len(src_tgt) != 2:
1500 raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo)
1501 repo_map[src_tgt[0]] = src_tgt[1]
1503 aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish)
1506 @cmdln.option('-c', '--client-side-copy', action='store_true',
1507 help='do a (slower) client-side copy')
1508 @cmdln.option('-k', '--keep-maintainers', action='store_true',
1509 help='keep original maintainers. Default is remove all and replace with the one calling the script.')
1510 @cmdln.option('-d', '--keep-develproject', action='store_true',
1511 help='keep develproject tag in the package metadata')
1512 @cmdln.option('-r', '--revision', metavar='rev',
1513 help='link the specified revision.')
1514 @cmdln.option('-t', '--to-apiurl', metavar='URL',
1515 help='URL of destination api server. Default is the source api server.')
1516 @cmdln.option('-m', '--message', metavar='TEXT',
1517 help='specify message TEXT')
1518 @cmdln.option('-e', '--expand', action='store_true',
1519 help='if the source package is a link then copy the expanded version of the link')
1520 def do_copypac(self, subcmd, opts, *args):
1521 """${cmd_name}: Copy a package
1523 A way to copy package to somewhere else.
1525 It can be done across buildservice instances, if the -t option is used.
1526 In that case, a client-side copy is implied.
1528 Using --client-side-copy always involves downloading all files, and
1529 uploading them to the target.
1531 The DESTPAC name is optional; the source packages' name will be used if
1535 osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1539 args = slash_split(args)
1541 if not args or len(args) < 3:
1542 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1543 + self.get_cmd_help('copypac'))
1545 src_project = args[0]
1546 src_package = args[1]
1547 dst_project = args[2]
1549 dst_package = args[3]
1551 dst_package = src_package
1553 src_apiurl = conf.config['apiurl']
1555 dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl)
1557 dst_apiurl = src_apiurl
1559 if src_project == dst_project and \
1560 src_package == dst_package and \
1561 src_apiurl == dst_apiurl:
1562 raise oscerr.WrongArgs('Source and destination are the same.')
1564 if src_apiurl != dst_apiurl:
1565 opts.client_side_copy = True
1567 rev, dummy = parseRevisionOption(opts.revision)
1570 comment = opts.message
1573 rev = show_upstream_rev(src_apiurl, src_project, src_package)
1574 comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev )
1576 r = copy_pac(src_apiurl, src_project, src_package,
1577 dst_apiurl, dst_project, dst_package,
1578 client_side_copy=opts.client_side_copy,
1579 keep_maintainers=opts.keep_maintainers,
1580 keep_develproject=opts.keep_develproject,
1587 @cmdln.option('-c', '--checkout', action='store_true',
1588 help='Checkout branched package afterwards ' \
1589 '(\'osc bco\' is a shorthand for this option)' )
1590 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
1591 help='Use this attribute to find affected packages (default is OBS:Maintained)')
1592 @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE',
1593 help='Use this attribute to find update projects (default is OBS:UpdateProject) ')
1594 def do_mbranch(self, subcmd, opts, *args):
1595 """${cmd_name}: Multiple branch of a package
1597 [See http://en.opensuse.org/Build_Service/Concepts/Maintenance for information
1600 This command is used for creating multiple links of defined version of a package
1601 in one project. This is esp. used for maintenance updates.
1603 The branched package will live in
1604 home:USERNAME:branches:ATTRIBUTE:PACKAGE
1605 if nothing else specified.
1608 osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ]
1611 args = slash_split(args)
1614 maintained_attribute = conf.config['maintained_attribute']
1615 maintained_update_project_attribute = conf.config['maintained_update_project_attribute']
1617 if not len(args) or len(args) > 2:
1618 raise oscerr.WrongArgs('Wrong number of arguments.')
1624 r = attribute_branch_pkg(conf.config['apiurl'], maintained_attribute, maintained_update_project_attribute, \
1628 print >>sys.stderr, 'ERROR: Attribute branch call came not back with a project.'
1631 print "Project " + r + " created."
1634 init_project_dir(conf.config['apiurl'], r, r)
1635 print statfrmt('A', r)
1638 for package in meta_get_packagelist(conf.config['apiurl'], r):
1640 checkout_package(conf.config['apiurl'], r, package, expand_link = True, prj_dir = r)
1642 print >>sys.stderr, 'Error while checkout package:\n', package
1644 if conf.config['verbose']:
1645 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1648 @cmdln.alias('branchco')
1650 @cmdln.alias('getpac')
1651 @cmdln.option('--nodevelproject', action='store_true',
1652 help='do not follow a defined devel project ' \
1653 '(primary project where a package is developed)')
1654 @cmdln.option('-c', '--checkout', action='store_true',
1655 help='Checkout branched package afterwards ' \
1656 '(\'osc bco\' is a shorthand for this option)' )
1657 @cmdln.option('-r', '--revision', metavar='rev',
1658 help='branch against a specific revision')
1659 @cmdln.option('-m', '--message', metavar='TEXT',
1660 help='specify message TEXT')
1661 def do_branch(self, subcmd, opts, *args):
1662 """${cmd_name}: Branch a package
1664 [See http://en.opensuse.org/Build_Service/Collaboration for information
1667 Create a source link from a package of an existing project to a new
1668 subproject of the requesters home project (home:branches:)
1670 The branched package will live in
1671 home:USERNAME:branches:PROJECT/PACKAGE
1672 if nothing else specified.
1674 With getpac or bco, the branched package will come from
1675 %(getpac_default_project)s
1676 if nothing else specified.
1679 osc branch SOURCEPROJECT SOURCEPACKAGE
1680 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT
1681 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE
1682 osc getpac SOURCEPACKAGE
1687 if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': opts.checkout = True
1688 args = slash_split(args)
1689 tproject = tpackage = None
1691 if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1:
1692 print >>sys.stderr, 'defaulting to %s/%s' % (conf.config['getpac_default_project'], args[0])
1693 # python has no args.unshift ???
1694 args = [ conf.config['getpac_default_project'] , args[0] ]
1696 if len(args) < 2 or len(args) > 4:
1697 raise oscerr.WrongArgs('Wrong number of arguments.')
1698 expected = 'home:%s:branches:%s' % (conf.config['user'], args[0])
1700 expected = tproject = args[2]
1704 if not opts.message:
1705 footer='please specify the purpose of your branch'
1706 template='This package was branched from %s in order to ...\n' % args[0]
1707 opts.message = edit_message(footer, template)
1709 exists, targetprj, targetpkg, srcprj, srcpkg = \
1710 branch_pkg(conf.config['apiurl'], args[0], args[1],
1711 nodevelproject=opts.nodevelproject, rev=opts.revision,
1712 target_project=tproject, target_package=tpackage,
1713 return_existing=opts.checkout, msg=opts.message or '')
1715 print >>sys.stderr, 'Using existing branch project: %s' % targetprj
1718 if not exists and (srcprj is not None and srcprj != args[0] or \
1719 srcprj is None and targetprj != expected):
1720 devloc = srcprj or targetprj
1721 if not srcprj and 'branches:' in targetprj:
1722 devloc = targetprj.split('branches:')[1]
1723 print '\nNote: The branch has been created of a different project,\n' \
1725 ' which is the primary location of where development for\n' \
1726 ' that package takes place.\n' \
1727 ' That\'s also where you would normally make changes against.\n' \
1728 ' A direct branch of the specified package can be forced\n' \
1729 ' with the --nodevelproject option.\n' % devloc
1731 package = tpackage or args[1]
1733 checkout_package(conf.config['apiurl'], targetprj, package,
1734 expand_link=True, prj_dir=targetprj)
1735 if conf.config['verbose']:
1736 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1739 if conf.get_configParser().get('general', 'apiurl') != conf.config['apiurl']:
1740 apiopt = '-A %s ' % conf.config['apiurl']
1741 print 'A working copy of the branched package can be checked out with:\n\n' \
1743 % (apiopt, targetprj, package)
1744 print_request_list(conf.config['apiurl'], args[0], args[1])
1746 print_request_list(conf.config['apiurl'], devloc, args[1])
1750 @cmdln.option('-f', '--force', action='store_true',
1751 help='deletes a package or an empty project')
1752 def do_rdelete(self, subcmd, opts, *args):
1753 """${cmd_name}: Delete a project or packages on the server.
1755 As a safety measure, project must be empty (i.e., you need to delete all
1756 packages first). If you are sure that you want to remove this project and all
1757 its packages use \'--force\' switch.
1760 osc rdelete -f PROJECT
1761 osc rdelete PROJECT PACKAGE [PACKAGE ...]
1766 args = slash_split(args)
1768 raise oscerr.WrongArgs('Missing argument.')
1774 # careful: if pkg is an empty string, the package delete request results
1775 # into a project delete request - which works recursively...
1777 delete_package(conf.config['apiurl'], prj, pkg)
1778 elif len(meta_get_packagelist(conf.config['apiurl'], prj)) >= 1 and not opts.force:
1779 print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
1780 'If you are sure that you want to remove this project and all its ' \
1781 'packages use the \'--force\' switch'
1784 delete_project(conf.config['apiurl'], prj)
1787 def do_deletepac(self, subcmd, opts, *args):
1788 print """${cmd_name} is obsolete !
1791 osc delete for checked out packages or projects
1793 osc rdelete for server side operations."""
1798 @cmdln.option('-f', '--force', action='store_true',
1799 help='deletes a project and its packages')
1800 def do_deleteprj(self, subcmd, opts, project):
1801 """${cmd_name} is obsolete !
1808 @cmdln.alias('metafromspec')
1809 @cmdln.option('', '--specfile', metavar='FILE',
1810 help='Path to specfile. (if you pass more than working copy this option is ignored)')
1811 def do_updatepacmetafromspec(self, subcmd, opts, *args):
1812 """${cmd_name}: Update package meta information from a specfile
1814 ARG, if specified, is a package working copy.
1820 args = parseargs(args)
1821 if opts.specfile and len(args) == 1:
1822 specfile = opts.specfile
1825 pacs = findpacs(args)
1827 p.read_meta_from_spec(specfile)
1828 p.update_package_meta()
1832 @cmdln.option('-c', '--change', metavar='rev',
1833 help='the change made by revision rev (like -r rev-1:rev).'
1834 'If rev is negative this is like -r rev:rev-1.')
1835 @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
1836 help='If rev1 is specified it will compare your working copy against '
1837 'the revision (rev1) on the server. '
1838 'If rev1 and rev2 are specified it will compare rev1 against rev2 '
1839 '(NOTE: changes in your working copy are ignored in this case)')
1840 @cmdln.option('-p', '--plain', action='store_true',
1841 help='output the diff in plain (not unified) diff format')
1842 @cmdln.option('--missingok', action='store_true',
1843 help='do not fail if the source or target project/package does not exist on the server')
1844 def do_diff(self, subcmd, opts, *args):
1845 """${cmd_name}: Generates a diff
1847 Generates a diff, comparing local changes against the repository
1850 ARG, specified, is a filename to include in the diff.
1856 args = parseargs(args)
1857 pacs = findpacs(args)
1861 rev = int(opts.change)
1871 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1874 rev1, rev2 = parseRevisionOption(opts.revision)
1878 diff += ''.join(make_diff(pac, rev1))
1880 diff += server_diff(pac.apiurl, pac.prjname, pac.name, rev1,
1881 pac.prjname, pac.name, rev2, not opts.plain, opts.missingok)
1886 @cmdln.option('--oldprj', metavar='OLDPRJ',
1887 help='project to compare against'
1888 ' (deprecated, use 3 argument form)')
1889 @cmdln.option('--oldpkg', metavar='OLDPKG',
1890 help='package to compare against'
1891 ' (deprecated, use 3 argument form)')
1892 @cmdln.option('-r', '--revision', metavar='N[:M]',
1893 help='revision id, where N = old revision and M = new revision')
1894 @cmdln.option('-p', '--plain', action='store_true',
1895 help='output the diff in plain (not unified) diff format')
1896 @cmdln.option('-c', '--change', metavar='rev',
1897 help='the change made by revision rev (like -r rev-1:rev). '
1898 'If rev is negative this is like -r rev:rev-1.')
1899 @cmdln.option('--missingok', action='store_true',
1900 help='do not fail if the source or target project/package does not exist on the server')
1901 def do_rdiff(self, subcmd, opts, *args):
1902 """${cmd_name}: Server-side "pretty" diff of two packages
1904 Compares two packages (three or four arguments) or shows the
1905 changes of a specified revision of a package (two arguments)
1907 If no revision is specified the latest revision is used.
1909 Note that this command doesn't return a normal diff (which could be
1910 applied as patch), but a "pretty" diff, which also compares the content
1915 osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC]
1916 osc ${cmd_name} PROJECT PACKAGE
1920 args = slash_split(args)
1931 new_project = args[0]
1932 new_package = args[1]
1934 old_project = opts.oldprj
1936 old_package = opts.oldpkg
1937 elif len(args) == 3 or len(args) == 4:
1938 if opts.oldprj or opts.oldpkg:
1939 raise oscerr.WrongArgs('--oldpkg and --oldprj are only valid with two arguments')
1940 old_project = args[0]
1941 new_package = old_package = args[1]
1942 new_project = args[2]
1944 new_package = args[3]
1946 raise oscerr.WrongArgs('Wrong number of arguments')
1951 rev = int(opts.change)
1961 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1965 rev1, rev2 = parseRevisionOption(opts.revision)
1967 rdiff = server_diff(conf.config['apiurl'],
1968 old_project, old_package, rev1,
1969 new_project, new_package, rev2, not opts.plain, opts.missingok)
1974 def do_install(self, subcmd, opts, *args):
1975 """${cmd_name}: install a package after build via zypper in -r
1977 Not implemented yet. Use osc repourls,
1978 select the url you best like (standard),
1979 chop off after the last /, this should work with zypper.
1986 args = slash_split(args)
1987 args = expand_proj_pack(args)
1990 ## if there is only one argument, and it ends in .ymp
1991 ## then fetch it, Parse XML to get the first
1992 ## metapackage.group.repositories.repository.url
1993 ## and construct zypper cmd's for all
1994 ## metapackage.group.software.item.name
1996 ## if args[0] is already an url, the use it as is.
1998 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])
1999 print self.do_install.__doc__
2000 print "Example: \n" + cmd
2003 def do_repourls(self, subcmd, opts, *args):
2004 """${cmd_name}: Shows URLs of .repo files
2006 Shows URLs on which to access the project .repos files (yum-style
2007 metadata) on download.opensuse.org.
2010 osc repourls [PROJECT]
2015 apiurl = conf.config['apiurl']
2019 elif len(args) == 0:
2020 project = store_read_project('.')
2021 apiurl = store_read_apiurl('.')
2023 raise oscerr.WrongArgs('Wrong number of arguments')
2025 # XXX: API should somehow tell that
2026 url_tmpl = 'http://download.opensuse.org/repositories/%s/%s/%s.repo'
2027 repos = get_repositories_of_project(apiurl, project)
2029 print url_tmpl % (project.replace(':', ':/'), repo, project)
2032 @cmdln.option('-r', '--revision', metavar='rev',
2033 help='checkout the specified revision. '
2034 'NOTE: if you checkout the complete project '
2035 'this option is ignored!')
2036 @cmdln.option('-e', '--expand-link', action='store_true',
2037 help='if a package is a link, check out the expanded '
2038 'sources (no-op, since this became the default)')
2039 @cmdln.option('-u', '--unexpand-link', action='store_true',
2040 help='if a package is a link, check out the _link file ' \
2041 'instead of the expanded sources')
2042 @cmdln.option('-c', '--current-dir', action='store_true',
2043 help='place PACKAGE folder in the current directory' \
2044 'instead of a PROJECT/PACKAGE directory')
2045 @cmdln.option('-s', '--source-service-files', action='store_true',
2046 help='server side generated files of source services' \
2047 'gets downloaded as well' )
2049 def do_checkout(self, subcmd, opts, *args):
2050 """${cmd_name}: Check out content from the repository
2052 Check out content from the repository server, creating a local working
2055 When checking out a single package, the option --revision can be used
2056 to specify a revision of the package to be checked out.
2058 When a package is a source link, then it will be checked out in
2059 expanded form. If --unexpand-link option is used, the checkout will
2060 instead produce the raw _link file plus patches.
2063 osc co PROJECT [PACKAGE] [FILE]
2064 osc co PROJECT # entire project
2065 osc co PROJECT PACKAGE # a package
2066 osc co PROJECT PACKAGE FILE # single file -> to current dir
2068 while inside a project directory:
2069 osc co PACKAGE # check out PACKAGE from project
2074 if opts.unexpand_link:
2078 if opts.source_service_files:
2079 service_files = True
2081 service_files = False
2083 args = slash_split(args)
2084 project = package = filename = None
2085 apiurl = conf.config['apiurl']
2087 project = project_dir = args[0]
2093 if args and len(args) == 1:
2094 localdir = os.getcwd()
2095 if is_project_dir(localdir):
2096 project = store_read_project(localdir)
2097 project_dir = localdir
2099 apiurl = store_read_apiurl(localdir)
2101 rev, dummy = parseRevisionOption(opts.revision)
2105 if rev and rev != "latest" and not checkRevision(project, package, rev):
2106 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2110 get_source_file(apiurl, project, package, filename, revision=rev, progress_obj=self.download_progress)
2113 if opts.current_dir:
2115 checkout_package(apiurl, project, package, rev, expand_link=expand_link, \
2116 prj_dir=project_dir, service_files=service_files, progress_obj=self.download_progress)
2117 print_request_list(apiurl, project, package)
2121 if sys.platform[:3] == 'win':
2122 prj_dir = prj_dir.replace(':', ';')
2123 if os.path.exists(prj_dir):
2124 sys.exit('osc: project \'%s\' already exists' % project)
2126 # check if the project does exist (show_project_meta will throw an exception)
2127 show_project_meta(apiurl, project)
2129 init_project_dir(apiurl, prj_dir, project)
2130 print statfrmt('A', prj_dir)
2133 for package in meta_get_packagelist(apiurl, project):
2135 checkout_package(apiurl, project, package, expand_link = expand_link, \
2136 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress)
2137 except oscerr.LinkExpandError, e:
2138 print >>sys.stderr, 'Link cannot be expanded:\n', e
2139 print >>sys.stderr, 'Use "osc repairlink" for fixing merge conflicts:\n'
2140 # check out in unexpanded form at least
2141 checkout_package(apiurl, project, package, expand_link = False, \
2142 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress)
2143 print_request_list(apiurl, project)
2146 raise oscerr.WrongArgs('Missing argument.\n\n' \
2147 + self.get_cmd_help('checkout'))
2150 @cmdln.option('-q', '--quiet', action='store_true',
2151 help='print as little as possible')
2152 @cmdln.option('-v', '--verbose', action='store_true',
2153 help='print extra information')
2155 def do_status(self, subcmd, opts, *args):
2156 """${cmd_name}: Show status of files in working copy
2158 Show the status of files in a local working copy, indicating whether
2159 files have been changed locally, deleted, added, ...
2161 The first column in the output specifies the status and is one of the
2162 following characters:
2163 ' ' no modifications
2168 '?' item is not under version control
2169 '!' item is missing (removed by non-osc command) or incomplete
2174 osc st file1 file2 ...
2177 osc status [OPTS] [PATH...]
2181 args = parseargs(args)
2183 # storage for single Package() objects
2185 # storage for a project dir ( { prj_instance : [ package objects ] } )
2188 # when 'status' is run inside a project dir, it should
2189 # stat all packages existing in the wc
2190 if is_project_dir(arg):
2191 prj = Project(arg, False)
2193 if conf.config['do_package_tracking']:
2195 for pac in prj.pacs_have:
2196 # we cannot create package objects if the dir does not exist
2197 if not pac in prj.pacs_broken:
2198 prjpacs[prj].append(os.path.join(arg, pac))
2200 pacpaths += [arg + '/' + n for n in prj.pacs_have]
2201 elif is_package_dir(arg):
2202 pacpaths.append(arg)
2203 elif os.path.isfile(arg):
2204 pacpaths.append(arg)
2206 msg = '\'%s\' is neither a project or a package directory' % arg
2207 raise oscerr.NoWorkingCopy, msg
2209 # process single packages
2210 lines = getStatus(findpacs(pacpaths), None, opts.verbose, opts.quiet)
2211 # process project dirs
2212 for prj, pacs in prjpacs.iteritems():
2213 lines += getStatus(findpacs(pacs), prj, opts.verbose, opts.quiet)
2215 print '\n'.join(lines)
2218 def do_add(self, subcmd, opts, *args):
2219 """${cmd_name}: Mark files to be added upon the next commit
2222 osc add FILE [FILE...]
2226 raise oscerr.WrongArgs('Missing argument.\n\n' \
2227 + self.get_cmd_help('add'))
2229 filenames = parseargs(args)
2233 def do_mkpac(self, subcmd, opts, *args):
2234 """${cmd_name}: Create a new package under version control
2237 osc mkpac new_package
2240 if not conf.config['do_package_tracking']:
2241 print >>sys.stderr, "to use this feature you have to enable \'do_package_tracking\' " \
2242 "in the [general] section in the configuration file"
2246 raise oscerr.WrongArgs('Wrong number of arguments.')
2248 createPackageDir(args[0])
2250 @cmdln.option('-r', '--recursive', action='store_true',
2251 help='If CWD is a project dir then scan all package dirs as well')
2253 def do_addremove(self, subcmd, opts, *args):
2254 """${cmd_name}: Adds new files, removes disappeared files
2256 Adds all files new in the local copy, and removes all disappeared files.
2258 ARG, if specified, is a package working copy.
2264 args = parseargs(args)
2266 for arg in arg_list:
2267 if is_project_dir(arg) and conf.config['do_package_tracking']:
2268 prj = Project(arg, False)
2269 for pac in prj.pacs_unvers:
2270 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2271 if os.path.isdir(pac_dir):
2272 addFiles([pac_dir], prj)
2273 for pac in prj.pacs_broken:
2274 if prj.get_state(pac) != 'D':
2275 prj.set_state(pac, 'D')
2276 print statfrmt('D', getTransActPath(os.path.join(prj.dir, pac)))
2278 for pac in prj.pacs_have:
2279 state = prj.get_state(pac)
2280 if state != None and state != 'D':
2281 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2282 args.append(pac_dir)
2284 prj.write_packages()
2285 elif is_project_dir(arg):
2286 print >>sys.stderr, 'osc: addremove is not supported in a project dir unless ' \
2287 '\'do_package_tracking\' is enabled in the configuration file'
2290 pacs = findpacs(args)
2292 p.todo = p.filenamelist + p.filenamelist_unvers
2294 for filename in p.todo:
2295 if os.path.isdir(filename):
2297 # ignore foo.rXX, foo.mine for files which are in 'C' state
2298 if os.path.splitext(filename)[0] in p.in_conflict:
2300 state = p.status(filename)
2303 # TODO: should ignore typical backup files suffix ~ or .orig
2305 print statfrmt('A', getTransActPath(os.path.join(p.dir, filename)))
2307 p.put_on_deletelist(filename)
2308 p.write_deletelist()
2309 os.unlink(os.path.join(p.storedir, filename))
2310 print statfrmt('D', getTransActPath(os.path.join(p.dir, filename)))
2315 @cmdln.alias('checkin')
2316 @cmdln.option('-m', '--message', metavar='TEXT',
2317 help='specify log message TEXT')
2318 @cmdln.option('-F', '--file', metavar='FILE',
2319 help='read log message from FILE')
2320 @cmdln.option('-f', '--force', default=False, action="store_true",
2321 help='force commit - do not tests a file list')
2322 def do_commit(self, subcmd, opts, *args):
2323 """${cmd_name}: Upload content to the repository server
2325 Upload content which is changed in your working copy, to the repository
2328 Optionally checks the state of a working copy, if found a file with
2329 unknown state, it requests an user input:
2330 * skip - don't change anything, just move to another file
2331 * remove - remove a file from dir
2332 * edit file list - edit filelist using EDITOR
2333 * commit - don't check anything and commit package
2334 * abort - abort commit - this is default value
2335 This can be supressed by check_filelist config item, or -f/--force
2336 command line option.
2339 osc ci # current dir
2341 osc ci file1 file2 ...
2347 args = parseargs(args)
2354 msg = open(opts.file).read()
2356 sys.exit('could not open file \'%s\'.' % opts.file)
2359 for arg in arg_list:
2360 if conf.config['do_package_tracking'] and is_project_dir(arg):
2361 Project(arg).commit(msg=msg)
2363 msg = edit_message()
2366 pacs = findpacs(args)
2368 if conf.config['check_filelist'] and not opts.force:
2369 check_filelist_before_commit(pacs)
2372 template = store_read_file(os.path.abspath('.'), '_commit_msg')
2373 # open editor for commit message
2374 # but first, produce status and diff to append to the template
2378 changed = getStatus([pac], quiet=True)
2381 diffs += ['\nDiff for working copy: %s' % pac.dir]
2382 diffs += make_diff(pac, 0)
2383 lines.extend(get_commit_message_template(pac))
2384 if template == None:
2385 template='\n'.join(lines)
2386 # if footer is empty, there is nothing to commit, and no edit needed.
2388 msg = edit_message(footer='\n'.join(footer), template=template)
2391 store_write_string(os.path.abspath('.'), '_commit_msg', msg)
2393 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2395 if conf.config['do_package_tracking'] and len(pacs) > 0:
2399 # it is possible to commit packages from different projects at the same
2400 # time: iterate over all pacs and put each pac to the right project in the dict
2402 path = os.path.normpath(os.path.join(pac.dir, os.pardir))
2403 if is_project_dir(path):
2404 pac_path = os.path.basename(os.path.normpath(pac.absdir))
2405 prj_paths.setdefault(path, []).append(pac_path)
2406 files[pac_path] = pac.todo
2408 single_paths.append(pac.dir)
2409 for prj, packages in prj_paths.iteritems():
2410 Project(prj).commit(tuple(packages), msg, files)
2411 for pac in single_paths:
2412 Package(pac).commit(msg)
2417 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2419 @cmdln.option('-r', '--revision', metavar='REV',
2420 help='update to specified revision (this option will be ignored '
2421 'if you are going to update the complete project or more than '
2423 @cmdln.option('-u', '--unexpand-link', action='store_true',
2424 help='if a package is an expanded link, update to the raw _link file')
2425 @cmdln.option('-e', '--expand-link', action='store_true',
2426 help='if a package is a link, update to the expanded sources')
2427 @cmdln.option('-s', '--source-service-files', action='store_true',
2428 help='Use server side generated sources instead of local generation.' )
2430 def do_update(self, subcmd, opts, *args):
2431 """${cmd_name}: Update a working copy
2436 If the current working directory is a package, update it.
2437 If the directory is a project directory, update all contained
2438 packages, AND check out newly added packages.
2440 To update only checked out packages, without checking out new
2441 ones, you might want to use "osc up *" from within the project
2445 Update the packages specified by the path argument(s)
2447 When --expand-link is used with source link packages, the expanded
2448 sources will be checked out. Without this option, the _link file and
2449 patches will be checked out. The option --unexpand-link can be used to
2450 switch back to the "raw" source with a _link file plus patch(es).
2456 if (opts.expand_link and opts.unexpand_link) \
2457 or (opts.expand_link and opts.revision) \
2458 or (opts.unexpand_link and opts.revision):
2459 raise oscerr.WrongOptions('Sorry, the options --expand-link, --unexpand-link and '
2460 '--revision are mutually exclusive.')
2462 if opts.source_service_files: service_files = True
2463 else: service_files = False
2465 args = parseargs(args)
2468 for arg in arg_list:
2469 if is_project_dir(arg):
2470 prj = Project(arg, progress_obj=self.download_progress)
2472 if conf.config['do_package_tracking']:
2473 prj.update(expand_link=opts.expand_link,
2474 unexpand_link=opts.unexpand_link)
2477 # if not tracking package, and 'update' is run inside a project dir,
2478 # it should do the following:
2479 # (a) update all packages
2480 args += prj.pacs_have
2481 # (b) fetch new packages
2482 prj.checkout_missing_pacs(expand_link = not opts.unexpand_link)
2484 print_request_list(prj.apiurl, prj.name)
2487 pacs = findpacs(args, progress_obj=self.download_progress)
2489 if opts.revision and len(args) == 1:
2490 rev, dummy = parseRevisionOption(opts.revision)
2491 if not checkRevision(pacs[0].prjname, pacs[0].name, rev, pacs[0].apiurl):
2492 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2499 print 'Updating %s' % p.name
2501 # FIXME: ugly workaround for #399247
2502 if opts.expand_link or opts.unexpand_link:
2503 if [ i for i in p.filenamelist+p.filenamelist_unvers if p.status(i) != ' ' and p.status(i) != '?']:
2504 print >>sys.stderr, 'osc: cannot expand/unexpand because your working ' \
2505 'copy has local modifications.\nPlease revert/commit them ' \
2510 if opts.expand_link and p.islink() and not p.isexpanded():
2511 if p.haslinkerror():
2513 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev)
2515 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev, linkrev="base")
2518 rev = p.linkinfo.xsrcmd5
2519 print 'Expanding to rev', rev
2520 elif opts.unexpand_link and p.islink() and p.isexpanded():
2521 print 'Unexpanding to rev', p.linkinfo.lsrcmd5
2522 rev = p.linkinfo.lsrcmd5
2523 elif p.islink() and p.isexpanded():
2524 rev = p.latest_rev()
2526 p.update(rev, service_files)
2527 if opts.unexpand_link:
2530 print_request_list(p.apiurl, p.prjname, p.name)
2533 @cmdln.option('-f', '--force', action='store_true',
2534 help='forces removal of entire package and its files')
2537 @cmdln.alias('remove')
2538 def do_delete(self, subcmd, opts, *args):
2539 """${cmd_name}: Mark files or package directories to be deleted upon the next 'checkin'
2542 cd .../PROJECT/PACKAGE
2543 osc delete FILE [...]
2545 osc delete PACKAGE [...]
2547 This command works on check out copies. Use "rdelete" for working on server
2548 side only. This is needed for removing the entire project.
2550 As a safety measure, projects must be empty (i.e., you need to delete all
2553 If you are sure that you want to remove a package and all
2554 its files use \'--force\' switch. Sometimes this also works without --force.
2560 raise oscerr.WrongArgs('Missing argument.\n\n' \
2561 + self.get_cmd_help('delete'))
2563 args = parseargs(args)
2564 # check if args contains a package which was removed by
2565 # a non-osc command and mark it with the 'D'-state
2568 if not os.path.exists(i):
2569 prj_dir, pac_dir = getPrjPacPaths(i)
2570 if is_project_dir(prj_dir):
2571 prj = Project(prj_dir, False)
2572 if i in prj.pacs_broken:
2573 if prj.get_state(i) != 'A':
2574 prj.set_state(pac_dir, 'D')
2576 prj.del_package_node(i)
2577 print statfrmt('D', getTransActPath(i))
2579 prj.write_packages()
2580 pacs = findpacs(args)
2584 prj_dir, pac_dir = getPrjPacPaths(p.absdir)
2585 if is_project_dir(prj_dir):
2586 if conf.config['do_package_tracking']:
2587 prj = Project(prj_dir, False)
2588 prj.delPackage(p, opts.force)
2590 print "WARNING: package tracking is disabled, operation skipped !"
2592 pathn = getTransActPath(p.dir)
2593 for filename in p.todo:
2594 ret, state = p.delete_file(filename, opts.force)
2596 print statfrmt('D', os.path.join(pathn, filename))
2599 sys.exit('\'%s\' is not under version control' % filename)
2600 elif state in ['A', 'M'] and not opts.force:
2601 sys.exit('\'%s\' has local modifications (use --force to remove this file)' % filename)
2604 def do_resolved(self, subcmd, opts, *args):
2605 """${cmd_name}: Remove 'conflicted' state on working copy files
2607 If an upstream change can't be merged automatically, a file is put into
2608 in 'conflicted' ('C') state. Within the file, conflicts are marked with
2609 special <<<<<<< as well as ======== and >>>>>>> lines.
2611 After manually resolving all conflicting parts, use this command to
2612 remove the 'conflicted' state.
2614 Note: this subcommand does not semantically resolve conflicts or
2615 remove conflict markers; it merely removes the conflict-related
2616 artifact files and allows PATH to be committed again.
2619 osc resolved FILE [FILE...]
2624 raise oscerr.WrongArgs('Missing argument.\n\n' \
2625 + self.get_cmd_help('resolved'))
2627 args = parseargs(args)
2628 pacs = findpacs(args)
2631 for filename in p.todo:
2632 print 'Resolved conflicted state of "%s"' % filename
2633 p.clear_from_conflictlist(filename)
2636 @cmdln.alias('platforms')
2637 def do_repositories(self, subcmd, opts, *args):
2638 """${cmd_name}: Shows available repositories
2642 Shows all available repositories/build targets
2644 2. osc repositories <project>
2645 Shows the configured repositories/build targets of a project
2653 print '\n'.join(get_repositories_of_project(conf.config['apiurl'], project))
2655 print '\n'.join(get_repositories(conf.config['apiurl']))
2659 def do_results_meta(self, subcmd, opts, *args):
2660 print "Command results_meta is obsolete. Please use: osc results --xml"
2664 @cmdln.option('-l', '--last-build', action='store_true',
2665 help='show last build results (succeeded/failed/unknown)')
2666 @cmdln.option('-r', '--repo', action='append', default = [],
2667 help='Show results only for specified repo(s)')
2668 @cmdln.option('-a', '--arch', action='append', default = [],
2669 help='Show results only for specified architecture(s)')
2670 @cmdln.option('', '--xml', action='store_true',
2671 help='generate output in XML (former results_meta)')
2672 def do_rresults(self, subcmd, opts, *args):
2673 print "Command rresults is obsolete. Running 'osc results' instead"
2674 self.do_results('results', opts, *args)
2678 @cmdln.option('-f', '--force', action='store_true', default=False,
2679 help="Don't ask and delete files")
2680 def do_rremove(self, subcmd, opts, project, package, *files):
2681 """${cmd_name}: Remove source files from selected package
2688 if not '/' in project:
2689 raise oscerr.WrongArgs("Missing operand, type osc help rremove for help")
2692 project, package = project.split('/')
2696 resp = raw_input("rm: remove source file `%s' from `%s/%s'? (yY|nN) " % (file, project, package))
2697 if resp not in ('y', 'Y'):
2700 delete_files(conf.config['apiurl'], project, package, (file, ))
2701 except urllib2.HTTPError, e:
2703 print >>sys.stderr, e
2705 if e.code in [ 400, 403, 404, 500 ]:
2706 if '<summary>' in body:
2707 msg = body.split('<summary>')[1]
2708 msg = msg.split('</summary>')[0]
2709 print >>sys.stderr, msg
2714 @cmdln.option('-l', '--last-build', action='store_true',
2715 help='show last build results (succeeded/failed/unknown)')
2716 @cmdln.option('-r', '--repo', action='append', default = [],
2717 help='Show results only for specified repo(s)')
2718 @cmdln.option('-a', '--arch', action='append', default = [],
2719 help='Show results only for specified architecture(s)')
2720 @cmdln.option('', '--xml', action='store_true',
2721 help='generate output in XML (former results_meta)')
2722 def do_results(self, subcmd, opts, *args):
2723 """${cmd_name}: Shows the build results of a package
2726 osc results (inside working copy)
2727 osc results remote_project remote_package
2732 args = slash_split(args)
2734 apiurl = conf.config['apiurl']
2737 if is_project_dir(wd):
2741 opts.hide_legend = None
2742 opts.name_filter = None
2743 opts.status_filter = None
2744 opts.vertical = None
2745 self.do_prjresults('prjresults', opts, *args)
2748 project = store_read_project(wd)
2749 package = store_read_package(wd)
2750 apiurl = store_read_apiurl(wd)
2752 raise oscerr.WrongArgs('Too few arguments (required none or two)')
2754 raise oscerr.WrongArgs('Too many arguments (required none or two)')
2763 func = show_results_meta
2766 print delim.join(func(apiurl, project, package, opts.last_build, opts.repo, opts.arch))
2768 # WARNING: this function is also called by do_results. You need to set a default there
2769 # as well when adding a new option!
2770 @cmdln.option('-q', '--hide-legend', action='store_true',
2771 help='hide the legend')
2772 @cmdln.option('-c', '--csv', action='store_true',
2774 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2775 help='show only packages with buildstatus STATUS (see legend)')
2776 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2777 help='show only packages whose names match EXPR')
2778 @cmdln.option('-a', '--arch', metavar='ARCH',
2779 help='show results only for specified architecture(s)')
2780 @cmdln.option('-r', '--repo', metavar='REPO',
2781 help='show results only for specified repo(s)')
2782 @cmdln.option('-V', '--vertical', action='store_true',
2783 help='list packages vertically instead horizontally')
2785 def do_prjresults(self, subcmd, opts, *args):
2786 """${cmd_name}: Shows project-wide build results
2789 osc prjresults (inside working copy)
2790 osc prjresults PROJECT
2796 apiurl = conf.config['apiurl']
2800 raise oscerr.WrongArgs('Wrong number of arguments.')
2803 project = store_read_project(wd)
2804 apiurl = store_read_apiurl(wd)
2806 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))
2809 @cmdln.option('-q', '--hide-legend', action='store_true',
2810 help='hide the legend')
2811 @cmdln.option('-c', '--csv', action='store_true',
2813 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2814 help='show only packages with buildstatus STATUS (see legend)')
2815 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2816 help='show only packages whose names match EXPR')
2819 def do_rprjresults(self, subcmd, opts, *args):
2820 print "Command rprjresults is obsolete. Please use 'osc prjresults'"
2824 @cmdln.option('-s', '--start', metavar='START',
2825 help='get log starting from the offset')
2826 def do_buildlog(self, subcmd, opts, *args):
2827 """${cmd_name}: Shows the build log of a package
2829 Shows the log file of the build of a package. Can be used to follow the
2830 log while it is being written.
2831 Needs to be called from within a package directory.
2833 The arguments REPOSITORY and ARCH are the first two columns in the 'osc
2834 results' output. If the buildlog url is used buildlog command has the
2835 same behavior as remotebuildlog.
2837 ${cmd_usage} [REPOSITORY ARCH | BUILDLOGURL]
2841 repository = arch = None
2843 if len(args) == 1 and args[0].startswith('http'):
2844 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2847 package = store_read_package(wd)
2848 project = store_read_project(wd)
2849 apiurl = store_read_apiurl(wd)
2853 offset = int(opts.start)
2855 if not repository or not arch:
2859 repository = args[0]
2862 print_buildlog(apiurl, project, package, repository, arch, offset)
2865 def print_repos(self):
2868 if is_package_dir(wd):
2871 elif is_project_dir(wd):
2876 print 'Valid arguments for this %s are:' % str
2878 self.do_repos(None, None)
2880 raise oscerr.WrongArgs('Missing arguments')
2883 @cmdln.alias('rbuildlog')
2884 @cmdln.option('-s', '--start', metavar='START',
2885 help='get log starting from the offset')
2886 def do_remotebuildlog(self, subcmd, opts, *args):
2887 """${cmd_name}: Shows the build log of a package
2889 Shows the log file of the build of a package. Can be used to follow the
2890 log while it is being written.
2893 osc remotebuildlog project package repository arch
2895 osc remotebuildlog project/package/repository/arch
2897 osc remotebuildlog buildlogurl
2900 if len(args) == 1 and args[0].startswith('http'):
2901 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2903 args = slash_split(args)
2904 apiurl = conf.config['apiurl']
2906 raise oscerr.WrongArgs('Too few arguments.')
2908 raise oscerr.WrongArgs('Too many arguments.')
2910 project, package, repository, arch = args
2914 offset = int(opts.start)
2916 print_buildlog(apiurl, project, package, repository, arch, offset)
2919 @cmdln.option('-s', '--start', metavar='START',
2920 help='get log starting from offset')
2921 def do_localbuildlog(self, subcmd, opts, *args):
2922 """${cmd_name}: Shows the build log of a local buildchroot
2925 osc lbl [REPOSITORY ARCH]
2926 osc lbl # show log of newest last local build
2930 if conf.config['build-type']:
2931 # FIXME: raise Exception instead
2932 print >>sys.stderr, 'Not implemented for VMs'
2936 package = store_read_package('.')
2938 files = glob.glob(os.path.join(os.getcwd(), store, "_buildinfo-*"))
2941 raise oscerr.WrongArgs('No buildconfig found, please specify repo and arch manually.')
2945 if os.stat(f).st_mtime > os.stat(cfg).st_mtime:
2947 root = ET.parse(cfg).getroot()
2948 project = root.get("project")
2949 repo = root.get("repository")
2950 arch = root.find("arch").text
2951 elif len(args) == 2:
2952 project = store_read_project('.')
2953 package = store_read_package('.')
2957 if is_package_dir(os.curdir):
2959 raise oscerr.WrongArgs('Wrong number of arguments.')
2961 buildroot = os.environ.get('OSC_BUILD_ROOT', conf.config['build-root'])
2962 buildroot = buildroot % {'project': project, 'package': package,
2963 'repo': repo, 'arch': arch}
2966 offset = int(opts.start)
2967 logfile = os.path.join(buildroot, '.build.log')
2968 if not os.path.isfile(logfile):
2969 raise oscerr.OscIOError(None, 'logfile \'%s\' does not exist' % logfile)
2970 f = open(logfile, 'r')
2972 data = f.read(BUFSIZE)
2974 sys.stdout.write(data)
2975 data = f.read(BUFSIZE)
2979 def do_triggerreason(self, subcmd, opts, *args):
2980 """${cmd_name}: Show reason why a package got triggered to build
2982 The server decides when a package needs to get rebuild, this command
2983 shows the detailed reason for a package. A brief reason is also stored
2984 in the jobhistory, which can be accessed via "osc jobhistory".
2986 Trigger reasons might be:
2987 - new build (never build yet or rebuild manually forced)
2988 - source change (eg. on updating sources)
2989 - meta change (packages which are used for building have changed)
2990 - rebuild count sync (In case that it is configured to sync release numbers)
2992 usage in package or project directory:
2993 osc reason REPOSITORY ARCH
2994 osc reason PROJECT PACKAGE REPOSITORY ARCH
2999 args = slash_split(args)
3000 project = package = repository = arch = None
3005 if len(args) == 2: # 2
3006 if is_package_dir('.'):
3007 package = store_read_package(wd)
3009 raise oscerr.WrongArgs('package is not specified.')
3010 project = store_read_project(wd)
3011 apiurl = store_read_apiurl(wd)
3012 repository = args[0]
3014 elif len(args) == 4:
3015 apiurl = conf.config['apiurl']
3018 repository = args[2]
3021 raise oscerr.WrongArgs('Too many arguments.')
3023 print apiurl, project, package, repository, arch
3024 xml = show_package_trigger_reason(apiurl, project, package, repository, arch)
3025 root = ET.fromstring(xml)
3026 reason = root.find('explain').text
3028 if reason == "meta change":
3029 print "changed keys:"
3030 for package in root.findall('packagechange'):
3031 print " ", package.get('change'), package.get('key')
3034 # FIXME: the new osc syntax should allow to specify multiple packages
3035 # FIXME: the command should optionally use buildinfo data to show all dependencies
3036 @cmdln.alias('whatdependson')
3037 def do_dependson(self, subcmd, opts, *args):
3038 """${cmd_name}: Show the build dependencies
3040 The command dependson and whatdependson can be used to find out what
3041 will be triggered when a certain package changes.
3042 This is no guarantee, since the new build might have changed dependencies.
3044 dependson shows the build dependencies inside of a project, valid for a
3045 given repository and architecture.
3046 NOTE: to see all binary packages, which can trigger a build you need to
3047 refer the buildinfo, since this command shows only the dependencies
3048 inside of a project.
3050 The arguments REPOSITORY and ARCH can be taken from the first two columns
3051 of the 'osc repos' output.
3053 usage in package or project directory:
3054 osc dependson REPOSITORY ARCH
3055 osc whatdependson REPOSITORY ARCH
3058 osc dependson PROJECT [PACKAGE] REPOSITORY ARCH
3059 osc whatdependson PROJECT [PACKAGE] REPOSITORY ARCH
3064 args = slash_split(args)
3065 project = packages = repository = arch = reverse = None
3067 if len(args) < 2 and (is_package_dir('.') or is_project_dir('.')):
3071 raise oscerr.WrongArgs('Too many arguments.')
3073 if len(args) < 3: # 2
3074 if is_package_dir('.'):
3075 packages = [store_read_package(wd)]
3076 elif not is_project_dir('.'):
3077 raise oscerr.WrongArgs('Project and package is not specified.')
3078 project = store_read_project(wd)
3079 apiurl = store_read_apiurl(wd)
3080 repository = args[0]
3084 apiurl = conf.config['apiurl']
3086 repository = args[1]
3090 apiurl = conf.config['apiurl']
3092 packages = [args[1]]
3093 repository = args[2]