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 print >>sys.stderr, 'Aborted...'
788 raise oscerr.UserAbort()
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) ')
848 raise oscerr.UserAbort()
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'
903 raise oscerr.UserAbort()
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
1239 if opts.diff and r.actions[0].type != 'submit':
1240 raise oscerr.WrongOptions('\'--diff\' is not possible for request type: \'%s\'' % r.actions[0].type)
1243 print server_diff(conf.config['apiurl'],
1244 r.actions[0].dst_project, r.actions[0].dst_package, None,
1245 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, True)
1246 except urllib2.HTTPError, e:
1248 e.osc_msg = 'Diff not possible'
1250 # backward compatiblity: only a recent api/backend supports the missingok parameter
1252 print server_diff(conf.config['apiurl'],
1253 r.actions[0].dst_project, r.actions[0].dst_package, None,
1254 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, False)
1255 except urllib2.HTTPError, e:
1256 e.osc_msg = 'Diff not possible'
1260 elif cmd == 'checkout' or cmd == 'co':
1261 r = get_request(conf.config['apiurl'], reqid)
1262 submits = [ i for i in r.actions if i.type == 'submit' ]
1263 if not len(submits):
1264 raise oscerr.WrongArgs('\'checkout\' only works for \'submit\' requests')
1265 checkout_package(conf.config['apiurl'], submits[0].src_project, submits[0].src_package, \
1266 submits[0].src_rev, expand_link=True, prj_dir=submits[0].src_project)
1269 if not opts.message:
1270 opts.message = edit_message()
1271 state_map = {'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked'}
1272 # Change review state only
1273 if subcmd == 'review':
1274 if cmd in ['accept', 'decline']:
1275 r = change_review_state(conf.config['apiurl'],
1276 reqid, state_map[cmd], conf.config['user'], '', opts.message or '')
1278 # Change state of entire request
1279 elif cmd in ['accept', 'decline', 'wipe', 'revoke']:
1280 r = change_request_state(conf.config['apiurl'],
1281 reqid, state_map[cmd], opts.message or '')
1284 # editmeta and its aliases are all depracated
1285 @cmdln.alias("editprj")
1286 @cmdln.alias("createprj")
1287 @cmdln.alias("editpac")
1288 @cmdln.alias("createpac")
1289 @cmdln.alias("edituser")
1290 @cmdln.alias("usermeta")
1292 def do_editmeta(self, subcmd, opts, *args):
1295 Obsolete command to edit metadata. Use 'meta' now.
1297 See the help output of 'meta'.
1301 print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
1302 print >>sys.stderr, 'See \'osc help meta\'.'
1303 #self.do_help([None, 'meta'])
1307 @cmdln.option('-r', '--revision', metavar='rev',
1308 help='use the specified revision.')
1309 @cmdln.option('-u', '--unset', action='store_true',
1310 help='remove revision in link, it will point always to latest revision')
1311 def do_setlinkrev(self, subcmd, opts, *args):
1312 """${cmd_name}: Updates a revision number in a source link.
1314 This command adds or updates a specified revision number in a source link.
1315 The current revision of the source is used, if no revision number is specified.
1319 osc setlinkrev PROJECT [PACKAGE]
1323 args = slash_split(args)
1324 apiurl = conf.config['apiurl']
1327 p = findpacs(os.curdir)[0]
1332 sys.exit('Local directory is no checked out source link package, aborting')
1333 elif len(args) == 2:
1336 elif len(args) == 1:
1339 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1340 + self.get_cmd_help('setlinkrev'))
1343 packages = [ package ]
1345 packages = meta_get_packagelist(apiurl, project)
1348 print "setting revision for package", p
1352 rev, dummy = parseRevisionOption(opts.revision)
1353 set_link_rev(apiurl, project, p, rev)
1356 def do_linktobranch(self, subcmd, opts, *args):
1357 """${cmd_name}: Convert a package containing a classic link with patch to a branch
1359 This command tells the server to convert a _link with or without a project.diff
1360 to a branch. This is a full copy with a _link file pointing to the branched place.
1363 osc linktobranch # can be used in checked out package
1364 osc linktobranch PROJECT PACKAGE
1368 args = slash_split(args)
1371 project = store_read_project(wd)
1372 package = store_read_package(wd)
1373 apiurl = store_read_apiurl(wd)
1374 update_local_dir = True
1376 raise oscerr.WrongArgs('Too few arguments (required none or two)')
1378 raise oscerr.WrongArgs('Too many arguments (required none or two)')
1380 apiurl = conf.config['apiurl']
1383 update_local_dir = False
1386 link_to_branch(apiurl, project, package)
1387 if update_local_dir:
1389 pac.update(rev=pac.latest_rev())
1392 @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'],
1393 help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.')
1394 @cmdln.option('-c', '--current', action='store_true',
1395 help='link fixed against current revision.')
1396 @cmdln.option('-r', '--revision', metavar='rev',
1397 help='link the specified revision.')
1398 @cmdln.option('-f', '--force', action='store_true',
1399 help='overwrite an existing link file if it is there.')
1400 @cmdln.option('-d', '--disable-publish', action='store_true',
1401 help='disable publishing of the linked package')
1402 def do_linkpac(self, subcmd, opts, *args):
1403 """${cmd_name}: "Link" a package to another package
1405 A linked package is a clone of another package, but plus local
1406 modifications. It can be cross-project.
1408 The DESTPAC name is optional; the source packages' name will be used if
1411 Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
1413 To add a patch, add the patch as file and add it to the _link file.
1414 You can also specify text which will be inserted at the top of the spec file.
1416 See the examples in the _link file.
1419 osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1423 args = slash_split(args)
1425 if not args or len(args) < 3:
1426 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1427 + self.get_cmd_help('linkpac'))
1429 rev, dummy = parseRevisionOption(opts.revision)
1431 src_project = args[0]
1432 src_package = args[1]
1433 dst_project = args[2]
1435 dst_package = args[3]
1437 dst_package = src_package
1439 if src_project == dst_project and src_package == dst_package:
1440 raise oscerr.WrongArgs('Error: source and destination are the same.')
1442 if src_project == dst_project and not opts.cicount:
1443 # in this case, the user usually wants to build different spec
1444 # files from the same source
1445 opts.cicount = "copy"
1448 rev = show_upstream_rev(conf.config['apiurl'], src_project, src_package)
1450 if rev and not checkRevision(src_project, src_package, rev):
1451 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
1454 link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish)
1456 @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]',
1457 help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]')
1458 @cmdln.option('-d', '--disable-publish', action='store_true',
1459 help='disable publishing of the aggregated package')
1460 def do_aggregatepac(self, subcmd, opts, *args):
1461 """${cmd_name}: "Aggregate" a package to another package
1463 Aggregation of a package means that the build results (binaries) of a
1464 package are basically copied into another project.
1465 This can be used to make packages available from building that are
1466 needed in a project but available only in a different project. Note
1467 that this is done at the expense of disk space. See
1468 http://en.opensuse.org/Build_Service/Tips_and_Tricks#_link_and__aggregate
1469 for more information.
1471 The DESTPAC name is optional; the source packages' name will be used if
1475 osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1479 args = slash_split(args)
1481 if not args or len(args) < 3:
1482 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1483 + self.get_cmd_help('aggregatepac'))
1485 src_project = args[0]
1486 src_package = args[1]
1487 dst_project = args[2]
1489 dst_package = args[3]
1491 dst_package = src_package
1493 if src_project == dst_project and src_package == dst_package:
1494 raise oscerr.WrongArgs('Error: source and destination are the same.')
1498 for pair in opts.map_repo.split(','):
1499 src_tgt = pair.split('=')
1500 if len(src_tgt) != 2:
1501 raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo)
1502 repo_map[src_tgt[0]] = src_tgt[1]
1504 aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish)
1507 @cmdln.option('-c', '--client-side-copy', action='store_true',
1508 help='do a (slower) client-side copy')
1509 @cmdln.option('-k', '--keep-maintainers', action='store_true',
1510 help='keep original maintainers. Default is remove all and replace with the one calling the script.')
1511 @cmdln.option('-d', '--keep-develproject', action='store_true',
1512 help='keep develproject tag in the package metadata')
1513 @cmdln.option('-r', '--revision', metavar='rev',
1514 help='link the specified revision.')
1515 @cmdln.option('-t', '--to-apiurl', metavar='URL',
1516 help='URL of destination api server. Default is the source api server.')
1517 @cmdln.option('-m', '--message', metavar='TEXT',
1518 help='specify message TEXT')
1519 @cmdln.option('-e', '--expand', action='store_true',
1520 help='if the source package is a link then copy the expanded version of the link')
1521 def do_copypac(self, subcmd, opts, *args):
1522 """${cmd_name}: Copy a package
1524 A way to copy package to somewhere else.
1526 It can be done across buildservice instances, if the -t option is used.
1527 In that case, a client-side copy is implied.
1529 Using --client-side-copy always involves downloading all files, and
1530 uploading them to the target.
1532 The DESTPAC name is optional; the source packages' name will be used if
1536 osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1540 args = slash_split(args)
1542 if not args or len(args) < 3:
1543 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1544 + self.get_cmd_help('copypac'))
1546 src_project = args[0]
1547 src_package = args[1]
1548 dst_project = args[2]
1550 dst_package = args[3]
1552 dst_package = src_package
1554 src_apiurl = conf.config['apiurl']
1556 dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl)
1558 dst_apiurl = src_apiurl
1560 if src_project == dst_project and \
1561 src_package == dst_package and \
1562 src_apiurl == dst_apiurl:
1563 raise oscerr.WrongArgs('Source and destination are the same.')
1565 if src_apiurl != dst_apiurl:
1566 opts.client_side_copy = True
1568 rev, dummy = parseRevisionOption(opts.revision)
1571 comment = opts.message
1574 rev = show_upstream_rev(src_apiurl, src_project, src_package)
1575 comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev )
1577 r = copy_pac(src_apiurl, src_project, src_package,
1578 dst_apiurl, dst_project, dst_package,
1579 client_side_copy=opts.client_side_copy,
1580 keep_maintainers=opts.keep_maintainers,
1581 keep_develproject=opts.keep_develproject,
1588 @cmdln.option('-c', '--checkout', action='store_true',
1589 help='Checkout branched package afterwards ' \
1590 '(\'osc bco\' is a shorthand for this option)' )
1591 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
1592 help='Use this attribute to find affected packages (default is OBS:Maintained)')
1593 @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE',
1594 help='Use this attribute to find update projects (default is OBS:UpdateProject) ')
1595 def do_mbranch(self, subcmd, opts, *args):
1596 """${cmd_name}: Multiple branch of a package
1598 [See http://en.opensuse.org/Build_Service/Concepts/Maintenance for information
1601 This command is used for creating multiple links of defined version of a package
1602 in one project. This is esp. used for maintenance updates.
1604 The branched package will live in
1605 home:USERNAME:branches:ATTRIBUTE:PACKAGE
1606 if nothing else specified.
1609 osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ]
1612 args = slash_split(args)
1615 maintained_attribute = conf.config['maintained_attribute']
1616 maintained_update_project_attribute = conf.config['maintained_update_project_attribute']
1618 if not len(args) or len(args) > 2:
1619 raise oscerr.WrongArgs('Wrong number of arguments.')
1625 r = attribute_branch_pkg(conf.config['apiurl'], maintained_attribute, maintained_update_project_attribute, \
1629 print >>sys.stderr, 'ERROR: Attribute branch call came not back with a project.'
1632 print "Project " + r + " created."
1635 init_project_dir(conf.config['apiurl'], r, r)
1636 print statfrmt('A', r)
1639 for package in meta_get_packagelist(conf.config['apiurl'], r):
1641 checkout_package(conf.config['apiurl'], r, package, expand_link = True, prj_dir = r)
1643 print >>sys.stderr, 'Error while checkout package:\n', package
1645 if conf.config['verbose']:
1646 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1649 @cmdln.alias('branchco')
1651 @cmdln.alias('getpac')
1652 @cmdln.option('--nodevelproject', action='store_true',
1653 help='do not follow a defined devel project ' \
1654 '(primary project where a package is developed)')
1655 @cmdln.option('-c', '--checkout', action='store_true',
1656 help='Checkout branched package afterwards ' \
1657 '(\'osc bco\' is a shorthand for this option)' )
1658 @cmdln.option('-r', '--revision', metavar='rev',
1659 help='branch against a specific revision')
1660 @cmdln.option('-m', '--message', metavar='TEXT',
1661 help='specify message TEXT')
1662 def do_branch(self, subcmd, opts, *args):
1663 """${cmd_name}: Branch a package
1665 [See http://en.opensuse.org/Build_Service/Collaboration for information
1668 Create a source link from a package of an existing project to a new
1669 subproject of the requesters home project (home:branches:)
1671 The branched package will live in
1672 home:USERNAME:branches:PROJECT/PACKAGE
1673 if nothing else specified.
1675 With getpac or bco, the branched package will come from
1676 %(getpac_default_project)s
1677 if nothing else specified.
1680 osc branch SOURCEPROJECT SOURCEPACKAGE
1681 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT
1682 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE
1683 osc getpac SOURCEPACKAGE
1688 if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': opts.checkout = True
1689 args = slash_split(args)
1690 tproject = tpackage = None
1692 if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1:
1693 print >>sys.stderr, 'defaulting to %s/%s' % (conf.config['getpac_default_project'], args[0])
1694 # python has no args.unshift ???
1695 args = [ conf.config['getpac_default_project'] , args[0] ]
1697 if len(args) < 2 or len(args) > 4:
1698 raise oscerr.WrongArgs('Wrong number of arguments.')
1699 expected = 'home:%s:branches:%s' % (conf.config['user'], args[0])
1701 expected = tproject = args[2]
1705 if not opts.message:
1706 footer='please specify the purpose of your branch'
1707 template='This package was branched from %s in order to ...\n' % args[0]
1708 opts.message = edit_message(footer, template)
1710 exists, targetprj, targetpkg, srcprj, srcpkg = \
1711 branch_pkg(conf.config['apiurl'], args[0], args[1],
1712 nodevelproject=opts.nodevelproject, rev=opts.revision,
1713 target_project=tproject, target_package=tpackage,
1714 return_existing=opts.checkout, msg=opts.message or '')
1716 print >>sys.stderr, 'Using existing branch project: %s' % targetprj
1719 if not exists and (srcprj is not None and srcprj != args[0] or \
1720 srcprj is None and targetprj != expected):
1721 devloc = srcprj or targetprj
1722 if not srcprj and 'branches:' in targetprj:
1723 devloc = targetprj.split('branches:')[1]
1724 print '\nNote: The branch has been created of a different project,\n' \
1726 ' which is the primary location of where development for\n' \
1727 ' that package takes place.\n' \
1728 ' That\'s also where you would normally make changes against.\n' \
1729 ' A direct branch of the specified package can be forced\n' \
1730 ' with the --nodevelproject option.\n' % devloc
1732 package = tpackage or args[1]
1734 checkout_package(conf.config['apiurl'], targetprj, package,
1735 expand_link=True, prj_dir=targetprj)
1736 if conf.config['verbose']:
1737 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1740 if conf.get_configParser().get('general', 'apiurl') != conf.config['apiurl']:
1741 apiopt = '-A %s ' % conf.config['apiurl']
1742 print 'A working copy of the branched package can be checked out with:\n\n' \
1744 % (apiopt, targetprj, package)
1745 print_request_list(conf.config['apiurl'], args[0], args[1])
1747 print_request_list(conf.config['apiurl'], devloc, args[1])
1751 @cmdln.option('-f', '--force', action='store_true',
1752 help='deletes a package or an empty project')
1753 def do_rdelete(self, subcmd, opts, *args):
1754 """${cmd_name}: Delete a project or packages on the server.
1756 As a safety measure, project must be empty (i.e., you need to delete all
1757 packages first). If you are sure that you want to remove this project and all
1758 its packages use \'--force\' switch.
1761 osc rdelete -f PROJECT
1762 osc rdelete PROJECT PACKAGE [PACKAGE ...]
1767 args = slash_split(args)
1769 raise oscerr.WrongArgs('Missing argument.')
1775 # careful: if pkg is an empty string, the package delete request results
1776 # into a project delete request - which works recursively...
1778 delete_package(conf.config['apiurl'], prj, pkg)
1779 elif len(meta_get_packagelist(conf.config['apiurl'], prj)) >= 1 and not opts.force:
1780 print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
1781 'If you are sure that you want to remove this project and all its ' \
1782 'packages use the \'--force\' switch'
1785 delete_project(conf.config['apiurl'], prj)
1788 def do_deletepac(self, subcmd, opts, *args):
1789 print """${cmd_name} is obsolete !
1792 osc delete for checked out packages or projects
1794 osc rdelete for server side operations."""
1799 @cmdln.option('-f', '--force', action='store_true',
1800 help='deletes a project and its packages')
1801 def do_deleteprj(self, subcmd, opts, project):
1802 """${cmd_name} is obsolete !
1809 @cmdln.alias('metafromspec')
1810 @cmdln.option('', '--specfile', metavar='FILE',
1811 help='Path to specfile. (if you pass more than working copy this option is ignored)')
1812 def do_updatepacmetafromspec(self, subcmd, opts, *args):
1813 """${cmd_name}: Update package meta information from a specfile
1815 ARG, if specified, is a package working copy.
1821 args = parseargs(args)
1822 if opts.specfile and len(args) == 1:
1823 specfile = opts.specfile
1826 pacs = findpacs(args)
1828 p.read_meta_from_spec(specfile)
1829 p.update_package_meta()
1833 @cmdln.option('-c', '--change', metavar='rev',
1834 help='the change made by revision rev (like -r rev-1:rev).'
1835 'If rev is negative this is like -r rev:rev-1.')
1836 @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
1837 help='If rev1 is specified it will compare your working copy against '
1838 'the revision (rev1) on the server. '
1839 'If rev1 and rev2 are specified it will compare rev1 against rev2 '
1840 '(NOTE: changes in your working copy are ignored in this case)')
1841 @cmdln.option('-p', '--plain', action='store_true',
1842 help='output the diff in plain (not unified) diff format')
1843 @cmdln.option('--missingok', action='store_true',
1844 help='do not fail if the source or target project/package does not exist on the server')
1845 def do_diff(self, subcmd, opts, *args):
1846 """${cmd_name}: Generates a diff
1848 Generates a diff, comparing local changes against the repository
1851 ARG, specified, is a filename to include in the diff.
1857 args = parseargs(args)
1858 pacs = findpacs(args)
1862 rev = int(opts.change)
1872 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1875 rev1, rev2 = parseRevisionOption(opts.revision)
1879 diff += ''.join(make_diff(pac, rev1))
1881 diff += server_diff(pac.apiurl, pac.prjname, pac.name, rev1,
1882 pac.prjname, pac.name, rev2, not opts.plain, opts.missingok)
1887 @cmdln.option('--oldprj', metavar='OLDPRJ',
1888 help='project to compare against'
1889 ' (deprecated, use 3 argument form)')
1890 @cmdln.option('--oldpkg', metavar='OLDPKG',
1891 help='package to compare against'
1892 ' (deprecated, use 3 argument form)')
1893 @cmdln.option('-r', '--revision', metavar='N[:M]',
1894 help='revision id, where N = old revision and M = new revision')
1895 @cmdln.option('-p', '--plain', action='store_true',
1896 help='output the diff in plain (not unified) diff format')
1897 @cmdln.option('-c', '--change', metavar='rev',
1898 help='the change made by revision rev (like -r rev-1:rev). '
1899 'If rev is negative this is like -r rev:rev-1.')
1900 @cmdln.option('--missingok', action='store_true',
1901 help='do not fail if the source or target project/package does not exist on the server')
1902 def do_rdiff(self, subcmd, opts, *args):
1903 """${cmd_name}: Server-side "pretty" diff of two packages
1905 Compares two packages (three or four arguments) or shows the
1906 changes of a specified revision of a package (two arguments)
1908 If no revision is specified the latest revision is used.
1910 Note that this command doesn't return a normal diff (which could be
1911 applied as patch), but a "pretty" diff, which also compares the content
1916 osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC]
1917 osc ${cmd_name} PROJECT PACKAGE
1921 args = slash_split(args)
1932 new_project = args[0]
1933 new_package = args[1]
1935 old_project = opts.oldprj
1937 old_package = opts.oldpkg
1938 elif len(args) == 3 or len(args) == 4:
1939 if opts.oldprj or opts.oldpkg:
1940 raise oscerr.WrongArgs('--oldpkg and --oldprj are only valid with two arguments')
1941 old_project = args[0]
1942 new_package = old_package = args[1]
1943 new_project = args[2]
1945 new_package = args[3]
1947 raise oscerr.WrongArgs('Wrong number of arguments')
1952 rev = int(opts.change)
1962 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1966 rev1, rev2 = parseRevisionOption(opts.revision)
1968 rdiff = server_diff(conf.config['apiurl'],
1969 old_project, old_package, rev1,
1970 new_project, new_package, rev2, not opts.plain, opts.missingok)
1975 def do_install(self, subcmd, opts, *args):
1976 """${cmd_name}: install a package after build via zypper in -r
1978 Not implemented yet. Use osc repourls,
1979 select the url you best like (standard),
1980 chop off after the last /, this should work with zypper.
1987 args = slash_split(args)
1988 args = expand_proj_pack(args)
1991 ## if there is only one argument, and it ends in .ymp
1992 ## then fetch it, Parse XML to get the first
1993 ## metapackage.group.repositories.repository.url
1994 ## and construct zypper cmd's for all
1995 ## metapackage.group.software.item.name
1997 ## if args[0] is already an url, the use it as is.
1999 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])
2000 print self.do_install.__doc__
2001 print "Example: \n" + cmd
2004 def do_repourls(self, subcmd, opts, *args):
2005 """${cmd_name}: Shows URLs of .repo files
2007 Shows URLs on which to access the project .repos files (yum-style
2008 metadata) on download.opensuse.org.
2011 osc repourls [PROJECT]
2016 apiurl = conf.config['apiurl']
2020 elif len(args) == 0:
2021 project = store_read_project('.')
2022 apiurl = store_read_apiurl('.')
2024 raise oscerr.WrongArgs('Wrong number of arguments')
2026 # XXX: API should somehow tell that
2027 url_tmpl = 'http://download.opensuse.org/repositories/%s/%s/%s.repo'
2028 repos = get_repositories_of_project(apiurl, project)
2030 print url_tmpl % (project.replace(':', ':/'), repo, project)
2033 @cmdln.option('-r', '--revision', metavar='rev',
2034 help='checkout the specified revision. '
2035 'NOTE: if you checkout the complete project '
2036 'this option is ignored!')
2037 @cmdln.option('-e', '--expand-link', action='store_true',
2038 help='if a package is a link, check out the expanded '
2039 'sources (no-op, since this became the default)')
2040 @cmdln.option('-u', '--unexpand-link', action='store_true',
2041 help='if a package is a link, check out the _link file ' \
2042 'instead of the expanded sources')
2043 @cmdln.option('-c', '--current-dir', action='store_true',
2044 help='place PACKAGE folder in the current directory' \
2045 'instead of a PROJECT/PACKAGE directory')
2046 @cmdln.option('-s', '--source-service-files', action='store_true',
2047 help='server side generated files of source services' \
2048 'gets downloaded as well' )
2049 @cmdln.option('-l', '--limit-size', metavar='limit_size',
2050 help='Skip all files with a given size')
2052 def do_checkout(self, subcmd, opts, *args):
2053 """${cmd_name}: Check out content from the repository
2055 Check out content from the repository server, creating a local working
2058 When checking out a single package, the option --revision can be used
2059 to specify a revision of the package to be checked out.
2061 When a package is a source link, then it will be checked out in
2062 expanded form. If --unexpand-link option is used, the checkout will
2063 instead produce the raw _link file plus patches.
2066 osc co PROJECT [PACKAGE] [FILE]
2067 osc co PROJECT # entire project
2068 osc co PROJECT PACKAGE # a package
2069 osc co PROJECT PACKAGE FILE # single file -> to current dir
2071 while inside a project directory:
2072 osc co PACKAGE # check out PACKAGE from project
2077 if opts.unexpand_link:
2081 if opts.source_service_files:
2082 service_files = True
2084 service_files = False
2086 args = slash_split(args)
2087 project = package = filename = None
2088 apiurl = conf.config['apiurl']
2090 project = project_dir = args[0]
2096 if args and len(args) == 1:
2097 localdir = os.getcwd()
2098 if is_project_dir(localdir):
2099 project = store_read_project(localdir)
2100 project_dir = localdir
2102 apiurl = store_read_apiurl(localdir)
2104 rev, dummy = parseRevisionOption(opts.revision)
2108 if rev and rev != "latest" and not checkRevision(project, package, rev):
2109 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2113 get_source_file(apiurl, project, package, filename, revision=rev, progress_obj=self.download_progress)
2116 if opts.current_dir:
2118 checkout_package(apiurl, project, package, rev, expand_link=expand_link, \
2119 prj_dir=project_dir, service_files=service_files, progress_obj=self.download_progress, limit_size=opts.limit_size)
2120 print_request_list(apiurl, project, package)
2124 if sys.platform[:3] == 'win':
2125 prj_dir = prj_dir.replace(':', ';')
2126 if os.path.exists(prj_dir):
2127 sys.exit('osc: project \'%s\' already exists' % project)
2129 # check if the project does exist (show_project_meta will throw an exception)
2130 show_project_meta(apiurl, project)
2132 init_project_dir(apiurl, prj_dir, project)
2133 print statfrmt('A', prj_dir)
2136 for package in meta_get_packagelist(apiurl, project):
2138 checkout_package(apiurl, project, package, expand_link = expand_link, \
2139 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress, limit_size=opts.limit_size)
2140 except oscerr.LinkExpandError, e:
2141 print >>sys.stderr, 'Link cannot be expanded:\n', e
2142 print >>sys.stderr, 'Use "osc repairlink" for fixing merge conflicts:\n'
2143 # check out in unexpanded form at least
2144 checkout_package(apiurl, project, package, expand_link = False, \
2145 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress, limit_size=opts.limit_size)
2146 print_request_list(apiurl, project)
2149 raise oscerr.WrongArgs('Missing argument.\n\n' \
2150 + self.get_cmd_help('checkout'))
2153 @cmdln.option('-q', '--quiet', action='store_true',
2154 help='print as little as possible')
2155 @cmdln.option('-v', '--verbose', action='store_true',
2156 help='print extra information')
2158 def do_status(self, subcmd, opts, *args):
2159 """${cmd_name}: Show status of files in working copy
2161 Show the status of files in a local working copy, indicating whether
2162 files have been changed locally, deleted, added, ...
2164 The first column in the output specifies the status and is one of the
2165 following characters:
2166 ' ' no modifications
2171 '?' item is not under version control
2172 '!' item is missing (removed by non-osc command) or incomplete
2177 osc st file1 file2 ...
2180 osc status [OPTS] [PATH...]
2184 args = parseargs(args)
2186 # storage for single Package() objects
2188 # storage for a project dir ( { prj_instance : [ package objects ] } )
2191 # when 'status' is run inside a project dir, it should
2192 # stat all packages existing in the wc
2193 if is_project_dir(arg):
2194 prj = Project(arg, False)
2196 if conf.config['do_package_tracking']:
2198 for pac in prj.pacs_have:
2199 # we cannot create package objects if the dir does not exist
2200 if not pac in prj.pacs_broken:
2201 prjpacs[prj].append(os.path.join(arg, pac))
2203 pacpaths += [arg + '/' + n for n in prj.pacs_have]
2204 elif is_package_dir(arg):
2205 pacpaths.append(arg)
2206 elif os.path.isfile(arg):
2207 pacpaths.append(arg)
2209 msg = '\'%s\' is neither a project or a package directory' % arg
2210 raise oscerr.NoWorkingCopy, msg
2212 # process single packages
2213 lines = getStatus(findpacs(pacpaths), None, opts.verbose, opts.quiet)
2214 # process project dirs
2215 for prj, pacs in prjpacs.iteritems():
2216 lines += getStatus(findpacs(pacs), prj, opts.verbose, opts.quiet)
2218 print '\n'.join(lines)
2221 def do_add(self, subcmd, opts, *args):
2222 """${cmd_name}: Mark files to be added upon the next commit
2225 osc add FILE [FILE...]
2229 raise oscerr.WrongArgs('Missing argument.\n\n' \
2230 + self.get_cmd_help('add'))
2232 filenames = parseargs(args)
2236 def do_mkpac(self, subcmd, opts, *args):
2237 """${cmd_name}: Create a new package under version control
2240 osc mkpac new_package
2243 if not conf.config['do_package_tracking']:
2244 print >>sys.stderr, "to use this feature you have to enable \'do_package_tracking\' " \
2245 "in the [general] section in the configuration file"
2249 raise oscerr.WrongArgs('Wrong number of arguments.')
2251 createPackageDir(args[0])
2253 @cmdln.option('-r', '--recursive', action='store_true',
2254 help='If CWD is a project dir then scan all package dirs as well')
2256 def do_addremove(self, subcmd, opts, *args):
2257 """${cmd_name}: Adds new files, removes disappeared files
2259 Adds all files new in the local copy, and removes all disappeared files.
2261 ARG, if specified, is a package working copy.
2267 args = parseargs(args)
2269 for arg in arg_list:
2270 if is_project_dir(arg) and conf.config['do_package_tracking']:
2271 prj = Project(arg, False)
2272 for pac in prj.pacs_unvers:
2273 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2274 if os.path.isdir(pac_dir):
2275 addFiles([pac_dir], prj)
2276 for pac in prj.pacs_broken:
2277 if prj.get_state(pac) != 'D':
2278 prj.set_state(pac, 'D')
2279 print statfrmt('D', getTransActPath(os.path.join(prj.dir, pac)))
2281 for pac in prj.pacs_have:
2282 state = prj.get_state(pac)
2283 if state != None and state != 'D':
2284 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2285 args.append(pac_dir)
2287 prj.write_packages()
2288 elif is_project_dir(arg):
2289 print >>sys.stderr, 'osc: addremove is not supported in a project dir unless ' \
2290 '\'do_package_tracking\' is enabled in the configuration file'
2293 pacs = findpacs(args)
2295 p.todo = p.filenamelist + p.filenamelist_unvers
2297 for filename in p.todo:
2298 if os.path.isdir(filename):
2300 # ignore foo.rXX, foo.mine for files which are in 'C' state
2301 if os.path.splitext(filename)[0] in p.in_conflict:
2303 state = p.status(filename)
2306 # TODO: should ignore typical backup files suffix ~ or .orig
2308 print statfrmt('A', getTransActPath(os.path.join(p.dir, filename)))
2310 p.put_on_deletelist(filename)
2311 p.write_deletelist()
2312 os.unlink(os.path.join(p.storedir, filename))
2313 print statfrmt('D', getTransActPath(os.path.join(p.dir, filename)))
2318 @cmdln.alias('checkin')
2319 @cmdln.option('-m', '--message', metavar='TEXT',
2320 help='specify log message TEXT')
2321 @cmdln.option('-F', '--file', metavar='FILE',
2322 help='read log message from FILE')
2323 @cmdln.option('-f', '--force', default=False, action="store_true",
2324 help='force commit - do not tests a file list')
2325 def do_commit(self, subcmd, opts, *args):
2326 """${cmd_name}: Upload content to the repository server
2328 Upload content which is changed in your working copy, to the repository
2331 Optionally checks the state of a working copy, if found a file with
2332 unknown state, it requests an user input:
2333 * skip - don't change anything, just move to another file
2334 * remove - remove a file from dir
2335 * edit file list - edit filelist using EDITOR
2336 * commit - don't check anything and commit package
2337 * abort - abort commit - this is default value
2338 This can be supressed by check_filelist config item, or -f/--force
2339 command line option.
2342 osc ci # current dir
2344 osc ci file1 file2 ...
2350 args = parseargs(args)
2357 msg = open(opts.file).read()
2359 sys.exit('could not open file \'%s\'.' % opts.file)
2362 for arg in arg_list:
2363 if conf.config['do_package_tracking'] and is_project_dir(arg):
2364 Project(arg).commit(msg=msg)
2366 msg = edit_message()
2369 pacs = findpacs(args)
2371 if conf.config['check_filelist'] and not opts.force:
2372 check_filelist_before_commit(pacs)
2375 template = store_read_file(os.path.abspath('.'), '_commit_msg')
2376 # open editor for commit message
2377 # but first, produce status and diff to append to the template
2381 changed = getStatus([pac], quiet=True)
2384 diffs += ['\nDiff for working copy: %s' % pac.dir]
2385 diffs += make_diff(pac, 0)
2386 lines.extend(get_commit_message_template(pac))
2387 if template == None:
2388 template='\n'.join(lines)
2389 # if footer is empty, there is nothing to commit, and no edit needed.
2391 msg = edit_message(footer='\n'.join(footer), template=template)
2394 store_write_string(os.path.abspath('.'), '_commit_msg', msg)
2396 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2398 if conf.config['do_package_tracking'] and len(pacs) > 0:
2402 # it is possible to commit packages from different projects at the same
2403 # time: iterate over all pacs and put each pac to the right project in the dict
2405 path = os.path.normpath(os.path.join(pac.dir, os.pardir))
2406 if is_project_dir(path):
2407 pac_path = os.path.basename(os.path.normpath(pac.absdir))
2408 prj_paths.setdefault(path, []).append(pac_path)
2409 files[pac_path] = pac.todo
2411 single_paths.append(pac.dir)
2412 for prj, packages in prj_paths.iteritems():
2413 Project(prj).commit(tuple(packages), msg, files)
2414 for pac in single_paths:
2415 Package(pac).commit(msg)
2420 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2422 @cmdln.option('-r', '--revision', metavar='REV',
2423 help='update to specified revision (this option will be ignored '
2424 'if you are going to update the complete project or more than '
2426 @cmdln.option('-u', '--unexpand-link', action='store_true',
2427 help='if a package is an expanded link, update to the raw _link file')
2428 @cmdln.option('-e', '--expand-link', action='store_true',
2429 help='if a package is a link, update to the expanded sources')
2430 @cmdln.option('-s', '--source-service-files', action='store_true',
2431 help='Use server side generated sources instead of local generation.' )
2432 @cmdln.option('-l', '--limit-size', metavar='limit_size',
2433 help='Skip all files with a given size')
2435 def do_update(self, subcmd, opts, *args):
2436 """${cmd_name}: Update a working copy
2441 If the current working directory is a package, update it.
2442 If the directory is a project directory, update all contained
2443 packages, AND check out newly added packages.
2445 To update only checked out packages, without checking out new
2446 ones, you might want to use "osc up *" from within the project
2450 Update the packages specified by the path argument(s)
2452 When --expand-link is used with source link packages, the expanded
2453 sources will be checked out. Without this option, the _link file and
2454 patches will be checked out. The option --unexpand-link can be used to
2455 switch back to the "raw" source with a _link file plus patch(es).
2461 if (opts.expand_link and opts.unexpand_link) \
2462 or (opts.expand_link and opts.revision) \
2463 or (opts.unexpand_link and opts.revision):
2464 raise oscerr.WrongOptions('Sorry, the options --expand-link, --unexpand-link and '
2465 '--revision are mutually exclusive.')
2467 if opts.source_service_files: service_files = True
2468 else: service_files = False
2470 args = parseargs(args)
2473 for arg in arg_list:
2474 if is_project_dir(arg):
2475 prj = Project(arg, progress_obj=self.download_progress)
2477 if conf.config['do_package_tracking']:
2478 prj.update(expand_link=opts.expand_link,
2479 unexpand_link=opts.unexpand_link)
2482 # if not tracking package, and 'update' is run inside a project dir,
2483 # it should do the following:
2484 # (a) update all packages
2485 args += prj.pacs_have
2486 # (b) fetch new packages
2487 prj.checkout_missing_pacs(expand_link = not opts.unexpand_link)
2489 print_request_list(prj.apiurl, prj.name)
2492 pacs = findpacs(args, progress_obj=self.download_progress)
2494 if opts.revision and len(args) == 1:
2495 rev, dummy = parseRevisionOption(opts.revision)
2496 if not checkRevision(pacs[0].prjname, pacs[0].name, rev, pacs[0].apiurl):
2497 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2504 print 'Updating %s' % p.name
2506 # FIXME: ugly workaround for #399247
2507 if opts.expand_link or opts.unexpand_link:
2508 if [ i for i in p.filenamelist+p.filenamelist_unvers if p.status(i) != ' ' and p.status(i) != '?']:
2509 print >>sys.stderr, 'osc: cannot expand/unexpand because your working ' \
2510 'copy has local modifications.\nPlease revert/commit them ' \
2515 if opts.expand_link and p.islink() and not p.isexpanded():
2516 if p.haslinkerror():
2518 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev)
2520 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev, linkrev="base")
2523 rev = p.linkinfo.xsrcmd5
2524 print 'Expanding to rev', rev
2525 elif opts.unexpand_link and p.islink() and p.isexpanded():
2526 print 'Unexpanding to rev', p.linkinfo.lsrcmd5
2527 rev = p.linkinfo.lsrcmd5
2528 elif p.islink() and p.isexpanded():
2529 rev = p.latest_rev()
2531 p.update(rev, service_files, opts.limit_size)
2532 if opts.unexpand_link:
2535 print_request_list(p.apiurl, p.prjname, p.name)
2538 @cmdln.option('-f', '--force', action='store_true',
2539 help='forces removal of entire package and its files')
2542 @cmdln.alias('remove')
2543 def do_delete(self, subcmd, opts, *args):
2544 """${cmd_name}: Mark files or package directories to be deleted upon the next 'checkin'
2547 cd .../PROJECT/PACKAGE
2548 osc delete FILE [...]
2550 osc delete PACKAGE [...]
2552 This command works on check out copies. Use "rdelete" for working on server
2553 side only. This is needed for removing the entire project.
2555 As a safety measure, projects must be empty (i.e., you need to delete all
2558 If you are sure that you want to remove a package and all
2559 its files use \'--force\' switch. Sometimes this also works without --force.
2565 raise oscerr.WrongArgs('Missing argument.\n\n' \
2566 + self.get_cmd_help('delete'))
2568 args = parseargs(args)
2569 # check if args contains a package which was removed by
2570 # a non-osc command and mark it with the 'D'-state
2573 if not os.path.exists(i):
2574 prj_dir, pac_dir = getPrjPacPaths(i)
2575 if is_project_dir(prj_dir):
2576 prj = Project(prj_dir, False)
2577 if i in prj.pacs_broken:
2578 if prj.get_state(i) != 'A':
2579 prj.set_state(pac_dir, 'D')
2581 prj.del_package_node(i)
2582 print statfrmt('D', getTransActPath(i))
2584 prj.write_packages()
2585 pacs = findpacs(args)
2589 prj_dir, pac_dir = getPrjPacPaths(p.absdir)
2590 if is_project_dir(prj_dir):
2591 if conf.config['do_package_tracking']:
2592 prj = Project(prj_dir, False)
2593 prj.delPackage(p, opts.force)
2595 print "WARNING: package tracking is disabled, operation skipped !"
2597 pathn = getTransActPath(p.dir)
2598 for filename in p.todo:
2599 ret, state = p.delete_file(filename, opts.force)
2601 print statfrmt('D', os.path.join(pathn, filename))
2604 sys.exit('\'%s\' is not under version control' % filename)
2605 elif state in ['A', 'M'] and not opts.force:
2606 sys.exit('\'%s\' has local modifications (use --force to remove this file)' % filename)
2609 def do_resolved(self, subcmd, opts, *args):
2610 """${cmd_name}: Remove 'conflicted' state on working copy files
2612 If an upstream change can't be merged automatically, a file is put into
2613 in 'conflicted' ('C') state. Within the file, conflicts are marked with
2614 special <<<<<<< as well as ======== and >>>>>>> lines.
2616 After manually resolving all conflicting parts, use this command to
2617 remove the 'conflicted' state.
2619 Note: this subcommand does not semantically resolve conflicts or
2620 remove conflict markers; it merely removes the conflict-related
2621 artifact files and allows PATH to be committed again.
2624 osc resolved FILE [FILE...]
2629 raise oscerr.WrongArgs('Missing argument.\n\n' \
2630 + self.get_cmd_help('resolved'))
2632 args = parseargs(args)
2633 pacs = findpacs(args)
2636 for filename in p.todo:
2637 print 'Resolved conflicted state of "%s"' % filename
2638 p.clear_from_conflictlist(filename)
2641 @cmdln.alias('platforms')
2642 def do_repositories(self, subcmd, opts, *args):
2643 """${cmd_name}: Shows available repositories
2647 Shows all available repositories/build targets
2649 2. osc repositories <project>
2650 Shows the configured repositories/build targets of a project
2658 print '\n'.join(get_repositories_of_project(conf.config['apiurl'], project))
2660 print '\n'.join(get_repositories(conf.config['apiurl']))
2664 def do_results_meta(self, subcmd, opts, *args):
2665 print "Command results_meta is obsolete. Please use: osc results --xml"
2669 @cmdln.option('-l', '--last-build', action='store_true',
2670 help='show last build results (succeeded/failed/unknown)')
2671 @cmdln.option('-r', '--repo', action='append', default = [],
2672 help='Show results only for specified repo(s)')
2673 @cmdln.option('-a', '--arch', action='append', default = [],
2674 help='Show results only for specified architecture(s)')
2675 @cmdln.option('', '--xml', action='store_true',
2676 help='generate output in XML (former results_meta)')
2677 def do_rresults(self, subcmd, opts, *args):
2678 print "Command rresults is obsolete. Running 'osc results' instead"
2679 self.do_results('results', opts, *args)
2683 @cmdln.option('-f', '--force', action='store_true', default=False,
2684 help="Don't ask and delete files")
2685 def do_rremove(self, subcmd, opts, project, package, *files):
2686 """${cmd_name}: Remove source files from selected package
2693 if not '/' in project:
2694 raise oscerr.WrongArgs("Missing operand, type osc help rremove for help")
2697 project, package = project.split('/')
2701 resp = raw_input("rm: remove source file `%s' from `%s/%s'? (yY|nN) " % (file, project, package))
2702 if resp not in ('y', 'Y'):
2705 delete_files(conf.config['apiurl'], project, package, (file, ))
2706 except urllib2.HTTPError, e:
2708 print >>sys.stderr, e
2710 if e.code in [ 400, 403, 404, 500 ]:
2711 if '<summary>' in body:
2712 msg = body.split('<summary>')[1]
2713 msg = msg.split('</summary>')[0]
2714 print >>sys.stderr, msg
2719 @cmdln.option('-l', '--last-build', action='store_true',
2720 help='show last build results (succeeded/failed/unknown)')
2721 @cmdln.option('-r', '--repo', action='append', default = [],
2722 help='Show results only for specified repo(s)')
2723 @cmdln.option('-a', '--arch', action='append', default = [],
2724 help='Show results only for specified architecture(s)')
2725 @cmdln.option('', '--xml', action='store_true', default=False,
2726 help='generate output in XML (former results_meta)')
2727 @cmdln.option('', '--csv', action='store_true', default=False,
2728 help='generate output in CSV format')
2729 @cmdln.option('', '--format', default='%(repository)s|%(arch)s|%(state)s|%(dirty)s|%(code)s|%(details)s',
2730 help='format string for csv output')
2731 def do_results(self, subcmd, opts, *args):
2732 """${cmd_name}: Shows the build results of a package
2735 osc results (inside working copy)
2736 osc results remote_project remote_package
2741 args = slash_split(args)
2743 apiurl = conf.config['apiurl']
2746 if is_project_dir(wd):
2750 opts.hide_legend = None
2751 opts.name_filter = None
2752 opts.status_filter = None
2753 opts.vertical = None
2754 self.do_prjresults('prjresults', opts, *args)
2757 project = store_read_project(wd)
2758 package = store_read_package(wd)
2759 apiurl = store_read_apiurl(wd)
2761 raise oscerr.WrongArgs('Too few arguments (required none or two)')
2763 raise oscerr.WrongArgs('Too many arguments (required none or two)')
2768 if opts.xml and opts.csv:
2769 raise oscerr.WrongOptions("--xml and --csv are mutual exclusive")
2772 func = show_results_meta
2776 return format_results(get_package_results(*args), opts.format)
2783 print delim.join(func(apiurl, project, package, opts.last_build, opts.repo, opts.arch))
2785 # WARNING: this function is also called by do_results. You need to set a default there
2786 # as well when adding a new option!
2787 @cmdln.option('-q', '--hide-legend', action='store_true',
2788 help='hide the legend')
2789 @cmdln.option('-c', '--csv', action='store_true',
2791 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2792 help='show only packages with buildstatus STATUS (see legend)')
2793 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2794 help='show only packages whose names match EXPR')
2795 @cmdln.option('-a', '--arch', metavar='ARCH',
2796 help='show results only for specified architecture(s)')
2797 @cmdln.option('-r', '--repo', metavar='REPO',
2798 help='show results only for specified repo(s)')
2799 @cmdln.option('-V', '--vertical', action='store_true',
2800 help='list packages vertically instead horizontally')
2802 def do_prjresults(self, subcmd, opts, *args):
2803 """${cmd_name}: Shows project-wide build results
2806 osc prjresults (inside working copy)
2807 osc prjresults PROJECT
2813 apiurl = conf.config['apiurl']
2817 raise oscerr.WrongArgs('Wrong number of arguments.')
2820 project = store_read_project(wd)
2821 apiurl = store_read_apiurl(wd)
2823 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))
2826 @cmdln.option('-q', '--hide-legend', action='store_true',
2827 help='hide the legend')
2828 @cmdln.option('-c', '--csv', action='store_true',
2830 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2831 help='show only packages with buildstatus STATUS (see legend)')
2832 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2833 help='show only packages whose names match EXPR')
2836 def do_rprjresults(self, subcmd, opts, *args):
2837 print "Command rprjresults is obsolete. Please use 'osc prjresults'"
2841 @cmdln.option('-s', '--start', metavar='START',
2842 help='get log starting from the offset')
2843 def do_buildlog(self, subcmd, opts, *args):
2844 """${cmd_name}: Shows the build log of a package
2846 Shows the log file of the build of a package. Can be used to follow the
2847 log while it is being written.
2848 Needs to be called from within a package directory.
2850 The arguments REPOSITORY and ARCH are the first two columns in the 'osc
2851 results' output. If the buildlog url is used buildlog command has the
2852 same behavior as remotebuildlog.
2854 ${cmd_usage} [REPOSITORY ARCH | BUILDLOGURL]
2858 repository = arch = None
2860 if len(args) == 1 and args[0].startswith('http'):
2861 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2864 package = store_read_package(wd)
2865 project = store_read_project(wd)
2866 apiurl = store_read_apiurl(wd)
2870 offset = int(opts.start)
2872 if not repository or not arch:
2876 repository = args[0]
2879 print_buildlog(apiurl, project, package, repository, arch, offset)
2882 def print_repos(self):
2885 if is_package_dir(wd):
2888 elif is_project_dir(wd):
2893 print 'Valid arguments for this %s are:' % str
2895 self.do_repos(None, None)
2897 raise oscerr.WrongArgs('Missing arguments')
2900 @cmdln.alias('rbuildlog')
2901 @cmdln.option('-s', '--start', metavar='START',
2902 help='get log starting from the offset')
2903 def do_remotebuildlog(self, subcmd, opts, *args):
2904 """${cmd_name}: Shows the build log of a package
2906 Shows the log file of the build of a package. Can be used to follow the
2907 log while it is being written.
2910 osc remotebuildlog project package repository arch
2912 osc remotebuildlog project/package/repository/arch
2914 osc remotebuildlog buildlogurl
2917 if len(args) == 1 and args[0].startswith('http'):
2918 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2920 args = slash_split(args)
2921 apiurl = conf.config['apiurl']
2923 raise oscerr.WrongArgs('Too few arguments.')
2925 raise oscerr.WrongArgs('Too many arguments.')
2927 project, package, repository, arch = args
2931 offset = int(opts.start)
2933 print_buildlog(apiurl, project, package, repository, arch, offset)
2936 @cmdln.option('-s', '--start', metavar='START',
2937 help='get log starting from offset')
2938 def do_localbuildlog(self, subcmd, opts, *args):
2939 """${cmd_name}: Shows the build log of a local buildchroot
2942 osc lbl [REPOSITORY ARCH]
2943 osc lbl # show log of newest last local build
2947 if conf.config['build-type']:
2948 # FIXME: raise Exception instead
2949 print >>sys.stderr, 'Not implemented for VMs'
2953 package = store_read_package('.')
2955 files = glob.glob(os.path.join(os.getcwd(), store, "_buildinfo-*"))
2958 raise oscerr.WrongArgs('No buildconfig found, please specify repo and arch manually.')
2962 if os.stat(f).st_mtime > os.stat(cfg).st_mtime:
2964 root = ET.parse(cfg).getroot()
2965 project = root.get("project")
2966 repo = root.get("repository")
2967 arch = root.find("arch").text
2968 elif len(args) == 2:
2969 project = store_read_project('.')
2970 package = store_read_package('.')
2974 if is_package_dir(os.curdir):
2976 raise oscerr.WrongArgs('Wrong number of arguments.')
2978 buildroot = os.environ.get('OSC_BUILD_ROOT', conf.config['build-root'])
2979 buildroot = buildroot % {'project': project, 'package': package,
2980 'repo': repo, 'arch': arch}
2983 offset = int(opts.start)
2984 logfile = os.path.join(buildroot, '.build.log')
2985 if not os.path.isfile(logfile):
2986 raise oscerr.OscIOError(None, 'logfile \'%s\' does not exist' % logfile)
2987 f = open(logfile, 'r')
2989 data = f.read(BUFSIZE)
2991 sys.stdout.write(data)
2992 data = f.read(BUFSIZE)
2996 def do_triggerreason(self, subcmd, opts, *args):
2997 """${cmd_name}: Show reason why a package got triggered to build
2999 The server decides when a package needs to get rebuild, this command
3000 shows the detailed reason for a package. A brief reason is also stored
3001 in the jobhistory, which can be accessed via "osc jobhistory".
3003 Trigger reasons might be:
3004 - new build (never build yet or rebuild manually forced)
3005 - source change (eg. on updating sources)
3006 - meta change (packages which are used for building have changed)
3007 - rebuild count sync (In case that it is configured to sync release numbers)
3009 usage in package or project directory:
3010 osc reason REPOSITORY ARCH
3011 osc reason PROJECT PACKAGE REPOSITORY ARCH
3016 args = slash_split(args)
3017 project = package = repository = arch = None
3022 if len(args) == 2: # 2
3023 if is_package_dir('.'):
3024 package = store_read_package(wd)
3026 raise oscerr.WrongArgs('package is not specified.')
3027 project = store_read_project(wd)
3028 apiurl = store_read_apiurl(wd)
3029 repository = args[0]
3031 elif len(args) == 4:
3032 apiurl = conf.config['apiurl']
3035 repository = args[2]
3038 raise oscerr.WrongArgs('Too many arguments.')
3040 print apiurl, project, package, repository, arch
3041 xml = show_package_trigger_reason(apiurl, project, package, repository, arch)
3042 root = ET.fromstring(xml)
3043 reason = root.find('explain').text
3045 if reason == "meta change":
3046 print "changed keys:"
3047 for package in root.findall('packagechange'):
3048 print " ", package.get('change'), package.get('key')
3051 # FIXME: the new osc syntax should allow to specify multiple packages
3052 # FIXME: the command should optionally use buildinfo data to show all dependencies
3053 @cmdln.alias('whatdependson')
3054 def do_dependson(self, subcmd, opts, *args):
3055 """${cmd_name}: Show the build dependencies
3057 The command dependson and whatdependson can be used to find out what
3058 will be triggered when a certain package changes.
3059 This is no guarantee, since the new build might have changed dependencies.
3061 dependson shows the build dependencies inside of a project, valid for a
3062 given repository and architecture.
3063 NOTE: to see all binary packages, which can trigger a build you need to
3064 refer the buildinfo, since this command shows only the dependencies
3065 inside of a project.
3067 The arguments REPOSITORY and ARCH can be taken from the first two columns
3068 of the 'osc repos' output.
3070 usage in package or project directory:
3071 osc dependson REPOSITORY ARCH
3072 osc whatdependson REPOSITORY ARCH
3075 osc dependson PROJECT [PACKAGE] REPOSITORY ARCH
3076 osc whatdependson PROJECT [PACKAGE] REPOSITORY ARCH
3081 args = slash_split(args)
3082 project = packages = repository = arch = reverse = None
3084 if len(args) < 2 and (is_package_dir('.') or is_project_dir('.')):
3088 raise oscerr.WrongArgs('Too many arguments.')
3090 if len(args) < 3: # 2
3091 if is_package_dir('.'):
3092 packages = [store_read_package(wd)]
3093 elif not is_project_dir('.'):