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(hide_finished=True)
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)
152 def get_api_url(self):
153 localdir = os.getcwd()
154 if (is_package_dir(localdir) or is_project_dir(localdir)) and not self.options.apiurl:
155 return store_read_apiurl(os.curdir)
157 return conf.config['apiurl']
159 # overridden from class Cmdln() to use config variables in help texts
160 def _help_preprocess(self, help, cmdname):
161 help = cmdln.Cmdln._help_preprocess(self, help, cmdname)
162 return help % conf.config
165 def do_init(self, subcmd, opts, project, package=None):
166 """${cmd_name}: Initialize a directory as working copy
168 Initialize an existing directory to be a working copy of an
169 (already existing) buildservice project/package.
171 (This is the same as checking out a package and then copying sources
172 into the directory. It does NOT create a new package. To create a
173 package, use 'osc meta pkg ... ...')
175 You wouldn't normally use this command.
177 To get a working copy of a package (e.g. for building it or working on
178 it, you would normally use the checkout command. Use "osc help
179 checkout" to get help for it.
188 init_project_dir(conf.config['apiurl'], os.curdir, project)
189 print 'Initializing %s (Project: %s)' % (os.curdir, project)
191 init_package_dir(conf.config['apiurl'], project, package, os.path.curdir)
192 print 'Initializing %s (Project: %s, Package: %s)' % (os.curdir, project, package)
198 @cmdln.option('-a', '--arch', metavar='ARCH',
199 help='specify architecture (only for binaries)')
200 @cmdln.option('-r', '--repo', metavar='REPO',
201 help='specify repository (only for binaries)')
202 @cmdln.option('-b', '--binaries', action='store_true',
203 help='list built binaries instead of sources')
204 @cmdln.option('-R', '--revision', metavar='REVISION',
205 help='specify revision (only for sources)')
206 @cmdln.option('-e', '--expand', action='store_true',
207 help='expand linked package (only for sources)')
208 @cmdln.option('-u', '--unexpand', action='store_true',
209 help='always work with unexpanded (source) packages')
210 @cmdln.option('-v', '--verbose', action='store_true',
211 help='print extra information')
212 @cmdln.option('-l', '--long', action='store_true', dest='verbose',
213 help='print extra information')
214 def do_list(self, subcmd, opts, *args):
215 """${cmd_name}: List sources or binaries on the server
217 Examples for listing sources:
218 ls # list all projects
219 ls PROJECT # list packages in a project
220 ls PROJECT PACKAGE # list source files of package of a project
221 ls PROJECT PACKAGE <file> # list <file> if this file exists
222 ls -v PROJECT PACKAGE # verbosely list source files of package
223 ls -l PROJECT PACKAGE # verbosely list source files of package
224 ll PROJECT PACKAGE # verbosely list source files of package
225 LL PROJECT PACKAGE # verbosely list source files of expanded link
227 With --verbose, the following fields will be shown for each item:
229 Revision number of the last commit
231 Date and time of the last commit
233 Examples for listing binaries:
234 ls -b PROJECT # list all binaries of a project
235 ls -b PROJECT -a ARCH # list ARCH binaries of a project
236 ls -b PROJECT -r REPO # list binaries in REPO
237 ls -b PROJECT PACKAGE REPO ARCH
240 ${cmd_name} [PROJECT [PACKAGE]]
241 ${cmd_name} -b [PROJECT [PACKAGE [REPO [ARCH]]]]
245 apiurl = conf.config['apiurl']
246 args = slash_split(args)
249 if subcmd == 'lL' or subcmd == 'LL':
263 if opts.repo != args[2]:
264 raise oscerr.WrongArgs("conflicting repos specified ('%s' vs '%s')"%(opts.repo, args[2]))
271 if not opts.binaries:
272 raise oscerr.WrongArgs('Too many arguments')
274 if opts.arch != args[3]:
275 raise oscerr.WrongArgs("conflicting archs specified ('%s' vs '%s')"%(opts.arch, args[3]))
280 if opts.binaries and opts.expand:
281 raise oscerr.WrongOptions('Sorry, --binaries and --expand are mutual exclusive.')
285 # ls -b toplevel doesn't make sense, so use info from
286 # current dir if available
289 if is_project_dir(dir):
290 project = store_read_project(dir)
291 elif is_package_dir(dir):
292 project = store_read_project(dir)
293 package = store_read_package(dir)
295 apiurl = self.get_api_url()
298 raise oscerr.WrongArgs('There are no binaries to list above project level.')
300 raise oscerr.WrongOptions('Sorry, the --revision option is not supported for binaries.')
304 if opts.repo and opts.arch:
305 repos.append(Repo(opts.repo, opts.arch))
306 elif opts.repo and not opts.arch:
307 repos = [repo for repo in get_repos_of_project(apiurl, project) if repo.name == opts.repo]
308 elif opts.arch and not opts.repo:
309 repos = [repo for repo in get_repos_of_project(apiurl, project) if repo.arch == opts.arch]
311 repos = get_repos_of_project(apiurl, project)
315 results.append((repo, get_binarylist(apiurl, project, repo.name, repo.arch, package=package, verbose=opts.verbose)))
317 for result in results:
320 print '%s/%s' % (result[0].name, result[0].arch)
325 print "%9d %s %-40s" % (f.size, shorttime(f.mtime), f.name)
331 elif not opts.binaries:
333 print '\n'.join(meta_get_project_list(conf.config['apiurl']))
337 if self.options.verbose:
338 print >>sys.stderr, 'Sorry, the --verbose option is not implemented for projects.'
340 raise oscerr.WrongOptions('Sorry, the --expand option is not implemented for projects.')
342 print '\n'.join(meta_get_packagelist(conf.config['apiurl'], project))
344 elif len(args) == 2 or len(args) == 3:
346 print_not_found = True
349 l = meta_get_filelist(conf.config['apiurl'],
352 verbose=opts.verbose,
355 link_seen = '_link' in l
357 out = [ '%s %7s %9d %s %s' % (i.md5, i.rev, i.size, shorttime(i.mtime), i.name) \
358 for i in l if not fname or fname == i.name ]
360 print_not_found = False
365 print_not_found = False
368 if opts.expand or opts.unexpand or not link_seen: break
369 m = show_files_meta(conf.config['apiurl'], project, package)
371 li.read(ET.fromstring(''.join(m)).find('linkinfo'))
373 raise oscerr.LinkExpandError(project, package, li.error)
374 project, package, rev = li.project, li.package, li.rev
376 print '# -> %s %s (%s)' % (project, package, rev)
378 print '# -> %s %s (latest)' % (project, package)
380 if fname and print_not_found:
381 print 'file \'%s\' does not exist' % fname
384 @cmdln.option('-f', '--force', action='store_true',
385 help='force generation of new patchinfo file')
386 @cmdln.option('--force-update', action='store_true',
387 help='drops away collected packages from an already built patch and let it collect again')
388 def do_patchinfo(self, subcmd, opts, *args):
389 """${cmd_name}: Generate and edit a patchinfo file.
391 A patchinfo file describes the packages for an update and the kind of
396 osc patchinfo PATCH_NAME
400 project_dir = localdir = os.getcwd()
401 if is_project_dir(localdir):
402 project = store_read_project(localdir)
403 apiurl = self.get_api_url()
405 sys.exit('This command must be called in a checked out project.')
407 for p in meta_get_packagelist(apiurl, project):
408 if p.startswith("_patchinfo:"):
411 if opts.force or not patchinfo:
412 print "Creating initial patchinfo..."
413 query='cmd=createpatchinfo'
415 query += "&name=" + args[0]
416 url = makeurl(apiurl, ['source', project], query=query)
418 for p in meta_get_packagelist(apiurl, project):
419 if p.startswith("_patchinfo:"):
422 if not os.path.exists(project_dir + "/" + patchinfo):
423 checkout_package(apiurl, project, patchinfo, prj_dir=project_dir)
425 filename = project_dir + "/" + patchinfo + "/_patchinfo"
429 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
430 help='affect only a given attribute')
431 @cmdln.option('--attribute-defaults', action='store_true',
432 help='include defined attribute defaults')
433 @cmdln.option('--attribute-project', action='store_true',
434 help='include project values, if missing in packages ')
435 @cmdln.option('-F', '--file', metavar='FILE',
436 help='read metadata from FILE, instead of opening an editor. '
437 '\'-\' denotes standard input. ')
438 @cmdln.option('-e', '--edit', action='store_true',
439 help='edit metadata')
440 @cmdln.option('-c', '--create', action='store_true',
441 help='create attribute without values')
442 @cmdln.option('-s', '--set', metavar='ATTRIBUTE_VALUES',
443 help='set attribute values')
444 @cmdln.option('--delete', action='store_true',
445 help='delete a pattern or attribute')
446 def do_meta(self, subcmd, opts, *args):
447 """${cmd_name}: Show meta information, or edit it
449 Show or edit build service metadata of type <prj|pkg|prjconf|user|pattern>.
451 This command displays metadata on buildservice objects like projects,
452 packages, or users. The type of metadata is specified by the word after
453 "meta", like e.g. "meta prj".
455 prj denotes metadata of a buildservice project.
456 prjconf denotes the (build) configuration of a project.
457 pkg denotes metadata of a buildservice package.
458 user denotes the metadata of a user.
459 pattern denotes installation patterns defined for a project.
461 To list patterns, use 'osc meta pattern PRJ'. An additional argument
462 will be the pattern file to view or edit.
464 With the --edit switch, the metadata can be edited. Per default, osc
465 opens the program specified by the environmental variable EDITOR with a
466 temporary file. Alternatively, content to be saved can be supplied via
467 the --file switch. If the argument is '-', input is taken from stdin:
468 osc meta prjconf home:user | sed ... | osc meta prjconf home:user -F -
470 When trying to edit a non-existing resource, it is created implicitly.
476 osc meta pkg PRJ PKG -e
477 osc meta attribute PRJ [PKG [SUBPACKAGE]] [--attribute ATTRIBUTE] [--create|--delete|--set [value_list]]
480 osc meta <prj|pkg|prjconf|user|pattern|attribute> ARGS...
481 osc meta <prj|pkg|prjconf|user|pattern|attribute> -e|--edit ARGS...
482 osc meta <prj|pkg|prjconf|user|pattern|attribute> -F|--file ARGS...
483 osc meta pattern --delete PRJ PATTERN
487 args = slash_split(args)
489 if not args or args[0] not in metatypes.keys():
490 raise oscerr.WrongArgs('Unknown meta type. Choose one of %s.' \
491 % ', '.join(metatypes))
497 min_args, max_args = 0, 2
498 elif cmd in ['pattern']:
499 min_args, max_args = 1, 2
500 elif cmd in ['attribute']:
501 min_args, max_args = 1, 3
502 elif cmd in ['prj', 'prjconf']:
503 min_args, max_args = 0, 1
505 min_args, max_args = 1, 1
507 if len(args) < min_args:
508 raise oscerr.WrongArgs('Too few arguments.')
509 if len(args) > max_args:
510 raise oscerr.WrongArgs('Too many arguments.')
514 if cmd in ['pkg', 'prj', 'prjconf' ]:
516 project = store_read_project(os.curdir)
522 package = store_read_package(os.curdir)
526 elif cmd == 'attribute':
532 if opts.attribute_project:
533 raise oscerr.WrongOptions('--attribute-project works only when also a package is given')
538 attributepath.append('source')
539 attributepath.append(project)
541 attributepath.append(package)
543 attributepath.append(subpackage)
544 attributepath.append('_attribute')
547 elif cmd == 'pattern':
553 # enforce pattern argument if needed
554 if opts.edit or opts.file:
555 raise oscerr.WrongArgs('A pattern file argument is required.')
558 if not opts.edit and not opts.file and not opts.delete and not opts.create and not opts.set:
560 sys.stdout.write(''.join(show_project_meta(conf.config['apiurl'], project)))
562 sys.stdout.write(''.join(show_package_meta(conf.config['apiurl'], project, package)))
563 elif cmd == 'attribute':
564 sys.stdout.write(''.join(show_attribute_meta(conf.config['apiurl'], project, package, subpackage, opts.attribute, opts.attribute_defaults, opts.attribute_project)))
565 elif cmd == 'prjconf':
566 sys.stdout.write(''.join(show_project_conf(conf.config['apiurl'], project)))
568 r = get_user_meta(conf.config['apiurl'], user)
570 sys.stdout.write(''.join(r))
571 elif cmd == 'pattern':
573 r = show_pattern_meta(conf.config['apiurl'], project, pattern)
575 sys.stdout.write(''.join(r))
577 r = show_pattern_metalist(conf.config['apiurl'], project)
579 sys.stdout.write('\n'.join(r) + '\n')
582 if opts.edit and not opts.file:
584 edit_meta(metatype='prj',
586 path_args=quote_plus(project),
589 'user': conf.config['user']}))
591 edit_meta(metatype='pkg',
593 path_args=(quote_plus(project), quote_plus(package)),
596 'user': conf.config['user']}))
597 elif cmd == 'prjconf':
598 edit_meta(metatype='prjconf',
600 path_args=quote_plus(project),
603 edit_meta(metatype='user',
605 path_args=(quote_plus(user)),
606 template_args=({'user': user}))
607 elif cmd == 'pattern':
608 edit_meta(metatype='pattern',
610 path_args=(project, pattern),
613 # create attribute entry
614 if (opts.create or opts.set) and cmd == 'attribute':
615 if not opts.attribute:
616 raise oscerr.WrongOptions('no attribute given to create')
619 opts.set = opts.set.replace('&', '&').replace('<', '<').replace('>', '>')
620 for i in opts.set.split(','):
621 values += '<value>%s</value>' % i
622 aname = opts.attribute.split(":")
623 d = '<attributes><attribute namespace=\'%s\' name=\'%s\' >%s</attribute></attributes>' % (aname[0], aname[1], values)
624 url = makeurl(conf.config['apiurl'], attributepath)
625 for data in streamfile(url, http_POST, data=d):
626 sys.stdout.write(data)
635 f = open(opts.file).read()
637 sys.exit('could not open file \'%s\'.' % opts.file)
640 edit_meta(metatype='prj',
643 path_args=quote_plus(project))
645 edit_meta(metatype='pkg',
648 path_args=(quote_plus(project), quote_plus(package)))
649 elif cmd == 'prjconf':
650 edit_meta(metatype='prjconf',
653 path_args=quote_plus(project))
655 edit_meta(metatype='user',
658 path_args=(quote_plus(user)))
659 elif cmd == 'pattern':
660 edit_meta(metatype='pattern',
663 path_args=(project, pattern))
668 path = metatypes[cmd]['path']
670 path = path % (project, pattern)
671 u = makeurl(conf.config['apiurl'], [path])
673 elif cmd == 'attribute':
674 if not opts.attribute:
675 raise oscerr.WrongOptions('no attribute given to create')
676 attributepath.append(opts.attribute)
677 u = makeurl(conf.config['apiurl'], attributepath)
678 for data in streamfile(u, http_DELETE):
679 sys.stdout.write(data)
681 raise oscerr.WrongOptions('The --delete switch is only for pattern metadata or attributes.')
684 @cmdln.option('-m', '--message', metavar='TEXT',
685 help='specify message TEXT')
686 @cmdln.option('-r', '--revision', metavar='REV',
687 help='for "create", specify a certain source revision ID (the md5 sum)')
688 @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
689 help='Superseding another request by this one')
690 @cmdln.option('--nodevelproject', action='store_true',
691 help='do not follow a defined devel project ' \
692 '(primary project where a package is developed)')
693 @cmdln.option('--cleanup', action='store_true',
694 help='remove package if submission gets accepted (default for home:<id>:branch projects)')
695 @cmdln.option('--no-cleanup', action='store_true',
696 help='never remove source package on accept, but update its content')
697 @cmdln.option('--no-update', action='store_true',
698 help='never touch source package on accept (will break source links)')
699 @cmdln.option('-d', '--diff', action='store_true',
700 help='show diff only instead of creating the actual request')
701 @cmdln.option('--yes', action='store_true',
702 help='proceed without asking.')
704 @cmdln.alias("submitreq")
705 @cmdln.alias("submitpac")
706 def do_submitrequest(self, subcmd, opts, *args):
707 """${cmd_name}: Create request to submit source into another Project
709 [See http://en.opensuse.org/Build_Service/Collaboration for information
712 See the "request" command for showing and modifing existing requests.
715 osc submitreq [OPTIONS]
716 osc submitreq [OPTIONS] DESTPRJ [DESTPKG]
717 osc submitreq [OPTIONS] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
721 src_update = conf.config['submitrequest_on_accept_action'] or None
722 # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
724 src_update = "cleanup"
725 elif opts.no_cleanup:
726 src_update = "update"
728 src_update = "noupdate"
730 args = slash_split(args)
732 # remove this block later again
733 oldcmds = ['create', 'list', 'log', 'show', 'decline', 'accept', 'delete', 'revoke']
734 if args and args[0] in oldcmds:
735 print "************************************************************************"
736 print "* WARNING: It looks that you are using this command with a *"
737 print "* deprecated syntax. *"
738 print "* Please run \"osc sr --help\" and \"osc rq --help\" *"
739 print "* to see the new syntax. *"
740 print "************************************************************************"
741 if args[0] == 'create':
747 raise oscerr.WrongArgs('Too many arguments.')
749 if len(args) > 0 and len(args) <= 2 and is_project_dir(os.getcwd()):
750 sys.exit('osc submitrequest from project directory is only working without target specs and for source linked files\n')
752 apiurl = self.get_api_url()
754 if len(args) == 0 and is_project_dir(os.getcwd()):
756 # submit requests for multiple packages are currently handled via multiple requests
757 # They could be also one request with multiple actions, but that avoids to accepts parts of it.
758 project = store_read_project(os.curdir)
764 # loop via all packages for checking their state
765 for p in meta_get_packagelist(apiurl, project):
766 if p.startswith("_patchinfo:"):
769 # get _link info from server, that knows about the local state ...
770 u = makeurl(apiurl, ['source', project, p])
772 root = ET.parse(f).getroot()
773 linkinfo = root.find('linkinfo')
775 print "Package ", p, " is not a source link."
776 sys.exit("This is currently not supported.")
777 if linkinfo.get('error'):
778 print "Package ", p, " is a broken source link."
779 sys.exit("Please fix this first")
780 t = linkinfo.get('project')
782 if len(root.findall('entry')) > 1: # This is not really correct, but should work mostly
783 # Real fix is to ask the api if sources are modificated
784 # but there is no such call yet.
785 targetprojects.append(t)
787 print "Submitting package ", p
789 print " Skipping package ", p
791 print "Skipping package ", p, " since it is a source link pointing inside the project."
795 print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
796 print "\nEverything fine? Can we create the requests ? [y/n]"
797 if sys.stdin.read(1) != "y":
798 print >>sys.stderr, 'Aborted...'
799 raise oscerr.UserAbort()
801 # loop via all packages to do the action
803 result = create_submit_request(apiurl, project, p)
806 sys.exit("submit request creation failed")
807 sr_ids.append(result)
809 # create submit requests for all found patchinfos
813 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
816 for t in targetprojects:
817 s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \
818 (project, p, t, p, options_block)
822 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
823 (actionxml, cgi.escape(opts.message or ""))
824 u = makeurl(apiurl, ['request'], query='cmd=create')
825 f = http_POST(u, data=xml)
827 root = ET.parse(f).getroot()
828 sr_ids.append(root.get('id'))
830 print "Requests created: ",
833 sys.exit('Successfull finished')
836 # try using the working copy at hand
837 p = findpacs(os.curdir)[0]
838 src_project = p.prjname
841 if len(args) == 0 and p.islink():
842 dst_project = p.linkinfo.project
843 dst_package = p.linkinfo.package
845 dst_project = args[0]
847 dst_package = args[1]
849 dst_package = src_package
851 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
852 'Please provide it the target via commandline arguments.' % p.name)
854 modified = [i for i in p.filenamelist if p.status(i) != ' ' and p.status(i) != '?']
855 if len(modified) > 0:
856 print 'Your working copy has local modifications.'
857 repl = raw_input('Proceed without committing the local changes? (y|N) ')
859 raise oscerr.UserAbort()
861 # get the arguments from the commandline
862 src_project, src_package, dst_project = args[0:3]
864 dst_package = args[3]
866 dst_package = src_package
868 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
869 + self.get_cmd_help('request'))
871 if not opts.nodevelproject:
874 devloc = show_develproject(apiurl, dst_project, dst_package)
875 except urllib2.HTTPError:
876 print >>sys.stderr, """\
877 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
878 % (dst_project, dst_package)
882 dst_project != devloc and \
883 src_project != devloc:
885 A different project, %s, is defined as the place where development
886 of the package %s primarily takes place.
887 Please submit there instead, or use --nodevelproject to force direct submission.""" \
888 % (devloc, dst_package)
893 if opts.diff or not opts.message:
895 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
896 rdiff += server_diff(apiurl,
897 dst_project, dst_package, opts.revision,
898 src_project, src_package, None, True)
904 reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit')
905 user = conf.get_apiurl_usr(apiurl)
906 myreqs = [ i for i in reqs if i.state.who == user ]
909 print 'You already created the following submit request: %s.' % \
910 ', '.join([str(i.reqid) for i in myreqs ])
911 repl = raw_input('Supersede the old requests? (y/n/c) ')
912 if repl.lower() == 'c':
913 print >>sys.stderr, 'Aborting'
914 raise oscerr.UserAbort()
919 changes_re = re.compile(r'^--- .*\.changes ')
920 for line in rdiff.split('\n'):
921 if line.startswith('--- '):
922 if changes_re.match(line):
927 difflines.append(line)
928 opts.message = edit_message(footer=rdiff, template='\n'.join(parse_diff_for_commit_message('\n'.join(difflines))))
930 result = create_submit_request(apiurl,
931 src_project, src_package,
932 dst_project, dst_package,
933 opts.message, orev=opts.revision, src_update=src_update)
934 if repl.lower() == 'y':
936 change_request_state(apiurl, str(req.reqid), 'superseded',
937 'superseded by %s' % result, result)
940 r = change_request_state(conf.config['apiurl'],
941 opts.supersede, 'superseded', opts.message or '', result)
943 print 'created request id', result
946 @cmdln.option('-m', '--message', metavar='TEXT',
947 help='specify message TEXT')
949 @cmdln.alias("deletereq")
950 def do_deleterequest(self, subcmd, opts, *args):
951 """${cmd_name}: Create request to delete a package or project
955 osc deletereq [-m TEXT] PROJECT [PACKAGE]
959 args = slash_split(args)
962 raise oscerr.WrongArgs('Please specify at least a project.')
964 raise oscerr.WrongArgs('Too many arguments.')
966 apiurl = conf.config['apiurl']
974 opts.message = edit_message()
976 result = create_delete_request(apiurl, project, package, opts.message)
980 @cmdln.option('-m', '--message', metavar='TEXT',
981 help='specify message TEXT')
983 @cmdln.alias("changedevelreq")
984 def do_changedevelrequest(self, subcmd, opts, *args):
985 """${cmd_name}: Create request to change the devel package definition.
987 [See http://en.opensuse.org/Build_Service/Collaboration for information
990 See the "request" command for showing and modifing existing requests.
992 osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
996 raise oscerr.WrongArgs('Too many arguments.')
998 apiurl = self.get_api_url()
1000 if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
1002 devel_project = store_read_project(wd)
1003 devel_package = package = store_read_package(wd)
1004 project = conf.config['getpac_default_project']
1007 raise oscerr.WrongArgs('Too few arguments.')
1009 devel_project = args[2]
1012 devel_package = package
1014 devel_package = args[3]
1016 if not opts.message:
1018 footer=textwrap.TextWrapper(width = 66).fill(
1019 'please explain why you like to change the devel project of %s/%s to %s/%s'
1020 % (project,package,devel_project,devel_package))
1021 opts.message = edit_message(footer)
1023 result = create_change_devel_request(apiurl,
1024 devel_project, devel_package,
1030 @cmdln.option('-d', '--diff', action='store_true',
1031 help='generate a diff')
1032 @cmdln.option('-u', '--unified', action='store_true',
1033 help='output the diff in the unified diff format')
1034 @cmdln.option('-m', '--message', metavar='TEXT',
1035 help='specify message TEXT')
1036 @cmdln.option('-t', '--type', metavar='TYPE',
1037 help='limit to requests which contain a given action type (submit/delete/change_devel)')
1038 @cmdln.option('-a', '--all', action='store_true',
1039 help='all states. Same as\'-s all\'')
1040 @cmdln.option('-s', '--state', default='', # default is 'all' if no args given, 'new' otherwise
1041 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]')
1042 @cmdln.option('-D', '--days', metavar='DAYS',
1043 help='only list requests in state "new" or changed in the last DAYS. [default=%(request_list_days)s]')
1044 @cmdln.option('-U', '--user', metavar='USER',
1045 help='same as -M, but for the specified USER')
1046 @cmdln.option('-b', '--brief', action='store_true', default=False,
1047 help='print output in list view as list subcommand')
1048 @cmdln.option('-M', '--mine', action='store_true',
1049 help='only show requests created by yourself')
1050 @cmdln.option('-B', '--bugowner', action='store_true',
1051 help='also show requests about packages where I am bugowner')
1052 @cmdln.option('-i', '--interactive', action='store_true',
1053 help='interactive review of request')
1054 @cmdln.option('--non-interactive', action='store_true',
1055 help='non-interactive review of request')
1056 @cmdln.option('--exclude-target-project', action='append',
1057 help='exclude target project from request list')
1058 @cmdln.option('--involved-projects', action='store_true',
1059 help='show all requests for project/packages where USER is involved')
1061 @cmdln.alias("review")
1062 def do_request(self, subcmd, opts, *args):
1063 """${cmd_name}: Show and modify requests
1065 [See http://en.opensuse.org/Build_Service/Collaboration for information
1068 This command shows and modifies existing requests. To create new requests
1069 you need to call one of the following:
1072 osc changedevelrequest
1073 To send low level requests to the buildservice API, use:
1076 This command has the following sub commands:
1078 "list" lists open requests attached to a project or package or person.
1079 Uses the project/package of the current directory if none of
1080 -M, -U USER, project/package are given.
1082 "log" will show the history of the given ID
1084 "show" will show the request itself, and generate a diff for review, if
1085 used with the --diff option. The keyword show can be omitted if the ID is numeric.
1087 "decline" will change the request state to "declined" and append a
1088 message that you specify with the --message option.
1090 "wipe" will permanently delete a request.
1092 "revoke" will set the request state to "revoked" and append a
1093 message that you specify with the --message option.
1095 "accept" will change the request state to "accepted" and will trigger
1096 the actual submit process. That would normally be a server-side copy of
1097 the source package to the target package.
1099 "checkout" will checkout the request's source package. This only works for "submit" requests.
1102 osc request list [-M] [-U USER] [-s state] [-D DAYS] [-t type] [-B] [PRJ [PKG]]
1104 osc request [show] [-d] [-b] ID
1105 osc request accept [-m TEXT] ID
1106 osc request approvenew [-m TEXT] PROJECT
1107 osc request decline [-m TEXT] ID
1108 osc request revoke [-m TEXT] ID
1110 osc request checkout/co ID
1111 osc review accept [-m TEXT] ID
1112 osc review decline [-m TEXT] ID
1113 osc review new [-m TEXT] ID # for setting a temporary comment without changing the state
1117 args = slash_split(args)
1119 if opts.all and opts.state:
1120 raise oscerr.WrongOptions('Sorry, the options \'--all\' and \'--state\' ' \
1121 'are mutually exclusive.')
1122 if opts.mine and opts.user:
1123 raise oscerr.WrongOptions('Sorry, the options \'--user\' and \'--mine\' ' \
1124 'are mutually exclusive.')
1125 if opts.interactive and opts.non_interactive:
1126 raise oscerr.WrongOptions('Sorry, the options \'--interactive\' and ' \
1127 '\'--non-interactive\' are mutually exclusive')
1132 if opts.state == '':
1135 if opts.state == '':
1138 cmds = ['list', 'log', 'show', 'decline', 'accept', 'approvenew', 'wipe', 'revoke', 'checkout', 'co', 'help']
1139 if not args or args[0] not in cmds:
1140 raise oscerr.WrongArgs('Unknown request action %s. Choose one of %s.' \
1141 % (args[0],', '.join(cmds)))
1147 return self.do_help(['help', 'request'])
1150 min_args, max_args = 0, 2
1152 min_args, max_args = 1, 1
1153 if len(args) < min_args:
1154 raise oscerr.WrongArgs('Too few arguments.')
1155 if len(args) > max_args:
1156 raise oscerr.WrongArgs('Too many arguments.')
1158 apiurl = self.get_api_url()
1160 if cmd == 'list' or cmd == 'approvenew':
1165 elif not opts.mine and not opts.user:
1167 project = store_read_project(os.curdir)
1168 package = store_read_package(os.curdir)
1169 except oscerr.NoWorkingCopy:
1174 elif cmd in ['log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co']:
1177 # list and approvenew
1178 if cmd == 'list' or cmd == 'approvenew':
1179 states = ('new', 'accepted', 'revoked', 'declined')
1181 if cmd == 'approvenew':
1183 results = get_request_list(apiurl, project, package, '', ['new'])
1185 state_list = opts.state.split(',')
1186 if opts.state == 'all':
1187 state_list = ['all']
1189 for s in state_list:
1191 raise oscerr.WrongArgs('Unknown state \'%s\', try one of %s' % (s, ','.join(states)))
1193 who = conf.get_apiurl_usr(apiurl)
1197 state_list = ['all']
1199 ## FIXME -B not implemented!
1201 if (self.options.debug):
1202 print 'list: option --bugowner ignored: not impl.'
1204 if opts.involved_projects:
1205 who = who or conf.get_apiurl_usr(apiurl)
1206 results = get_user_projpkgs_request_list(apiurl, who, req_state=state_list,
1207 req_type=opts.type, exclude_projects=opts.exclude_target_project or [])
1209 results = get_request_list(apiurl, project, package, who,
1210 state_list, opts.type, opts.exclude_target_project or [])
1212 results.sort(reverse=True)
1214 days = opts.days or conf.config['request_list_days']
1221 since = time.strftime('%Y-%m-%dT%H:%M:%S',time.localtime(time.time()-days*24*3600))
1224 ## bs has received 2009-09-20 a new xquery compare() function
1225 ## which allows us to limit the list inside of get_request_list
1226 ## That would be much faster for coolo. But counting the remainder
1227 ## would not be possible with current xquery implementation.
1228 ## Workaround: fetch all, and filter on client side.
1230 ## FIXME: date filtering should become implemented on server side
1231 for result in results:
1232 if days == 0 or result.state.when > since or result.state.name == 'new':
1233 print result.list_view()
1237 print "There are %d requests older than %s days.\n" % (skipped, days)
1239 if cmd == 'approvenew':
1240 print "\n *** Approve them all ? [y/n] ***"
1241 if sys.stdin.read(1) == "y":
1243 if not opts.message:
1244 opts.message = edit_message()
1245 for result in results:
1246 print result.reqid, ": ",
1247 r = change_request_state(conf.config['apiurl'],
1248 str(result.reqid), 'accepted', opts.message or '')
1251 print >>sys.stderr, 'Aborted...'
1252 raise oscerr.UserAbort()
1255 for l in get_request_log(conf.config['apiurl'], reqid):
1260 r = get_request(conf.config['apiurl'], reqid)
1263 elif (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive:
1264 return request_interactive_review(conf.config['apiurl'], r)
1267 # fixme: will inevitably fail if the given target doesn't exist
1268 if opts.diff and r.actions[0].type != 'submit':
1269 raise oscerr.WrongOptions('\'--diff\' is not possible for request type: \'%s\'' % r.actions[0].type)
1272 print server_diff(conf.config['apiurl'],
1273 r.actions[0].dst_project, r.actions[0].dst_package, None,
1274 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, True)
1275 except urllib2.HTTPError, e:
1277 e.osc_msg = 'Diff not possible'
1279 # backward compatiblity: only a recent api/backend supports the missingok parameter
1281 print server_diff(conf.config['apiurl'],
1282 r.actions[0].dst_project, r.actions[0].dst_package, None,
1283 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, False)
1284 except urllib2.HTTPError, e:
1285 e.osc_msg = 'Diff not possible'
1289 elif cmd == 'checkout' or cmd == 'co':
1290 r = get_request(conf.config['apiurl'], reqid)
1291 submits = [ i for i in r.actions if i.type == 'submit' ]
1292 if not len(submits):
1293 raise oscerr.WrongArgs('\'checkout\' only works for \'submit\' requests')
1294 checkout_package(conf.config['apiurl'], submits[0].src_project, submits[0].src_package, \
1295 submits[0].src_rev, expand_link=True, prj_dir=submits[0].src_project)
1298 if not opts.message:
1299 opts.message = edit_message()
1300 state_map = {'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked'}
1301 # Change review state only
1302 if subcmd == 'review':
1303 if cmd in ['accept', 'decline', 'new']:
1304 r = change_review_state(conf.config['apiurl'],
1305 reqid, state_map[cmd], conf.config['user'], '', opts.message or '')
1307 # Change state of entire request
1308 elif cmd in ['accept', 'decline', 'wipe', 'revoke']:
1309 r = change_request_state(conf.config['apiurl'],
1310 reqid, state_map[cmd], opts.message or '')
1313 # editmeta and its aliases are all depracated
1314 @cmdln.alias("editprj")
1315 @cmdln.alias("createprj")
1316 @cmdln.alias("editpac")
1317 @cmdln.alias("createpac")
1318 @cmdln.alias("edituser")
1319 @cmdln.alias("usermeta")
1321 def do_editmeta(self, subcmd, opts, *args):
1324 Obsolete command to edit metadata. Use 'meta' now.
1326 See the help output of 'meta'.
1330 print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
1331 print >>sys.stderr, 'See \'osc help meta\'.'
1332 #self.do_help([None, 'meta'])
1336 @cmdln.option('-r', '--revision', metavar='rev',
1337 help='use the specified revision.')
1338 @cmdln.option('-u', '--unset', action='store_true',
1339 help='remove revision in link, it will point always to latest revision')
1340 def do_setlinkrev(self, subcmd, opts, *args):
1341 """${cmd_name}: Updates a revision number in a source link.
1343 This command adds or updates a specified revision number in a source link.
1344 The current revision of the source is used, if no revision number is specified.
1348 osc setlinkrev PROJECT [PACKAGE]
1352 args = slash_split(args)
1353 apiurl = conf.config['apiurl']
1356 p = findpacs(os.curdir)[0]
1361 sys.exit('Local directory is no checked out source link package, aborting')
1362 elif len(args) == 2:
1365 elif len(args) == 1:
1368 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1369 + self.get_cmd_help('setlinkrev'))
1372 packages = [ package ]
1374 packages = meta_get_packagelist(apiurl, project)
1377 print "setting revision for package", p
1381 rev, dummy = parseRevisionOption(opts.revision)
1382 set_link_rev(apiurl, project, p, rev)
1385 def do_linktobranch(self, subcmd, opts, *args):
1386 """${cmd_name}: Convert a package containing a classic link with patch to a branch
1388 This command tells the server to convert a _link with or without a project.diff
1389 to a branch. This is a full copy with a _link file pointing to the branched place.
1392 osc linktobranch # can be used in checked out package
1393 osc linktobranch PROJECT PACKAGE
1396 args = slash_split(args)
1397 apiurl = self.get_api_url()
1401 project = store_read_project(wd)
1402 package = store_read_package(wd)
1403 update_local_dir = True
1405 raise oscerr.WrongArgs('Too few arguments (required none or two)')
1407 raise oscerr.WrongArgs('Too many arguments (required none or two)')
1411 update_local_dir = False
1414 link_to_branch(apiurl, project, package)
1415 if update_local_dir:
1417 pac.update(rev=pac.latest_rev())
1420 @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'],
1421 help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.')
1422 @cmdln.option('-c', '--current', action='store_true',
1423 help='link fixed against current revision.')
1424 @cmdln.option('-r', '--revision', metavar='rev',
1425 help='link the specified revision.')
1426 @cmdln.option('-f', '--force', action='store_true',
1427 help='overwrite an existing link file if it is there.')
1428 @cmdln.option('-d', '--disable-publish', action='store_true',
1429 help='disable publishing of the linked package')
1430 def do_linkpac(self, subcmd, opts, *args):
1431 """${cmd_name}: "Link" a package to another package
1433 A linked package is a clone of another package, but plus local
1434 modifications. It can be cross-project.
1436 The DESTPAC name is optional; the source packages' name will be used if
1439 Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
1441 To add a patch, add the patch as file and add it to the _link file.
1442 You can also specify text which will be inserted at the top of the spec file.
1444 See the examples in the _link file.
1447 osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1451 args = slash_split(args)
1453 if not args or len(args) < 3:
1454 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1455 + self.get_cmd_help('linkpac'))
1457 rev, dummy = parseRevisionOption(opts.revision)
1459 src_project = args[0]
1460 src_package = args[1]
1461 dst_project = args[2]
1463 dst_package = args[3]
1465 dst_package = src_package
1467 if src_project == dst_project and src_package == dst_package:
1468 raise oscerr.WrongArgs('Error: source and destination are the same.')
1470 if src_project == dst_project and not opts.cicount:
1471 # in this case, the user usually wants to build different spec
1472 # files from the same source
1473 opts.cicount = "copy"
1476 rev = show_upstream_rev(conf.config['apiurl'], src_project, src_package)
1478 if rev and not checkRevision(src_project, src_package, rev):
1479 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
1482 link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish)
1484 @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]',
1485 help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]')
1486 @cmdln.option('-d', '--disable-publish', action='store_true',
1487 help='disable publishing of the aggregated package')
1488 def do_aggregatepac(self, subcmd, opts, *args):
1489 """${cmd_name}: "Aggregate" a package to another package
1491 Aggregation of a package means that the build results (binaries) of a
1492 package are basically copied into another project.
1493 This can be used to make packages available from building that are
1494 needed in a project but available only in a different project. Note
1495 that this is done at the expense of disk space. See
1496 http://en.opensuse.org/Build_Service/Tips_and_Tricks#_link_and__aggregate
1497 for more information.
1499 The DESTPAC name is optional; the source packages' name will be used if
1503 osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1507 args = slash_split(args)
1509 if not args or len(args) < 3:
1510 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1511 + self.get_cmd_help('aggregatepac'))
1513 src_project = args[0]
1514 src_package = args[1]
1515 dst_project = args[2]
1517 dst_package = args[3]
1519 dst_package = src_package
1521 if src_project == dst_project and src_package == dst_package:
1522 raise oscerr.WrongArgs('Error: source and destination are the same.')
1526 for pair in opts.map_repo.split(','):
1527 src_tgt = pair.split('=')
1528 if len(src_tgt) != 2:
1529 raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo)
1530 repo_map[src_tgt[0]] = src_tgt[1]
1532 aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish)
1535 @cmdln.option('-c', '--client-side-copy', action='store_true',
1536 help='do a (slower) client-side copy')
1537 @cmdln.option('-k', '--keep-maintainers', action='store_true',
1538 help='keep original maintainers. Default is remove all and replace with the one calling the script.')
1539 @cmdln.option('-d', '--keep-develproject', action='store_true',
1540 help='keep develproject tag in the package metadata')
1541 @cmdln.option('-r', '--revision', metavar='rev',
1542 help='link the specified revision.')
1543 @cmdln.option('-t', '--to-apiurl', metavar='URL',
1544 help='URL of destination api server. Default is the source api server.')
1545 @cmdln.option('-m', '--message', metavar='TEXT',
1546 help='specify message TEXT')
1547 @cmdln.option('-e', '--expand', action='store_true',
1548 help='if the source package is a link then copy the expanded version of the link')
1549 def do_copypac(self, subcmd, opts, *args):
1550 """${cmd_name}: Copy a package
1552 A way to copy package to somewhere else.
1554 It can be done across buildservice instances, if the -t option is used.
1555 In that case, a client-side copy is implied.
1557 Using --client-side-copy always involves downloading all files, and
1558 uploading them to the target.
1560 The DESTPAC name is optional; the source packages' name will be used if
1564 osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1568 args = slash_split(args)
1570 if not args or len(args) < 3:
1571 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1572 + self.get_cmd_help('copypac'))
1574 src_project = args[0]
1575 src_package = args[1]
1576 dst_project = args[2]
1578 dst_package = args[3]
1580 dst_package = src_package
1582 src_apiurl = conf.config['apiurl']
1584 dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl)
1586 dst_apiurl = src_apiurl
1588 if src_apiurl != dst_apiurl:
1589 opts.client_side_copy = True
1591 rev, dummy = parseRevisionOption(opts.revision)
1594 comment = opts.message
1597 rev = show_upstream_rev(src_apiurl, src_project, src_package)
1598 comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev )
1600 if src_project == dst_project and \
1601 src_package == dst_package and \
1603 src_apiurl == dst_apiurl:
1604 raise oscerr.WrongArgs('Source and destination are the same.')
1606 r = copy_pac(src_apiurl, src_project, src_package,
1607 dst_apiurl, dst_project, dst_package,
1608 client_side_copy=opts.client_side_copy,
1609 keep_maintainers=opts.keep_maintainers,
1610 keep_develproject=opts.keep_develproject,
1617 @cmdln.option('-c', '--checkout', action='store_true',
1618 help='Checkout branched package afterwards ' \
1619 '(\'osc bco\' is a shorthand for this option)' )
1620 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
1621 help='Use this attribute to find affected packages (default is OBS:Maintained)')
1622 @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE',
1623 help='Use this attribute to find update projects (default is OBS:UpdateProject) ')
1624 def do_mbranch(self, subcmd, opts, *args):
1625 """${cmd_name}: Multiple branch of a package
1627 [See http://en.opensuse.org/Build_Service/Concepts/Maintenance for information
1630 This command is used for creating multiple links of defined version of a package
1631 in one project. This is esp. used for maintenance updates.
1633 The branched package will live in
1634 home:USERNAME:branches:ATTRIBUTE:PACKAGE
1635 if nothing else specified.
1638 osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ]
1641 args = slash_split(args)
1644 maintained_attribute = conf.config['maintained_attribute']
1645 maintained_update_project_attribute = conf.config['maintained_update_project_attribute']
1647 if not len(args) or len(args) > 2:
1648 raise oscerr.WrongArgs('Wrong number of arguments.')
1654 r = attribute_branch_pkg(conf.config['apiurl'], maintained_attribute, maintained_update_project_attribute, \
1658 print >>sys.stderr, 'ERROR: Attribute branch call came not back with a project.'
1661 print "Project " + r + " created."
1664 init_project_dir(conf.config['apiurl'], r, r)
1665 print statfrmt('A', r)
1668 for package in meta_get_packagelist(conf.config['apiurl'], r):
1670 checkout_package(conf.config['apiurl'], r, package, expand_link = True, prj_dir = r)
1672 print >>sys.stderr, 'Error while checkout package:\n', package
1674 if conf.config['verbose']:
1675 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1678 @cmdln.alias('branchco')
1680 @cmdln.alias('getpac')
1681 @cmdln.option('--nodevelproject', action='store_true',
1682 help='do not follow a defined devel project ' \
1683 '(primary project where a package is developed)')
1684 @cmdln.option('-c', '--checkout', action='store_true',
1685 help='Checkout branched package afterwards ' \
1686 '(\'osc bco\' is a shorthand for this option)' )
1687 @cmdln.option('-f', '--force', default=False, action="store_true",
1688 help='force branch, overwrite target')
1689 @cmdln.option('-m', '--message', metavar='TEXT',
1690 help='specify message TEXT')
1691 @cmdln.option('-r', '--revision', metavar='rev',
1692 help='branch against a specific revision')
1693 def do_branch(self, subcmd, opts, *args):
1694 """${cmd_name}: Branch a package
1696 [See http://en.opensuse.org/Build_Service/Collaboration for information
1699 Create a source link from a package of an existing project to a new
1700 subproject of the requesters home project (home:branches:)
1702 The branched package will live in
1703 home:USERNAME:branches:PROJECT/PACKAGE
1704 if nothing else specified.
1706 With getpac or bco, the branched package will come from
1707 %(getpac_default_project)s
1708 if nothing else specified.
1712 osc branch SOURCEPROJECT SOURCEPACKAGE
1713 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT
1714 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE
1715 osc getpac SOURCEPACKAGE
1720 if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': opts.checkout = True
1721 args = slash_split(args)
1722 tproject = tpackage = None
1724 if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1:
1725 print >>sys.stderr, 'defaulting to %s/%s' % (conf.config['getpac_default_project'], args[0])
1726 # python has no args.unshift ???
1727 args = [ conf.config['getpac_default_project'] , args[0] ]
1729 if len(args) == 0 and is_package_dir('.'):
1730 args = (store_read_project('.'), store_read_package('.'))
1732 if len(args) < 2 or len(args) > 4:
1733 raise oscerr.WrongArgs('Wrong number of arguments.')
1735 expected = 'home:%s:branches:%s' % (conf.config['user'], args[0])
1737 expected = tproject = args[2]
1741 if not opts.message:
1742 footer='please specify the purpose of your branch'
1743 template='This package was branched from %s in order to ...\n' % args[0]
1744 opts.message = edit_message(footer, template)
1746 exists, targetprj, targetpkg, srcprj, srcpkg = \
1747 branch_pkg(conf.config['apiurl'], args[0], args[1],
1748 nodevelproject=opts.nodevelproject, rev=opts.revision,
1749 target_project=tproject, target_package=tpackage,
1750 return_existing=opts.checkout, msg=opts.message or '',
1753 print >>sys.stderr, 'Using existing branch project: %s' % targetprj
1756 if not exists and (srcprj is not None and srcprj != args[0] or \
1757 srcprj is None and targetprj != expected):
1758 devloc = srcprj or targetprj
1759 if not srcprj and 'branches:' in targetprj:
1760 devloc = targetprj.split('branches:')[1]
1761 print '\nNote: The branch has been created of a different project,\n' \
1763 ' which is the primary location of where development for\n' \
1764 ' that package takes place.\n' \
1765 ' That\'s also where you would normally make changes against.\n' \
1766 ' A direct branch of the specified package can be forced\n' \
1767 ' with the --nodevelproject option.\n' % devloc
1769 package = tpackage or args[1]
1771 checkout_package(conf.config['apiurl'], targetprj, package,
1772 expand_link=True, prj_dir=targetprj)
1773 if conf.config['verbose']:
1774 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1777 if conf.get_configParser().get('general', 'apiurl') != conf.config['apiurl']:
1778 apiopt = '-A %s ' % conf.config['apiurl']
1779 print 'A working copy of the branched package can be checked out with:\n\n' \
1781 % (apiopt, targetprj, package)
1782 print_request_list(conf.config['apiurl'], args[0], args[1])
1784 print_request_list(conf.config['apiurl'], devloc, args[1])
1788 @cmdln.option('-f', '--force', action='store_true',
1789 help='deletes a package or an empty project')
1790 def do_rdelete(self, subcmd, opts, *args):
1791 """${cmd_name}: Delete a project or packages on the server.
1793 As a safety measure, project must be empty (i.e., you need to delete all
1794 packages first). If you are sure that you want to remove this project and all
1795 its packages use \'--force\' switch.
1798 osc rdelete -f PROJECT
1799 osc rdelete PROJECT PACKAGE [PACKAGE ...]
1804 args = slash_split(args)
1806 raise oscerr.WrongArgs('Missing argument.')
1812 # careful: if pkg is an empty string, the package delete request results
1813 # into a project delete request - which works recursively...
1815 delete_package(conf.config['apiurl'], prj, pkg)
1816 elif len(meta_get_packagelist(conf.config['apiurl'], prj)) >= 1 and not opts.force:
1817 print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
1818 'If you are sure that you want to remove this project and all its ' \
1819 'packages use the \'--force\' switch'
1822 delete_project(conf.config['apiurl'], prj)
1825 def do_deletepac(self, subcmd, opts, *args):
1826 print """${cmd_name} is obsolete !
1829 osc delete for checked out packages or projects
1831 osc rdelete for server side operations."""
1836 @cmdln.option('-f', '--force', action='store_true',
1837 help='deletes a project and its packages')
1838 def do_deleteprj(self, subcmd, opts, project):
1839 """${cmd_name} is obsolete !
1846 @cmdln.alias('metafromspec')
1847 @cmdln.option('', '--specfile', metavar='FILE',
1848 help='Path to specfile. (if you pass more than working copy this option is ignored)')
1849 def do_updatepacmetafromspec(self, subcmd, opts, *args):
1850 """${cmd_name}: Update package meta information from a specfile
1852 ARG, if specified, is a package working copy.
1858 args = parseargs(args)
1859 if opts.specfile and len(args) == 1:
1860 specfile = opts.specfile
1863 pacs = findpacs(args)
1865 p.read_meta_from_spec(specfile)
1866 p.update_package_meta()
1870 @cmdln.option('-c', '--change', metavar='rev',
1871 help='the change made by revision rev (like -r rev-1:rev).'
1872 'If rev is negative this is like -r rev:rev-1.')
1873 @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
1874 help='If rev1 is specified it will compare your working copy against '
1875 'the revision (rev1) on the server. '
1876 'If rev1 and rev2 are specified it will compare rev1 against rev2 '
1877 '(NOTE: changes in your working copy are ignored in this case)')
1878 @cmdln.option('-p', '--plain', action='store_true',
1879 help='output the diff in plain (not unified) diff format')
1880 @cmdln.option('--missingok', action='store_true',
1881 help='do not fail if the source or target project/package does not exist on the server')
1882 def do_diff(self, subcmd, opts, *args):
1883 """${cmd_name}: Generates a diff
1885 Generates a diff, comparing local changes against the repository
1888 ARG, specified, is a filename to include in the diff.
1894 args = parseargs(args)
1895 pacs = findpacs(args)
1899 rev = int(opts.change)
1909 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1912 rev1, rev2 = parseRevisionOption(opts.revision)
1916 diff += ''.join(make_diff(pac, rev1))
1918 diff += server_diff(pac.apiurl, pac.prjname, pac.name, rev1,
1919 pac.prjname, pac.name, rev2, not opts.plain, opts.missingok)
1924 @cmdln.option('--oldprj', metavar='OLDPRJ',
1925 help='project to compare against'
1926 ' (deprecated, use 3 argument form)')
1927 @cmdln.option('--oldpkg', metavar='OLDPKG',
1928 help='package to compare against'
1929 ' (deprecated, use 3 argument form)')
1930 @cmdln.option('-r', '--revision', metavar='N[:M]',
1931 help='revision id, where N = old revision and M = new revision')
1932 @cmdln.option('-p', '--plain', action='store_true',
1933 help='output the diff in plain (not unified) diff format')
1934 @cmdln.option('-c', '--change', metavar='rev',
1935 help='the change made by revision rev (like -r rev-1:rev). '
1936 'If rev is negative this is like -r rev:rev-1.')
1937 @cmdln.option('--missingok', action='store_true',
1938 help='do not fail if the source or target project/package does not exist on the server')
1939 def do_rdiff(self, subcmd, opts, *args):
1940 """${cmd_name}: Server-side "pretty" diff of two packages
1942 Compares two packages (three or four arguments) or shows the
1943 changes of a specified revision of a package (two arguments)
1945 If no revision is specified the latest revision is used.
1947 Note that this command doesn't return a normal diff (which could be
1948 applied as patch), but a "pretty" diff, which also compares the content
1953 osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC]
1954 osc ${cmd_name} PROJECT PACKAGE
1958 args = slash_split(args)
1969 new_project = args[0]
1970 new_package = args[1]
1972 old_project = opts.oldprj
1974 old_package = opts.oldpkg
1975 elif len(args) == 3 or len(args) == 4:
1976 if opts.oldprj or opts.oldpkg:
1977 raise oscerr.WrongArgs('--oldpkg and --oldprj are only valid with two arguments')
1978 old_project = args[0]
1979 new_package = old_package = args[1]
1980 new_project = args[2]
1982 new_package = args[3]
1984 raise oscerr.WrongArgs('Wrong number of arguments')
1989 rev = int(opts.change)
1999 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
2003 rev1, rev2 = parseRevisionOption(opts.revision)
2005 rdiff = server_diff(conf.config['apiurl'],
2006 old_project, old_package, rev1,
2007 new_project, new_package, rev2, not opts.plain, opts.missingok)
2012 def do_install(self, subcmd, opts, *args):
2013 """${cmd_name}: install a package after build via zypper in -r
2015 Not implemented yet. Use osc repourls,
2016 select the url you best like (standard),
2017 chop off after the last /, this should work with zypper.
2024 args = slash_split(args)
2025 args = expand_proj_pack(args)
2028 ## if there is only one argument, and it ends in .ymp
2029 ## then fetch it, Parse XML to get the first
2030 ## metapackage.group.repositories.repository.url
2031 ## and construct zypper cmd's for all
2032 ## metapackage.group.software.item.name
2034 ## if args[0] is already an url, the use it as is.
2036 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])
2037 print self.do_install.__doc__
2038 print "Example: \n" + cmd
2041 def do_repourls(self, subcmd, opts, *args):
2042 """${cmd_name}: Shows URLs of .repo files
2044 Shows URLs on which to access the project .repos files (yum-style
2045 metadata) on download.opensuse.org.
2048 osc repourls [PROJECT]
2053 apiurl = self.get_api_url()
2057 elif len(args) == 0:
2058 project = store_read_project('.')
2060 raise oscerr.WrongArgs('Wrong number of arguments')
2062 # XXX: API should somehow tell that
2063 url_tmpl = 'http://download.opensuse.org/repositories/%s/%s/%s.repo'
2064 repos = get_repositories_of_project(apiurl, project)
2066 print url_tmpl % (project.replace(':', ':/'), repo, project)
2069 @cmdln.option('-r', '--revision', metavar='rev',
2070 help='checkout the specified revision. '
2071 'NOTE: if you checkout the complete project '
2072 'this option is ignored!')
2073 @cmdln.option('-e', '--expand-link', action='store_true',
2074 help='if a package is a link, check out the expanded '
2075 'sources (no-op, since this became the default)')
2076 @cmdln.option('-u', '--unexpand-link', action='store_true',
2077 help='if a package is a link, check out the _link file ' \
2078 'instead of the expanded sources')
2079 @cmdln.option('-c', '--current-dir', action='store_true',
2080 help='place PACKAGE folder in the current directory' \
2081 'instead of a PROJECT/PACKAGE directory')
2082 @cmdln.option('-s', '--source-service-files', action='store_true',
2083 help='server side generated files of source services' \
2084 'gets downloaded as well' )
2085 @cmdln.option('-l', '--limit-size', metavar='limit_size',
2086 help='Skip all files with a given size')
2088 def do_checkout(self, subcmd, opts, *args):
2089 """${cmd_name}: Check out content from the repository
2091 Check out content from the repository server, creating a local working
2094 When checking out a single package, the option --revision can be used
2095 to specify a revision of the package to be checked out.
2097 When a package is a source link, then it will be checked out in
2098 expanded form. If --unexpand-link option is used, the checkout will
2099 instead produce the raw _link file plus patches.
2102 osc co PROJECT [PACKAGE] [FILE]
2103 osc co PROJECT # entire project
2104 osc co PROJECT PACKAGE # a package
2105 osc co PROJECT PACKAGE FILE # single file -> to current dir
2107 while inside a project directory:
2108 osc co PACKAGE # check out PACKAGE from project
2113 if opts.unexpand_link:
2117 if opts.source_service_files:
2118 service_files = True
2120 service_files = False
2122 args = slash_split(args)
2123 project = package = filename = None
2125 apiurl = self.get_api_url()
2128 project = project_dir = args[0]
2134 if args and len(args) == 1:
2135 localdir = os.getcwd()
2136 if is_project_dir(localdir):
2137 project = store_read_project(localdir)
2138 project_dir = localdir
2141 rev, dummy = parseRevisionOption(opts.revision)
2145 if rev and rev != "latest" and not checkRevision(project, package, rev):
2146 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2150 get_source_file(apiurl, project, package, filename, revision=rev, progress_obj=self.download_progress)
2153 if opts.current_dir:
2155 checkout_package(apiurl, project, package, rev, expand_link=expand_link, \
2156 prj_dir=project_dir, service_files=service_files, progress_obj=self.download_progress, limit_size=opts.limit_size)
2157 print_request_list(apiurl, project, package)
2161 if sys.platform[:3] == 'win':
2162 prj_dir = prj_dir.replace(':', ';')
2163 if os.path.exists(prj_dir):
2164 sys.exit('osc: project \'%s\' already exists' % project)
2166 # check if the project does exist (show_project_meta will throw an exception)
2167 show_project_meta(apiurl, project)
2169 init_project_dir(apiurl, prj_dir, project)
2170 print statfrmt('A', prj_dir)
2173 for package in meta_get_packagelist(apiurl, project):
2175 checkout_package(apiurl, project, package, expand_link = expand_link, \
2176 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress, limit_size=opts.limit_size)
2177 except oscerr.LinkExpandError, e:
2178 print >>sys.stderr, 'Link cannot be expanded:\n', e
2179 print >>sys.stderr, 'Use "osc repairlink" for fixing merge conflicts:\n'
2180 # check out in unexpanded form at least
2181 checkout_package(apiurl, project, package, expand_link = False, \
2182 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress, limit_size=opts.limit_size)
2183 print_request_list(apiurl, project)
2186 raise oscerr.WrongArgs('Missing argument.\n\n' \
2187 + self.get_cmd_help('checkout'))
2190 @cmdln.option('-q', '--quiet', action='store_true',
2191 help='print as little as possible')
2192 @cmdln.option('-v', '--verbose', action='store_true',
2193 help='print extra information')
2195 def do_status(self, subcmd, opts, *args):
2196 """${cmd_name}: Show status of files in working copy
2198 Show the status of files in a local working copy, indicating whether
2199 files have been changed locally, deleted, added, ...
2201 The first column in the output specifies the status and is one of the
2202 following characters:
2203 ' ' no modifications
2208 '?' item is not under version control
2209 '!' item is missing (removed by non-osc command) or incomplete
2214 osc st file1 file2 ...
2217 osc status [OPTS] [PATH...]
2221 args = parseargs(args)
2223 # storage for single Package() objects
2225 # storage for a project dir ( { prj_instance : [ package objects ] } )
2228 # when 'status' is run inside a project dir, it should
2229 # stat all packages existing in the wc
2230 if is_project_dir(arg):
2231 prj = Project(arg, False)
2233 if conf.config['do_package_tracking']:
2235 for pac in prj.pacs_have:
2236 # we cannot create package objects if the dir does not exist
2237 if not pac in prj.pacs_broken:
2238 prjpacs[prj].append(os.path.join(arg, pac))
2240 pacpaths += [arg + '/' + n for n in prj.pacs_have]
2241 elif is_package_dir(arg):
2242 pacpaths.append(arg)
2243 elif os.path.isfile(arg):
2244 pacpaths.append(arg)
2246 msg = '\'%s\' is neither a project or a package directory' % arg
2247 raise oscerr.NoWorkingCopy, msg
2249 # process single packages
2250 lines = getStatus(findpacs(pacpaths), None, opts.verbose, opts.quiet)
2251 # process project dirs
2252 for prj, pacs in prjpacs.iteritems():
2253 lines += getStatus(findpacs(pacs), prj, opts.verbose, opts.quiet)
2255 print '\n'.join(lines)
2258 def do_add(self, subcmd, opts, *args):
2259 """${cmd_name}: Mark files to be added upon the next commit
2261 In case a URL is given the file will get downloaded and registered to be downloaded
2262 by the server as well via the download_url source service.
2264 This is recommended for release tar balls to track their source and to help
2265 others to review your changes esp. on version upgrades.
2268 osc add URL [URL...]
2269 osc add FILE [FILE...]
2273 raise oscerr.WrongArgs('Missing argument.\n\n' \
2274 + self.get_cmd_help('add'))
2276 # Do some magic here, when adding a url. We want that the server to download the tar ball and to verify it
2277 for arg in parseargs(args):
2278 if arg.startswith('http://') or arg.startswith('https://') or arg.startswith('ftp://'):
2279 addDownloadUrlService(arg)
2284 def do_mkpac(self, subcmd, opts, *args):
2285 """${cmd_name}: Create a new package under version control
2288 osc mkpac new_package
2291 if not conf.config['do_package_tracking']:
2292 print >>sys.stderr, "to use this feature you have to enable \'do_package_tracking\' " \
2293 "in the [general] section in the configuration file"
2297 raise oscerr.WrongArgs('Wrong number of arguments.')
2299 createPackageDir(args[0])
2301 @cmdln.option('-r', '--recursive', action='store_true',
2302 help='If CWD is a project dir then scan all package dirs as well')
2304 def do_addremove(self, subcmd, opts, *args):
2305 """${cmd_name}: Adds new files, removes disappeared files
2307 Adds all files new in the local copy, and removes all disappeared files.
2309 ARG, if specified, is a package working copy.
2315 args = parseargs(args)
2317 for arg in arg_list:
2318 if is_project_dir(arg) and conf.config['do_package_tracking']:
2319 prj = Project(arg, False)
2320 for pac in prj.pacs_unvers:
2321 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2322 if os.path.isdir(pac_dir):
2323 addFiles([pac_dir], prj)
2324 for pac in prj.pacs_broken:
2325 if prj.get_state(pac) != 'D':
2326 prj.set_state(pac, 'D')
2327 print statfrmt('D', getTransActPath(os.path.join(prj.dir, pac)))
2329 for pac in prj.pacs_have:
2330 state = prj.get_state(pac)
2331 if state != None and state != 'D':
2332 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2333 args.append(pac_dir)
2335 prj.write_packages()
2336 elif is_project_dir(arg):
2337 print >>sys.stderr, 'osc: addremove is not supported in a project dir unless ' \
2338 '\'do_package_tracking\' is enabled in the configuration file'
2341 pacs = findpacs(args)
2343 p.todo = p.filenamelist + p.filenamelist_unvers
2345 for filename in p.todo:
2346 if os.path.isdir(filename):
2348 # ignore foo.rXX, foo.mine for files which are in 'C' state
2349 if os.path.splitext(filename)[0] in p.in_conflict:
2351 state = p.status(filename)
2354 # TODO: should ignore typical backup files suffix ~ or .orig
2356 print statfrmt('A', getTransActPath(os.path.join(p.dir, filename)))
2358 p.put_on_deletelist(filename)
2359 p.write_deletelist()
2360 os.unlink(os.path.join(p.storedir, filename))
2361 print statfrmt('D', getTransActPath(os.path.join(p.dir, filename)))
2366 @cmdln.alias('checkin')
2367 @cmdln.option('-m', '--message', metavar='TEXT',
2368 help='specify log message TEXT')
2369 @cmdln.option('-F', '--file', metavar='FILE',
2370 help='read log message from FILE')
2371 @cmdln.option('-f', '--force', default=False, action="store_true",
2372 help='force commit - do not tests a file list')
2373 @cmdln.option('--skip-validation', default=False, action="store_true",
2374 help='Skip the source validation')
2375 def do_commit(self, subcmd, opts, *args):
2376 """${cmd_name}: Upload content to the repository server
2378 Upload content which is changed in your working copy, to the repository
2381 Optionally checks the state of a working copy, if found a file with
2382 unknown state, it requests an user input:
2383 * skip - don't change anything, just move to another file
2384 * remove - remove a file from dir
2385 * edit file list - edit filelist using EDITOR
2386 * commit - don't check anything and commit package
2387 * abort - abort commit - this is default value
2388 This can be supressed by check_filelist config item, or -f/--force
2389 command line option.
2392 osc ci # current dir
2394 osc ci file1 file2 ...
2400 args = parseargs(args)
2403 validators = conf.config['source_validator_directory']
2404 if opts.skip_validation:
2406 elif not os.path.exists(validators):
2407 print "WARNING: validator directory", validators, "configured, but not existing. Skipping ..."
2414 msg = open(opts.file).read()
2416 sys.exit('could not open file \'%s\'.' % opts.file)
2419 for arg in arg_list:
2420 if conf.config['do_package_tracking'] and is_project_dir(arg):
2422 msg = edit_message()
2424 Project(arg).commit(msg=msg, validators=validators)
2425 except oscerr.RuntimeError, e:
2426 print >>sys.stderr, "ERROR: source_validator failed", e
2430 pacs = findpacs(args)
2432 if conf.config['check_filelist'] and not opts.force:
2433 check_filelist_before_commit(pacs)
2436 template = store_read_file(os.path.abspath('.'), '_commit_msg')
2437 # open editor for commit message
2438 # but first, produce status and diff to append to the template
2442 changed = getStatus([pac], quiet=True)
2445 diffs += ['\nDiff for working copy: %s' % pac.dir]
2446 diffs += make_diff(pac, 0)
2447 lines.extend(get_commit_message_template(pac))
2448 if template == None:
2449 template='\n'.join(lines)
2450 # if footer is empty, there is nothing to commit, and no edit needed.
2452 msg = edit_message(footer='\n'.join(footer), template=template)
2455 store_write_string(os.path.abspath('.'), '_commit_msg', msg)
2457 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2459 if conf.config['do_package_tracking'] and len(pacs) > 0:
2463 # it is possible to commit packages from different projects at the same
2464 # time: iterate over all pacs and put each pac to the right project in the dict
2466 path = os.path.normpath(os.path.join(pac.dir, os.pardir))
2467 if is_project_dir(path):
2468 pac_path = os.path.basename(os.path.normpath(pac.absdir))
2469 prj_paths.setdefault(path, []).append(pac_path)
2470 files[pac_path] = pac.todo
2472 single_paths.append(pac.dir)
2473 for prj, packages in prj_paths.iteritems():
2475 Project(prj).commit(tuple(packages), msg, files, validators=validators)
2476 except oscerr.RuntimeError, e:
2477 print >>sys.stderr, "ERROR: source_validator failed", e
2479 for pac in single_paths:
2481 Package(pac).commit(msg, validators=validators)
2482 except oscerr.RuntimeError, e:
2483 print >>sys.stderr, "ERROR: source_validator failed", e
2487 p.commit(msg, validators=validators)
2489 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2491 @cmdln.option('-r', '--revision', metavar='REV',
2492 help='update to specified revision (this option will be ignored '
2493 'if you are going to update the complete project or more than '
2495 @cmdln.option('-u', '--unexpand-link', action='store_true',
2496 help='if a package is an expanded link, update to the raw _link file')
2497 @cmdln.option('-e', '--expand-link', action='store_true',
2498 help='if a package is a link, update to the expanded sources')
2499 @cmdln.option('-s', '--source-service-files', action='store_true',
2500 help='Use server side generated sources instead of local generation.' )
2501 @cmdln.option('-l', '--limit-size', metavar='limit_size',
2502 help='Skip all files with a given size')
2504 def do_update(self, subcmd, opts, *args):
2505 """${cmd_name}: Update a working copy
2510 If the current working directory is a package, update it.
2511 If the directory is a project directory, update all contained
2512 packages, AND check out newly added packages.
2514 To update only checked out packages, without checking out new
2515 ones, you might want to use "osc up *" from within the project
2519 Update the packages specified by the path argument(s)
2521 When --expand-link is used with source link packages, the expanded
2522 sources will be checked out. Without this option, the _link file and
2523 patches will be checked out. The option --unexpand-link can be used to
2524 switch back to the "raw" source with a _link file plus patch(es).
2530 if (opts.expand_link and opts.unexpand_link) \
2531 or (opts.expand_link and opts.revision) \
2532 or (opts.unexpand_link and opts.revision):
2533 raise oscerr.WrongOptions('Sorry, the options --expand-link, --unexpand-link and '
2534 '--revision are mutually exclusive.')
2536 if opts.source_service_files: service_files = True
2537 else: service_files = False
2539 args = parseargs(args)
2542 for arg in arg_list:
2543 if is_project_dir(arg):
2544 prj = Project(arg, progress_obj=self.download_progress)
2546 if conf.config['do_package_tracking']:
2547 prj.update(expand_link=opts.expand_link,
2548 unexpand_link=opts.unexpand_link)
2551 # if not tracking package, and 'update' is run inside a project dir,
2552 # it should do the following:
2553 # (a) update all packages
2554 args += prj.pacs_have
2555 # (b) fetch new packages
2556 prj.checkout_missing_pacs(expand_link = not opts.unexpand_link)
2558 print_request_list(prj.apiurl, prj.name)
2561 pacs = findpacs(args, progress_obj=self.download_progress)
2563 if opts.revision and len(args) == 1:
2564 rev, dummy = parseRevisionOption(opts.revision)
2565 if not checkRevision(pacs[0].prjname, pacs[0].name, rev, pacs[0].apiurl):
2566 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2573 print 'Updating %s' % p.name
2575 # FIXME: ugly workaround for #399247
2576 if opts.expand_link or opts.unexpand_link:
2577 if [ i for i in p.filenamelist+p.filenamelist_unvers if p.status(i) != ' ' and p.status(i) != '?']:
2578 print >>sys.stderr, 'osc: cannot expand/unexpand because your working ' \
2579 'copy has local modifications.\nPlease revert/commit them ' \
2584 if opts.expand_link and p.islink() and not p.isexpanded():
2585 if p.haslinkerror():
2587 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev)
2589 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev, linkrev="base")
2592 p.update(rev, service_files, opts.limit_size)
2593 rev = p.linkinfo.xsrcmd5
2594 print 'Expanding to rev', rev
2595 elif opts.unexpand_link and p.islink() and p.isexpanded():
2596 print 'Unexpanding to rev', p.linkinfo.lsrcmd5
2597 p.update(rev, service_files, opts.limit_size)
2598 rev = p.linkinfo.lsrcmd5
2599 elif p.islink() and p.isexpanded():
2600 rev = p.latest_rev()
2602 p.update(rev, service_files, opts.limit_size)
2603 if opts.unexpand_link:
2606 print_request_list(p.apiurl, p.prjname, p.name)
2609 @cmdln.option('-f', '--force', action='store_true',
2610 help='forces removal of entire package and its files')
2613 @cmdln.alias('remove')
2614 def do_delete(self, subcmd, opts, *args):
2615 """${cmd_name}: Mark files or package directories to be deleted upon the next 'checkin'
2618 cd .../PROJECT/PACKAGE
2619 osc delete FILE [...]
2621 osc delete PACKAGE [...]
2623 This command works on check out copies. Use "rdelete" for working on server
2624 side only. This is needed for removing the entire project.
2626 As a safety measure, projects must be empty (i.e., you need to delete all
2629 If you are sure that you want to remove a package and all
2630 its files use \'--force\' switch. Sometimes this also works without --force.
2636 raise oscerr.WrongArgs('Missing argument.\n\n' \
2637 + self.get_cmd_help('delete'))
2639 args = parseargs(args)
2640 # check if args contains a package which was removed by
2641 # a non-osc command and mark it with the 'D'-state
2644 if not os.path.exists(i):
2645 prj_dir, pac_dir = getPrjPacPaths(i)
2646 if is_project_dir(prj_dir):
2647 prj = Project(prj_dir, False)
2648 if i in prj.pacs_broken:
2649 if prj.get_state(i) != 'A':
2650 prj.set_state(pac_dir, 'D')
2652 prj.del_package_node(i)
2653 print statfrmt('D', getTransActPath(i))
2655 prj.write_packages()
2656 pacs = findpacs(args)
2660 prj_dir, pac_dir = getPrjPacPaths(p.absdir)
2661 if is_project_dir(prj_dir):
2662 if conf.config['do_package_tracking']:
2663 prj = Project(prj_dir, False)
2664 prj.delPackage(p, opts.force)
2666 print "WARNING: package tracking is disabled, operation skipped !"
2668 pathn = getTransActPath(p.dir)
2669 for filename in p.todo:
2670 ret, state = p.delete_file(filename, opts.force)
2672 print statfrmt('D', os.path.join(pathn, filename))
2675 sys.exit('\'%s\' is not under version control' % filename)
2676 elif state in ['A', 'M'] and not opts.force:
2677 sys.exit('\'%s\' has local modifications (use --force to remove this file)' % filename)
2680 def do_resolved(self, subcmd, opts, *args):
2681 """${cmd_name}: Remove 'conflicted' state on working copy files
2683 If an upstream change can't be merged automatically, a file is put into
2684 in 'conflicted' ('C') state. Within the file, conflicts are marked with
2685 special <<<<<<< as well as ======== and >>>>>>> lines.
2687 After manually resolving all conflicting parts, use this command to
2688 remove the 'conflicted' state.
2690 Note: this subcommand does not semantically resolve conflicts or
2691 remove conflict markers; it merely removes the conflict-related
2692 artifact files and allows PATH to be committed again.
2695 osc resolved FILE [FILE...]
2700 raise oscerr.WrongArgs('Missing argument.\n\n' \
2701 + self.get_cmd_help('resolved'))
2703 args = parseargs(args)
2704 pacs = findpacs(args)
2707 for filename in p.todo:
2708 print 'Resolved conflicted state of "%s"' % filename
2709 p.clear_from_conflictlist(filename)
2712 @cmdln.alias('platforms')
2713 def do_repositories(self, subcmd, opts, *args):
2714 """${cmd_name}: Shows available repositories
2718 Shows all available repositories/build targets
2720 2. osc repositories <project>
2721 Shows the configured repositories/build targets of a project
2729 print '\n'.join(get_repositories_of_project(conf.config['apiurl'], project))
2731 print '\n'.join(get_repositories(conf.config['apiurl']))
2735 def do_results_meta(self, subcmd, opts, *args):
2736 print "Command results_meta is obsolete. Please use: osc results --xml"
2740 @cmdln.option('-l', '--last-build', action='store_true',
2741 help='show last build results (succeeded/failed/unknown)')
2742 @cmdln.option('-r', '--repo', action='append', default = [],
2743 help='Show results only for specified repo(s)')
2744 @cmdln.option('-a', '--arch', action='append', default = [],
2745 help='Show results only for specified architecture(s)')
2746 @cmdln.option('', '--xml', action='store_true',
2747 help='generate output in XML (former results_meta)')
2748 def do_rresults(self, subcmd, opts, *args):
2749 print "Command rresults is obsolete. Running 'osc results' instead"
2750 self.do_results('results', opts, *args)
2754 @cmdln.option('-f', '--force', action='store_true', default=False,
2755 help="Don't ask and delete files")
2756 def do_rremove(self, subcmd, opts, project, package, *files):
2757 """${cmd_name}: Remove source files from selected package
2764 if not '/' in project:
2765 raise oscerr.WrongArgs("Missing operand, type osc help rremove for help")
2768 project, package = project.split('/')
2772 resp = raw_input("rm: remove source file `%s' from `%s/%s'? (yY|nN) " % (file, project, package))
2773 if resp not in ('y', 'Y'):
2776 delete_files(conf.config['apiurl'], project, package, (file, ))
2777 except urllib2.HTTPError, e:
2779 print >>sys.stderr, e
2781 if e.code in [ 400, 403, 404, 500 ]:
2782 if '<summary>' in body:
2783 msg = body.split('<summary>')[1]
2784 msg = msg.split('</summary>')[0]
2785 print >>sys.stderr, msg
2790 @cmdln.option('-l', '--last-build', action='store_true',
2791 help='show last build results (succeeded/failed/unknown)')
2792 @cmdln.option('-r', '--repo', action='append', default = [],
2793 help='Show results only for specified repo(s)')
2794 @cmdln.option('-a', '--arch', action='append', default = [],
2795 help='Show results only for specified architecture(s)')
2796 @cmdln.option('', '--xml', action='store_true', default=False,
2797 help='generate output in XML (former results_meta)')
2798 @cmdln.option('', '--csv', action='store_true', default=False,
2799 help='generate output in CSV format')
2800 @cmdln.option('', '--format', default='%(repository)s|%(arch)s|%(state)s|%(dirty)s|%(code)s|%(details)s',
2801 help='format string for csv output')
2802 def do_results(self, subcmd, opts, *args):
2803 """${cmd_name}: Shows the build results of a package
2806 osc results (inside working copy)
2807 osc results remote_project remote_package
2812 args = slash_split(args)
2814 apiurl = self.get_api_url()
2817 if is_project_dir(wd):
2821 opts.hide_legend = None
2822 opts.name_filter = None
2823 opts.status_filter = None
2824 opts.vertical = None
2825 self.do_prjresults('prjresults', opts, *args)
2828 project = store_read_project(wd)
2829 package = store_read_package(wd)
2831 raise oscerr.WrongArgs('Too few arguments (required none or two)')
2833 raise oscerr.WrongArgs('Too many arguments (required none or two)')
2838 if opts.xml and opts.csv:
2839 raise oscerr.WrongOptions("--xml and --csv are mutual exclusive")
2842 func = show_results_meta
2846 return format_results(get_package_results(*args), opts.format)
2853 print delim.join(func(apiurl, project, package, opts.last_build, opts.repo, opts.arch))
2855 # WARNING: this function is also called by do_results. You need to set a default there
2856 # as well when adding a new option!
2857 @cmdln.option('-q', '--hide-legend', action='store_true',
2858 help='hide the legend')
2859 @cmdln.option('-c', '--csv', action='store_true',
2861 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2862 help='show only packages with buildstatus STATUS (see legend)')
2863 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2864 help='show only packages whose names match EXPR')
2865 @cmdln.option('-a', '--arch', metavar='ARCH',
2866 help='show results only for specified architecture(s)')
2867 @cmdln.option('-r', '--repo', metavar='REPO',
2868 help='show results only for specified repo(s)')
2869 @cmdln.option('-V', '--vertical', action='store_true',
2870 help='list packages vertically instead horizontally')
2872 def do_prjresults(self, subcmd, opts, *args):
2873 """${cmd_name}: Shows project-wide build results
2876 osc prjresults (inside working copy)
2877 osc prjresults PROJECT
2881 apiurl = self.get_api_url()
2887 raise oscerr.WrongArgs('Wrong number of arguments.')
2890 project = store_read_project(wd)
2892 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))
2895 @cmdln.option('-q', '--hide-legend', action='store_true',
2896 help='hide the legend')
2897 @cmdln.option('-c', '--csv', action='store_true',
2899 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2900 help='show only packages with buildstatus STATUS (see legend)')
2901 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2902 help='show only packages whose names match EXPR')
2905 def do_rprjresults(self, subcmd, opts, *args):
2906 print "Command rprjresults is obsolete. Please use 'osc prjresults'"
2910 @cmdln.option('-s', '--start', metavar='START',
2911 help='get log starting from the offset')
2912 def do_buildlog(self, subcmd, opts, *args):
2913 """${cmd_name}: Shows the build log of a package
2915 Shows the log file of the build of a package. Can be used to follow the
2916 log while it is being written.
2917 Needs to be called from within a package directory.
2919 The arguments REPOSITORY and ARCH are the first two columns in the 'osc
2920 results' output. If the buildlog url is used buildlog command has the
2921 same behavior as remotebuildlog.
2923 ${cmd_usage} [REPOSITORY ARCH | BUILDLOGURL]
2927 repository = arch = None
2929 apiurl = self.get_api_url()
2931 if len(args) == 1 and args[0].startswith('http'):
2932 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2935 package = store_read_package(wd)
2936 project = store_read_project(wd)
2940 offset = int(opts.start)
2942 if not repository or not arch:
2946 repository = args[0]
2949 print_buildlog(apiurl, project, package, repository, arch, offset)
2952 def print_repos(self):
2955 if is_package_dir(wd):
2958 elif is_project_dir(wd):
2963 print 'Valid arguments for this %s are:' % str
2965 self.do_repos(None, None)
2967 raise oscerr.WrongArgs('Missing arguments')
2970 @cmdln.alias('rbuildlog')
2971 @cmdln.option('-s', '--start', metavar='START',
2972 help='get log starting from the offset')
2973 def do_remotebuildlog(self, subcmd, opts, *args):
2974 """${cmd_name}: Shows the build log of a package
2976 Shows the log file of the build of a package. Can be used to follow the
2977 log while it is being written.
2980 osc remotebuildlog project package repository arch
2982 osc remotebuildlog project/package/repository/arch
2984 osc remotebuildlog buildlogurl
2987 if len(args) == 1 and args[0].startswith('http'):
2988 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2990 args = slash_split(args)
2991 apiurl = conf.config['apiurl']
2993 raise oscerr.WrongArgs('Too few arguments.')
2995 raise oscerr.WrongArgs('Too many arguments.')
2997 project, package, repository, arch = args
3001 offset = int(opts.start)
3003 print_buildlog(apiurl, project, package, repository, arch, offset)
3006 @cmdln.option('-s', '--start', metavar='START',
3007 help='get log starting from offset')
3008 def do_localbuildlog(self, subcmd, opts, *args):
3009 """${cmd_name}: Shows the build log of a local buildchroot
3012 osc lbl [REPOSITORY ARCH]
3013 osc lbl # show log of newest last local build
3017 if conf.config['build-type']:
3018 # FIXME: raise Exception instead
3019 print >>sys.stderr, 'Not implemented for VMs'
3023 package = store_read_package('.')
3025 files = glob.glob(os.path.join(os.getcwd(), store, "_buildinfo-*"))
3028 raise oscerr.WrongArgs('No buildconfig found, please specify repo and arch manually.')
3032 if os.stat(f).st_mtime > os.stat(cfg).st_mtime:
3034 root = ET.parse(cfg).getroot()
3035 project = root.get("project")
3036 repo = root.get("repository")
3037 arch = root.find("arch").text
3038 elif len(args) == 2:
3039 project = store_read_project('.')
3040 package = store_read_package('.')
3044 if is_package_dir(os.curdir):
3046 raise oscerr.WrongArgs('Wrong number of arguments.')
3048 buildroot = os.environ.get('OSC_BUILD_ROOT', conf.config['build-root'])
3049 buildroot = buildroot % {'project': project, 'package': package,
3050 'repo': repo, 'arch': arch}
3053 offset = int(opts.start)
3054 logfile = os.path.join(buildroot, '.build.log')
3055 if not os.path.isfile(logfile):
3056 raise oscerr.OscIOError(None, 'logfile \'%s\' does not exist' % logfile)
3057 f = open(logfile, 'r')
3059 data = f.read(BUFSIZE)
3061 sys.stdout.write(data)
3062 data = f.read(BUFSIZE)
3066 def do_triggerreason(self, subcmd, opts, *args):
3067 """${cmd_name}: Show reason why a package got triggered to build
3069 The server decides when a package needs to get rebuild, this command
3070 shows the detailed reason for a package. A brief reason is also stored
3071 in the jobhistory, which can be accessed via "osc jobhistory".
3073 Trigger reasons might be:
3074 - new build (never build yet or rebuild manually forced)
3075 - source change (eg. on updating sources)
3076 - meta change (packages which are used for building have changed)
3077 - rebuild count sync (In case that it is configured to sync release numbers)
3079 usage in package or project directory:
3080 osc reason REPOSITORY ARCH
3081 osc reason PROJECT PACKAGE REPOSITORY ARCH