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 @cmdln.option('-D', '--deleted', action='store_true',
215 help='show only the former deleted projects or packages')
216 def do_list(self, subcmd, opts, *args):
217 """${cmd_name}: List sources or binaries on the server
219 Examples for listing sources:
220 ls # list all projects
221 ls PROJECT # list packages in a project
222 ls PROJECT PACKAGE # list source files of package of a project
223 ls PROJECT PACKAGE <file> # list <file> if this file exists
224 ls -v PROJECT PACKAGE # verbosely list source files of package
225 ls -l PROJECT PACKAGE # verbosely list source files of package
226 ll PROJECT PACKAGE # verbosely list source files of package
227 LL PROJECT PACKAGE # verbosely list source files of expanded link
229 With --verbose, the following fields will be shown for each item:
231 Revision number of the last commit
233 Date and time of the last commit
235 Examples for listing binaries:
236 ls -b PROJECT # list all binaries of a project
237 ls -b PROJECT -a ARCH # list ARCH binaries of a project
238 ls -b PROJECT -r REPO # list binaries in REPO
239 ls -b PROJECT PACKAGE REPO ARCH
242 ${cmd_name} [PROJECT [PACKAGE]]
243 ${cmd_name} -b [PROJECT [PACKAGE [REPO [ARCH]]]]
247 apiurl = conf.config['apiurl']
248 args = slash_split(args)
251 if subcmd == 'lL' or subcmd == 'LL':
263 raise oscerr.WrongArgs("Too many arguments when listing deleted packages")
266 raise oscerr.WrongArgs("Too many arguments when listing deleted packages")
269 if opts.repo != args[2]:
270 raise oscerr.WrongArgs("conflicting repos specified ('%s' vs '%s')"%(opts.repo, args[2]))
277 if not opts.binaries:
278 raise oscerr.WrongArgs('Too many arguments')
280 if opts.arch != args[3]:
281 raise oscerr.WrongArgs("conflicting archs specified ('%s' vs '%s')"%(opts.arch, args[3]))
286 if opts.binaries and opts.expand:
287 raise oscerr.WrongOptions('Sorry, --binaries and --expand are mutual exclusive.')
291 # ls -b toplevel doesn't make sense, so use info from
292 # current dir if available
295 if is_project_dir(dir):
296 project = store_read_project(dir)
297 elif is_package_dir(dir):
298 project = store_read_project(dir)
299 package = store_read_package(dir)
301 apiurl = self.get_api_url()
304 raise oscerr.WrongArgs('There are no binaries to list above project level.')
306 raise oscerr.WrongOptions('Sorry, the --revision option is not supported for binaries.')
310 if opts.repo and opts.arch:
311 repos.append(Repo(opts.repo, opts.arch))
312 elif opts.repo and not opts.arch:
313 repos = [repo for repo in get_repos_of_project(apiurl, project) if repo.name == opts.repo]
314 elif opts.arch and not opts.repo:
315 repos = [repo for repo in get_repos_of_project(apiurl, project) if repo.arch == opts.arch]
317 repos = get_repos_of_project(apiurl, project)
321 results.append((repo, get_binarylist(apiurl, project, repo.name, repo.arch, package=package, verbose=opts.verbose)))
323 for result in results:
326 print '%s/%s' % (result[0].name, result[0].arch)
331 print "%9d %s %-40s" % (f.size, shorttime(f.mtime), f.name)
337 elif not opts.binaries:
339 print '\n'.join(meta_get_project_list(conf.config['apiurl'], opts.deleted))
343 if self.options.verbose:
344 print >>sys.stderr, 'Sorry, the --verbose option is not implemented for projects.'
346 raise oscerr.WrongOptions('Sorry, the --expand option is not implemented for projects.')
348 print '\n'.join(meta_get_packagelist(conf.config['apiurl'], project, opts.deleted))
350 elif len(args) == 2 or len(args) == 3:
352 print_not_found = True
355 l = meta_get_filelist(conf.config['apiurl'],
358 verbose=opts.verbose,
361 link_seen = '_link' in l
363 out = [ '%s %7s %9d %s %s' % (i.md5, i.rev, i.size, shorttime(i.mtime), i.name) \
364 for i in l if not fname or fname == i.name ]
366 print_not_found = False
371 print_not_found = False
374 if opts.expand or opts.unexpand or not link_seen: break
375 m = show_files_meta(conf.config['apiurl'], project, package)
377 li.read(ET.fromstring(''.join(m)).find('linkinfo'))
379 raise oscerr.LinkExpandError(project, package, li.error)
380 project, package, rev = li.project, li.package, li.rev
382 print '# -> %s %s (%s)' % (project, package, rev)
384 print '# -> %s %s (latest)' % (project, package)
386 if fname and print_not_found:
387 print 'file \'%s\' does not exist' % fname
390 @cmdln.option('-f', '--force', action='store_true',
391 help='force generation of new patchinfo file')
392 @cmdln.option('--force-update', action='store_true',
393 help='drops away collected packages from an already built patch and let it collect again')
394 def do_patchinfo(self, subcmd, opts, *args):
395 """${cmd_name}: Generate and edit a patchinfo file.
397 A patchinfo file describes the packages for an update and the kind of
402 osc patchinfo PATCH_NAME
406 project_dir = localdir = os.getcwd()
407 if is_project_dir(localdir):
408 project = store_read_project(localdir)
409 apiurl = self.get_api_url()
411 sys.exit('This command must be called in a checked out project.')
413 for p in meta_get_packagelist(apiurl, project):
414 if p.startswith("_patchinfo:"):
417 if opts.force or not patchinfo:
418 print "Creating initial patchinfo..."
419 query='cmd=createpatchinfo'
421 query += "&name=" + args[0]
422 url = makeurl(apiurl, ['source', project], query=query)
424 for p in meta_get_packagelist(apiurl, project):
425 if p.startswith("_patchinfo:"):
428 if not os.path.exists(project_dir + "/" + patchinfo):
429 checkout_package(apiurl, project, patchinfo, prj_dir=project_dir)
431 filename = project_dir + "/" + patchinfo + "/_patchinfo"
435 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
436 help='affect only a given attribute')
437 @cmdln.option('--attribute-defaults', action='store_true',
438 help='include defined attribute defaults')
439 @cmdln.option('--attribute-project', action='store_true',
440 help='include project values, if missing in packages ')
441 @cmdln.option('-F', '--file', metavar='FILE',
442 help='read metadata from FILE, instead of opening an editor. '
443 '\'-\' denotes standard input. ')
444 @cmdln.option('-e', '--edit', action='store_true',
445 help='edit metadata')
446 @cmdln.option('-c', '--create', action='store_true',
447 help='create attribute without values')
448 @cmdln.option('-s', '--set', metavar='ATTRIBUTE_VALUES',
449 help='set attribute values')
450 @cmdln.option('--delete', action='store_true',
451 help='delete a pattern or attribute')
452 def do_meta(self, subcmd, opts, *args):
453 """${cmd_name}: Show meta information, or edit it
455 Show or edit build service metadata of type <prj|pkg|prjconf|user|pattern>.
457 This command displays metadata on buildservice objects like projects,
458 packages, or users. The type of metadata is specified by the word after
459 "meta", like e.g. "meta prj".
461 prj denotes metadata of a buildservice project.
462 prjconf denotes the (build) configuration of a project.
463 pkg denotes metadata of a buildservice package.
464 user denotes the metadata of a user.
465 pattern denotes installation patterns defined for a project.
467 To list patterns, use 'osc meta pattern PRJ'. An additional argument
468 will be the pattern file to view or edit.
470 With the --edit switch, the metadata can be edited. Per default, osc
471 opens the program specified by the environmental variable EDITOR with a
472 temporary file. Alternatively, content to be saved can be supplied via
473 the --file switch. If the argument is '-', input is taken from stdin:
474 osc meta prjconf home:user | sed ... | osc meta prjconf home:user -F -
476 When trying to edit a non-existing resource, it is created implicitly.
482 osc meta pkg PRJ PKG -e
483 osc meta attribute PRJ [PKG [SUBPACKAGE]] [--attribute ATTRIBUTE] [--create|--delete|--set [value_list]]
486 osc meta <prj|pkg|prjconf|user|pattern|attribute> ARGS...
487 osc meta <prj|pkg|prjconf|user|pattern|attribute> -e|--edit ARGS...
488 osc meta <prj|pkg|prjconf|user|pattern|attribute> -F|--file ARGS...
489 osc meta pattern --delete PRJ PATTERN
493 args = slash_split(args)
495 if not args or args[0] not in metatypes.keys():
496 raise oscerr.WrongArgs('Unknown meta type. Choose one of %s.' \
497 % ', '.join(metatypes))
502 apiurl = self.get_api_url()
505 min_args, max_args = 0, 2
506 elif cmd in ['pattern']:
507 min_args, max_args = 1, 2
508 elif cmd in ['attribute']:
509 min_args, max_args = 1, 3
510 elif cmd in ['prj', 'prjconf']:
511 min_args, max_args = 0, 1
513 min_args, max_args = 1, 1
515 if len(args) < min_args:
516 raise oscerr.WrongArgs('Too few arguments.')
517 if len(args) > max_args:
518 raise oscerr.WrongArgs('Too many arguments.')
522 if cmd in ['pkg', 'prj', 'prjconf' ]:
524 project = store_read_project(os.curdir)
530 package = store_read_package(os.curdir)
534 elif cmd == 'attribute':
540 if opts.attribute_project:
541 raise oscerr.WrongOptions('--attribute-project works only when also a package is given')
546 attributepath.append('source')
547 attributepath.append(project)
549 attributepath.append(package)
551 attributepath.append(subpackage)
552 attributepath.append('_attribute')
555 elif cmd == 'pattern':
561 # enforce pattern argument if needed
562 if opts.edit or opts.file:
563 raise oscerr.WrongArgs('A pattern file argument is required.')
566 if not opts.edit and not opts.file and not opts.delete and not opts.create and not opts.set:
568 sys.stdout.write(''.join(show_project_meta(apiurl, project)))
570 sys.stdout.write(''.join(show_package_meta(apiurl, project, package)))
571 elif cmd == 'attribute':
572 sys.stdout.write(''.join(show_attribute_meta(apiurl, project, package, subpackage, opts.attribute, opts.attribute_defaults, opts.attribute_project)))
573 elif cmd == 'prjconf':
574 sys.stdout.write(''.join(show_project_conf(apiurl, project)))
576 r = get_user_meta(apiurl, user)
578 sys.stdout.write(''.join(r))
579 elif cmd == 'pattern':
581 r = show_pattern_meta(apiurl, project, pattern)
583 sys.stdout.write(''.join(r))
585 r = show_pattern_metalist(apiurl, project)
587 sys.stdout.write('\n'.join(r) + '\n')
590 if opts.edit and not opts.file:
592 edit_meta(metatype='prj',
594 path_args=quote_plus(project),
598 'user': conf.config['user']}))
600 edit_meta(metatype='pkg',
602 path_args=(quote_plus(project), quote_plus(package)),
606 'user': conf.config['user']}))
607 elif cmd == 'prjconf':
608 edit_meta(metatype='prjconf',
610 path_args=quote_plus(project),
614 edit_meta(metatype='user',
616 path_args=(quote_plus(user)),
618 template_args=({'user': user}))
619 elif cmd == 'pattern':
620 edit_meta(metatype='pattern',
622 path_args=(project, pattern),
626 # create attribute entry
627 if (opts.create or opts.set) and cmd == 'attribute':
628 if not opts.attribute:
629 raise oscerr.WrongOptions('no attribute given to create')
632 opts.set = opts.set.replace('&', '&').replace('<', '<').replace('>', '>')
633 for i in opts.set.split(','):
634 values += '<value>%s</value>' % i
635 aname = opts.attribute.split(":")
636 d = '<attributes><attribute namespace=\'%s\' name=\'%s\' >%s</attribute></attributes>' % (aname[0], aname[1], values)
637 url = makeurl(apiurl, attributepath)
638 for data in streamfile(url, http_POST, data=d):
639 sys.stdout.write(data)
648 f = open(opts.file).read()
650 sys.exit('could not open file \'%s\'.' % opts.file)
653 edit_meta(metatype='prj',
657 path_args=quote_plus(project))
659 edit_meta(metatype='pkg',
663 path_args=(quote_plus(project), quote_plus(package)))
664 elif cmd == 'prjconf':
665 edit_meta(metatype='prjconf',
669 path_args=quote_plus(project))
671 edit_meta(metatype='user',
675 path_args=(quote_plus(user)))
676 elif cmd == 'pattern':
677 edit_meta(metatype='pattern',
681 path_args=(project, pattern))
686 path = metatypes[cmd]['path']
688 path = path % (project, pattern)
689 u = makeurl(apiurl, [path])
691 elif cmd == 'attribute':
692 if not opts.attribute:
693 raise oscerr.WrongOptions('no attribute given to create')
694 attributepath.append(opts.attribute)
695 u = makeurl(apiurl, attributepath)
696 for data in streamfile(u, http_DELETE):
697 sys.stdout.write(data)
699 raise oscerr.WrongOptions('The --delete switch is only for pattern metadata or attributes.')
702 @cmdln.option('-m', '--message', metavar='TEXT',
703 help='specify message TEXT')
704 @cmdln.option('-r', '--revision', metavar='REV',
705 help='for "create", specify a certain source revision ID (the md5 sum)')
706 @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
707 help='Superseding another request by this one')
708 @cmdln.option('--nodevelproject', action='store_true',
709 help='do not follow a defined devel project ' \
710 '(primary project where a package is developed)')
711 @cmdln.option('--cleanup', action='store_true',
712 help='remove package if submission gets accepted (default for home:<id>:branch projects)')
713 @cmdln.option('--no-cleanup', action='store_true',
714 help='never remove source package on accept, but update its content')
715 @cmdln.option('--no-update', action='store_true',
716 help='never touch source package on accept (will break source links)')
717 @cmdln.option('-d', '--diff', action='store_true',
718 help='show diff only instead of creating the actual request')
719 @cmdln.option('--yes', action='store_true',
720 help='proceed without asking.')
722 @cmdln.alias("submitreq")
723 @cmdln.alias("submitpac")
724 def do_submitrequest(self, subcmd, opts, *args):
725 """${cmd_name}: Create request to submit source into another Project
727 [See http://en.opensuse.org/Build_Service/Collaboration for information
730 See the "request" command for showing and modifing existing requests.
733 osc submitreq [OPTIONS]
734 osc submitreq [OPTIONS] DESTPRJ [DESTPKG]
735 osc submitreq [OPTIONS] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
739 src_update = conf.config['submitrequest_on_accept_action'] or None
740 # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
742 src_update = "cleanup"
743 elif opts.no_cleanup:
744 src_update = "update"
746 src_update = "noupdate"
748 args = slash_split(args)
750 # remove this block later again
751 oldcmds = ['create', 'list', 'log', 'show', 'decline', 'accept', 'delete', 'revoke']
752 if args and args[0] in oldcmds:
753 print "************************************************************************"
754 print "* WARNING: It looks that you are using this command with a *"
755 print "* deprecated syntax. *"
756 print "* Please run \"osc sr --help\" and \"osc rq --help\" *"
757 print "* to see the new syntax. *"
758 print "************************************************************************"
759 if args[0] == 'create':
765 raise oscerr.WrongArgs('Too many arguments.')
767 if len(args) > 0 and len(args) <= 2 and is_project_dir(os.getcwd()):
768 sys.exit('osc submitrequest from project directory is only working without target specs and for source linked files\n')
770 apiurl = self.get_api_url()
772 if len(args) == 0 and is_project_dir(os.getcwd()):
774 # submit requests for multiple packages are currently handled via multiple requests
775 # They could be also one request with multiple actions, but that avoids to accepts parts of it.
776 project = store_read_project(os.curdir)
782 # loop via all packages for checking their state
783 for p in meta_get_packagelist(apiurl, project):
784 if p.startswith("_patchinfo:"):
787 # get _link info from server, that knows about the local state ...
788 u = makeurl(apiurl, ['source', project, p])
790 root = ET.parse(f).getroot()
791 linkinfo = root.find('linkinfo')
793 print "Package ", p, " is not a source link."
794 sys.exit("This is currently not supported.")
795 if linkinfo.get('error'):
796 print "Package ", p, " is a broken source link."
797 sys.exit("Please fix this first")
798 t = linkinfo.get('project')
800 if len(root.findall('entry')) > 1: # This is not really correct, but should work mostly
801 # Real fix is to ask the api if sources are modificated
802 # but there is no such call yet.
803 targetprojects.append(t)
805 print "Submitting package ", p
807 print " Skipping package ", p
809 print "Skipping package ", p, " since it is a source link pointing inside the project."
813 print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
814 print "\nEverything fine? Can we create the requests ? [y/n]"
815 if sys.stdin.read(1) != "y":
816 print >>sys.stderr, 'Aborted...'
817 raise oscerr.UserAbort()
819 # loop via all packages to do the action
821 result = create_submit_request(apiurl, project, p)
824 sys.exit("submit request creation failed")
825 sr_ids.append(result)
827 # create submit requests for all found patchinfos
831 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
834 for t in targetprojects:
835 s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \
836 (project, p, t, p, options_block)
840 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
841 (actionxml, cgi.escape(opts.message or ""))
842 u = makeurl(apiurl, ['request'], query='cmd=create')
843 f = http_POST(u, data=xml)
845 root = ET.parse(f).getroot()
846 sr_ids.append(root.get('id'))
848 print "Requests created: ",
851 sys.exit('Successfull finished')
854 # try using the working copy at hand
855 p = findpacs(os.curdir)[0]
856 src_project = p.prjname
859 if len(args) == 0 and p.islink():
860 dst_project = p.linkinfo.project
861 dst_package = p.linkinfo.package
863 dst_project = args[0]
865 dst_package = args[1]
867 dst_package = src_package
869 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
870 'Please provide it the target via commandline arguments.' % p.name)
872 modified = [i for i in p.filenamelist if p.status(i) != ' ' and p.status(i) != '?']
873 if len(modified) > 0:
874 print 'Your working copy has local modifications.'
875 repl = raw_input('Proceed without committing the local changes? (y|N) ')
877 raise oscerr.UserAbort()
879 # get the arguments from the commandline
880 src_project, src_package, dst_project = args[0:3]
882 dst_package = args[3]
884 dst_package = src_package
886 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
887 + self.get_cmd_help('request'))
889 if not opts.nodevelproject:
892 devloc = show_develproject(apiurl, dst_project, dst_package)
893 except urllib2.HTTPError:
894 print >>sys.stderr, """\
895 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
896 % (dst_project, dst_package)
900 dst_project != devloc and \
901 src_project != devloc:
903 A different project, %s, is defined as the place where development
904 of the package %s primarily takes place.
905 Please submit there instead, or use --nodevelproject to force direct submission.""" \
906 % (devloc, dst_package)
911 if opts.diff or not opts.message:
913 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
914 rdiff += server_diff(apiurl,
915 dst_project, dst_package, opts.revision,
916 src_project, src_package, None, True)
922 reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit')
923 user = conf.get_apiurl_usr(apiurl)
924 myreqs = [ i for i in reqs if i.state.who == user ]
927 print 'You already created the following submit request: %s.' % \
928 ', '.join([str(i.reqid) for i in myreqs ])
929 repl = raw_input('Supersede the old requests? (y/n/c) ')
930 if repl.lower() == 'c':
931 print >>sys.stderr, 'Aborting'
932 raise oscerr.UserAbort()
937 changes_re = re.compile(r'^--- .*\.changes ')
938 for line in rdiff.split('\n'):
939 if line.startswith('--- '):
940 if changes_re.match(line):
945 difflines.append(line)
946 opts.message = edit_message(footer=rdiff, template='\n'.join(parse_diff_for_commit_message('\n'.join(difflines))))
948 result = create_submit_request(apiurl,
949 src_project, src_package,
950 dst_project, dst_package,
951 opts.message, orev=opts.revision, src_update=src_update)
952 if repl.lower() == 'y':
954 change_request_state(apiurl, str(req.reqid), 'superseded',
955 'superseded by %s' % result, result)
958 r = change_request_state(conf.config['apiurl'],
959 opts.supersede, 'superseded', opts.message or '', result)
961 print 'created request id', result
963 def _actionparser(self, opt_str, value, parser):
965 if not hasattr(parser.values, 'actiondata'):
966 setattr(parser.values, 'actiondata', [])
967 if parser.values.actions == None:
968 parser.values.actions = []
973 if ((arg[:2] == "--" and len(arg) > 2) or
974 (arg[:1] == "-" and len(arg) > 1 and arg[1] != "-")):
980 parser.values.actions.append(value[0])
982 parser.values.actiondata.append(value)
984 def _submit_request(self, args, opts, options_block):
986 apiurl = self.get_api_url()
987 if len(args) == 0 and is_project_dir(os.getcwd()):
989 # submit requests for multiple packages are currently handled via multiple requests
990 # They could be also one request with multiple actions, but that avoids to accepts parts of it.
991 project = store_read_project(os.curdir)
997 # loop via all packages for checking their state
998 for p in meta_get_packagelist(apiurl, project):
999 if p.startswith("_patchinfo:"):
1002 # get _link info from server, that knows about the local state ...
1003 u = makeurl(apiurl, ['source', project, p])
1005 root = ET.parse(f).getroot()
1006 linkinfo = root.find('linkinfo')
1007 if linkinfo == None:
1008 print "Package ", p, " is not a source link."
1009 sys.exit("This is currently not supported.")
1010 if linkinfo.get('error'):
1011 print "Package ", p, " is a broken source link."
1012 sys.exit("Please fix this first")
1013 t = linkinfo.get('project')
1017 rdiff = server_diff(apiurl, t, p, opts.revision, project, p, None, True)
1022 targetprojects.append(t)
1024 rdiffmsg.append("old: %s/%s\nnew: %s/%s\n%s" %(t, p, project, p,rdiff))
1026 print "Skipping package ", p, " since it has no difference with the target package."
1028 print "Skipping package ", p, " since it is a source link pointing inside the project."
1030 print ''.join(rdiffmsg)
1035 print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
1036 print "\nEverything fine? Can we create the requests ? [y/n]"
1037 if sys.stdin.read(1) != "y":
1038 sys.exit("Aborted...")
1040 # loop via all packages to do the action
1042 s = """<action type="submit"> <source project="%s" package="%s" rev="%s"/> <target project="%s" package="%s"/> %s </action>""" % \
1043 (project, p, opts.revision or show_upstream_rev(apiurl, project, p), t, p, options_block)
1046 # create submit requests for all found patchinfos
1048 for t in targetprojects:
1049 s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \
1050 (project, p, t, p, options_block)
1055 elif len(args) <= 2:
1056 # try using the working copy at hand
1057 p = findpacs(os.curdir)[0]
1058 src_project = p.prjname
1059 src_package = p.name
1060 if len(args) == 0 and p.islink():
1061 dst_project = p.linkinfo.project
1062 dst_package = p.linkinfo.package
1064 dst_project = args[0]
1066 dst_package = args[1]
1068 dst_package = src_package
1070 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
1071 'Please provide it the target via commandline arguments.' % p.name)
1073 modified = [i for i in p.filenamelist if p.status(i) != ' ' and p.status(i) != '?']
1074 if len(modified) > 0:
1075 print 'Your working copy has local modifications.'
1076 repl = raw_input('Proceed without committing the local changes? (y|N) ')
1079 elif len(args) >= 3:
1080 # get the arguments from the commandline
1081 src_project, src_package, dst_project = args[0:3]
1083 dst_package = args[3]
1085 dst_package = src_package
1087 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1088 + self.get_cmd_help('request'))
1090 if not opts.nodevelproject:
1093 devloc = show_develproject(apiurl, dst_project, dst_package)
1094 except urllib2.HTTPError:
1095 print >>sys.stderr, """\
1096 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
1097 % (dst_project, dst_package)
1101 dst_project != devloc and \
1102 src_project != devloc:
1104 A different project, %s, is defined as the place where development
1105 of the package %s primarily takes place.
1106 Please submit there instead, or use --nodevelproject to force direct submission.""" \
1107 % (devloc, dst_package)
1114 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
1115 rdiff += server_diff(apiurl,
1116 dst_project, dst_package, opts.revision,
1117 src_project, src_package, None, True)
1123 reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit')
1124 user = conf.get_apiurl_usr(apiurl)
1125 myreqs = [ i for i in reqs if i.state.who == user ]
1128 print 'You already created the following submit request: %s.' % \
1129 ', '.join([str(i.reqid) for i in myreqs ])
1130 repl = raw_input('Supersede the old requests? (y/n/c) ')
1131 if repl.lower() == 'c':
1132 print >>sys.stderr, 'Aborting'
1135 actionxml = """<action type="submit"> <source project="%s" package="%s" rev="%s"/> <target project="%s" package="%s"/> %s </action>""" % \
1136 (src_project, src_package, opts.revision or show_upstream_rev(apiurl, src_project, src_package), dst_project, dst_package, options_block)
1137 if repl.lower() == 'y':
1139 change_request_state(apiurl, str(req.reqid), 'superseded',
1140 'superseded by %s' % result, result)
1143 r = change_request_state(apiurl,
1144 opts.supersede, 'superseded', '', result)
1146 #print 'created request id', result
1149 def _delete_request(self, args, opts):
1151 raise oscerr.WrongArgs('Please specify at least a project.')
1153 raise oscerr.WrongArgs('Too many arguments.')
1157 package = """package="%s" """ % (args[1])
1158 actionxml = """<action type="delete"> <target project="%s" %s/> </action> """ % (args[0], package)
1161 def _changedevel_request(self, args, opts):
1163 raise oscerr.WrongArgs('Too many arguments.')
1165 if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
1167 devel_project = store_read_project(wd)
1168 devel_package = package = store_read_package(wd)
1169 project = conf.config['getpac_default_project']
1172 raise oscerr.WrongArgs('Too few arguments.')
1174 devel_project = args[2]
1177 devel_package = package
1179 devel_package = args[3]
1181 actionxml = """ <action type="change_devel"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> </action> """ % \
1182 (devel_project, devel_package, project, package)
1186 def _add_role(self, args, opts):
1188 raise oscerr.WrongArgs('Too many arguments.')
1190 raise oscerr.WrongArgs('Too few arguments.')
1192 apiurl = self.get_api_url()
1200 if get_user_meta(apiurl, user) == None:
1201 raise oscerr.WrongArgs('osc: an error occured.')
1203 actionxml = """ <action type="add_role"> <target project="%s" package="%s" /> <person name="%s" role="%s" /> </action> """ % \
1204 (project, package, user, role)
1208 def _set_bugowner(self, args, opts):
1210 raise oscerr.WrongArgs('Too many arguments.')
1212 raise oscerr.WrongArgs('Too few arguments.')
1214 apiurl = self.get_api_url()
1221 if get_user_meta(apiurl, user) == None:
1222 raise oscerr.WrongArgs('osc: an error occured.')
1224 actionxml = """ <action type="set_bugowner"> <target project="%s" package="%s" /> <person name="%s" /> </action> """ % \
1225 (project, package, user)
1229 @cmdln.option('-a', '--action', action='callback', callback = _actionparser,dest = 'actions',
1230 help='specify action type of a request, can be : submit/delete/change_devel/add_role/set_bugowner')
1231 @cmdln.option('-m', '--message', metavar='TEXT',
1232 help='specify message TEXT')
1233 @cmdln.option('-r', '--revision', metavar='REV',
1234 help='for "create", specify a certain source revision ID (the md5 sum)')
1235 @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
1236 help='Superseding another request by this one')
1237 @cmdln.option('--nodevelproject', action='store_true',
1238 help='do not follow a defined devel project ' \
1239 '(primary project where a package is developed)')
1240 @cmdln.option('--cleanup', action='store_true',
1241 help='remove package if submission gets accepted (default for home:<id>:branch projects)')
1242 @cmdln.option('--no-cleanup', action='store_true',
1243 help='never remove source package on accept, but update its content')
1244 @cmdln.option('--no-update', action='store_true',
1245 help='never touch source package on accept (will break source links)')
1246 @cmdln.option('-d', '--diff', action='store_true',
1247 help='show diff only instead of creating the actual request')
1248 @cmdln.option('--yes', action='store_true',
1249 help='proceed without asking.')
1250 @cmdln.alias("creq")
1251 def do_createrequest(self, subcmd, opts, *args):
1252 """${cmd_name}: create multiple requests with a single command
1255 osc creq [OPTIONS] [
1256 -a submit SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
1257 -a delete PROJECT [PACKAGE]
1258 -a change_devel PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
1259 -a add_role USER ROLE PROJECT [PACKAGE]
1260 -a set_bugowner USER PROJECT [PACKAGE]
1263 Option -m works for all types of request, the rest work only for submit.
1265 osc creq -a submit -a delete home:someone:branches:openSUSE:Tools -a change_devel openSUSE:Tools osc home:someone:branches:openSUSE:Tools -m ok
1267 This will submit all modified packages under current directory, delete project home:someone:branches:openSUSE:Tools and change the devel project to home:someone:branches:openSUSE:Tools for package osc in project openSUSE:Tools.
1270 src_update = conf.config['submitrequest_on_accept_action'] or None
1271 # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
1273 src_update = "cleanup"
1274 elif opts.no_cleanup:
1275 src_update = "update"
1276 elif opts.no_update:
1277 src_update = "noupdate"
1281 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
1283 args = slash_split(args)
1285 apiurl = self.get_api_url()
1289 for ai in opts.actions:
1291 args = opts.actiondata[i]
1293 actionsxml += self._submit_request(args,opts, options_block)
1294 elif ai == 'delete':
1295 args = opts.actiondata[i]
1296 actionsxml += self._delete_request(args,opts)
1298 elif ai == 'change_devel':
1299 args = opts.actiondata[i]
1300 actionsxml += self._changedevel_request(args,opts)
1302 elif ai == 'add_role':
1303 args = opts.actiondata[i]
1304 actionsxml += self._add_role(args,opts)
1306 elif ai == 'set_bugowner':
1307 args = opts.actiondata[i]
1308 actionsxml += self._set_bugowner(args,opts)
1311 raise oscerr.WrongArgs('Unsupported action %s' % ai)
1312 if actionsxml == "":
1313 sys.exit('No actions need to be taken.')
1315 if not opts.message:
1316 opts.message = edit_message()
1319 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
1320 (actionsxml, cgi.escape(opts.message or ""))
1321 u = makeurl(apiurl, ['request'], query='cmd=create')
1322 f = http_POST(u, data=xml)
1324 root = ET.parse(f).getroot()
1325 return root.get('id')
1328 @cmdln.option('-m', '--message', metavar='TEXT',
1329 help='specify message TEXT')
1331 @cmdln.alias("deletereq")
1332 def do_deleterequest(self, subcmd, opts, *args):
1333 """${cmd_name}: Create request to delete a package or project
1337 osc deletereq [-m TEXT] PROJECT [PACKAGE]
1341 args = slash_split(args)
1344 raise oscerr.WrongArgs('Please specify at least a project.')
1346 raise oscerr.WrongArgs('Too many arguments.')
1348 apiurl = conf.config['apiurl']
1355 if not opts.message:
1356 opts.message = edit_message()
1358 result = create_delete_request(apiurl, project, package, opts.message)
1362 @cmdln.option('-m', '--message', metavar='TEXT',
1363 help='specify message TEXT')
1365 @cmdln.alias("changedevelreq")
1366 def do_changedevelrequest(self, subcmd, opts, *args):
1367 """${cmd_name}: Create request to change the devel package definition.
1369 [See http://en.opensuse.org/Build_Service/Collaboration for information
1372 See the "request" command for showing and modifing existing requests.
1374 osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
1378 raise oscerr.WrongArgs('Too many arguments.')
1380 apiurl = self.get_api_url()
1382 if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
1384 devel_project = store_read_project(wd)
1385 devel_package = package = store_read_package(wd)
1386 project = conf.config['getpac_default_project']
1389 raise oscerr.WrongArgs('Too few arguments.')
1391 devel_project = args[2]
1394 devel_package = package
1396 devel_package = args[3]
1398 if not opts.message:
1400 footer=textwrap.TextWrapper(width = 66).fill(
1401 'please explain why you like to change the devel project of %s/%s to %s/%s'
1402 % (project,package,devel_project,devel_package))
1403 opts.message = edit_message(footer)
1405 result = create_change_devel_request(apiurl,
1406 devel_project, devel_package,
1412 @cmdln.option('-d', '--diff', action='store_true',
1413 help='generate a diff')
1414 @cmdln.option('-u', '--unified', action='store_true',
1415 help='output the diff in the unified diff format')
1416 @cmdln.option('-m', '--message', metavar='TEXT',
1417 help='specify message TEXT')
1418 @cmdln.option('-t', '--type', metavar='TYPE',
1419 help='limit to requests which contain a given action type (submit/delete/change_devel)')
1420 @cmdln.option('-a', '--all', action='store_true',
1421 help='all states. Same as\'-s all\'')
1422 @cmdln.option('-s', '--state', default='', # default is 'all' if no args given, 'new' otherwise
1423 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]')
1424 @cmdln.option('-D', '--days', metavar='DAYS',
1425 help='only list requests in state "new" or changed in the last DAYS. [default=%(request_list_days)s]')
1426 @cmdln.option('-U', '--user', metavar='USER',
1427 help='same as -M, but for the specified USER')
1428 @cmdln.option('-b', '--brief', action='store_true', default=False,
1429 help='print output in list view as list subcommand')
1430 @cmdln.option('-M', '--mine', action='store_true',
1431 help='only show requests created by yourself')
1432 @cmdln.option('-B', '--bugowner', action='store_true',
1433 help='also show requests about packages where I am bugowner')
1434 @cmdln.option('-i', '--interactive', action='store_true',
1435 help='interactive review of request')
1436 @cmdln.option('--non-interactive', action='store_true',
1437 help='non-interactive review of request')
1438 @cmdln.option('--exclude-target-project', action='append',
1439 help='exclude target project from request list')
1440 @cmdln.option('--involved-projects', action='store_true',
1441 help='show all requests for project/packages where USER is involved')
1443 @cmdln.alias("review")
1444 def do_request(self, subcmd, opts, *args):
1445 """${cmd_name}: Show and modify requests
1447 [See http://en.opensuse.org/Build_Service/Collaboration for information
1450 This command shows and modifies existing requests. To create new requests
1451 you need to call one of the following:
1454 osc changedevelrequest
1455 To send low level requests to the buildservice API, use:
1458 This command has the following sub commands:
1460 "list" lists open requests attached to a project or package or person.
1461 Uses the project/package of the current directory if none of
1462 -M, -U USER, project/package are given.
1464 "log" will show the history of the given ID
1466 "show" will show the request itself, and generate a diff for review, if
1467 used with the --diff option. The keyword show can be omitted if the ID is numeric.
1469 "decline" will change the request state to "declined" and append a
1470 message that you specify with the --message option.
1472 "wipe" will permanently delete a request.
1474 "revoke" will set the request state to "revoked" and append a
1475 message that you specify with the --message option.
1477 "accept" will change the request state to "accepted" and will trigger
1478 the actual submit process. That would normally be a server-side copy of
1479 the source package to the target package.
1481 "checkout" will checkout the request's source package. This only works for "submit" requests.
1484 osc request list [-M] [-U USER] [-s state] [-D DAYS] [-t type] [-B] [PRJ [PKG]]
1486 osc request [show] [-d] [-b] ID
1487 osc request accept [-m TEXT] ID
1488 osc request approvenew [-m TEXT] PROJECT
1489 osc request decline [-m TEXT] ID
1490 osc request revoke [-m TEXT] ID
1492 osc request checkout/co ID
1493 osc review accept [-m TEXT] ID
1494 osc review decline [-m TEXT] ID
1495 osc review new [-m TEXT] ID # for setting a temporary comment without changing the state
1499 args = slash_split(args)
1501 if opts.all and opts.state:
1502 raise oscerr.WrongOptions('Sorry, the options \'--all\' and \'--state\' ' \
1503 'are mutually exclusive.')
1504 if opts.mine and opts.user:
1505 raise oscerr.WrongOptions('Sorry, the options \'--user\' and \'--mine\' ' \
1506 'are mutually exclusive.')
1507 if opts.interactive and opts.non_interactive:
1508 raise oscerr.WrongOptions('Sorry, the options \'--interactive\' and ' \
1509 '\'--non-interactive\' are mutually exclusive')
1514 if opts.state == '':
1517 if opts.state == '':
1520 cmds = ['list', 'log', 'show', 'decline', 'accept', 'approvenew', 'wipe', 'revoke', 'checkout', 'co', 'help']
1521 if not args or args[0] not in cmds:
1522 raise oscerr.WrongArgs('Unknown request action %s. Choose one of %s.' \
1523 % (args[0],', '.join(cmds)))
1529 return self.do_help(['help', 'request'])
1532 min_args, max_args = 0, 2
1534 min_args, max_args = 1, 1
1535 if len(args) < min_args:
1536 raise oscerr.WrongArgs('Too few arguments.')
1537 if len(args) > max_args:
1538 raise oscerr.WrongArgs('Too many arguments.')
1540 apiurl = self.get_api_url()
1542 if cmd == 'list' or cmd == 'approvenew':
1547 elif not opts.mine and not opts.user:
1549 project = store_read_project(os.curdir)
1550 package = store_read_package(os.curdir)
1551 except oscerr.NoWorkingCopy:
1556 elif cmd in ['log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co']:
1559 # list and approvenew
1560 if cmd == 'list' or cmd == 'approvenew':
1561 states = ('new', 'accepted', 'revoked', 'declined')
1563 if cmd == 'approvenew':
1565 results = get_request_list(apiurl, project, package, '', ['new'])
1567 state_list = opts.state.split(',')
1568 if opts.state == 'all':
1569 state_list = ['all']
1571 for s in state_list:
1573 raise oscerr.WrongArgs('Unknown state \'%s\', try one of %s' % (s, ','.join(states)))
1575 who = conf.get_apiurl_usr(apiurl)
1579 state_list = ['all']
1581 ## FIXME -B not implemented!
1583 if (self.options.debug):
1584 print 'list: option --bugowner ignored: not impl.'
1586 if opts.involved_projects:
1587 who = who or conf.get_apiurl_usr(apiurl)
1588 results = get_user_projpkgs_request_list(apiurl, who, req_state=state_list,
1589 req_type=opts.type, exclude_projects=opts.exclude_target_project or [])
1591 results = get_request_list(apiurl, project, package, who,
1592 state_list, opts.type, opts.exclude_target_project or [])
1594 results.sort(reverse=True)
1596 days = opts.days or conf.config['request_list_days']
1603 since = time.strftime('%Y-%m-%dT%H:%M:%S',time.localtime(time.time()-days*24*3600))
1606 ## bs has received 2009-09-20 a new xquery compare() function
1607 ## which allows us to limit the list inside of get_request_list
1608 ## That would be much faster for coolo. But counting the remainder
1609 ## would not be possible with current xquery implementation.
1610 ## Workaround: fetch all, and filter on client side.
1612 ## FIXME: date filtering should become implemented on server side
1613 for result in results:
1614 if days == 0 or result.state.when > since or result.state.name == 'new':
1615 print result.list_view()
1619 print "There are %d requests older than %s days.\n" % (skipped, days)
1621 if cmd == 'approvenew':
1622 print "\n *** Approve them all ? [y/n] ***"
1623 if sys.stdin.read(1) == "y":
1625 if not opts.message:
1626 opts.message = edit_message()
1627 for result in results:
1628 print result.reqid, ": ",
1629 r = change_request_state(conf.config['apiurl'],
1630 str(result.reqid), 'accepted', opts.message or '')
1633 print >>sys.stderr, 'Aborted...'
1634 raise oscerr.UserAbort()
1637 for l in get_request_log(conf.config['apiurl'], reqid):
1642 r = get_request(conf.config['apiurl'], reqid)
1645 elif (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive:
1646 return request_interactive_review(conf.config['apiurl'], r)
1649 # fixme: will inevitably fail if the given target doesn't exist
1650 if opts.diff and r.actions[0].type != 'submit':
1651 raise oscerr.WrongOptions('\'--diff\' is not possible for request type: \'%s\'' % r.actions[0].type)
1654 print server_diff(conf.config['apiurl'],
1655 r.actions[0].dst_project, r.actions[0].dst_package, None,
1656 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, True)
1657 except urllib2.HTTPError, e:
1659 e.osc_msg = 'Diff not possible'
1661 # backward compatiblity: only a recent api/backend supports the missingok parameter
1663 print server_diff(conf.config['apiurl'],
1664 r.actions[0].dst_project, r.actions[0].dst_package, None,
1665 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, False)
1666 except urllib2.HTTPError, e:
1667 e.osc_msg = 'Diff not possible'
1671 elif cmd == 'checkout' or cmd == 'co':
1672 r = get_request(conf.config['apiurl'], reqid)
1673 submits = [ i for i in r.actions if i.type == 'submit' ]
1674 if not len(submits):
1675 raise oscerr.WrongArgs('\'checkout\' only works for \'submit\' requests')
1676 checkout_package(conf.config['apiurl'], submits[0].src_project, submits[0].src_package, \
1677 submits[0].src_rev, expand_link=True, prj_dir=submits[0].src_project)
1680 if not opts.message:
1681 opts.message = edit_message()
1682 state_map = {'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked'}
1683 # Change review state only
1684 if subcmd == 'review':
1685 if cmd in ['accept', 'decline', 'new']:
1686 r = change_review_state(conf.config['apiurl'],
1687 reqid, state_map[cmd], conf.config['user'], '', opts.message or '')
1689 # Change state of entire request
1690 elif cmd in ['accept', 'decline', 'wipe', 'revoke']:
1691 r = change_request_state(conf.config['apiurl'],
1692 reqid, state_map[cmd], opts.message or '')
1695 # editmeta and its aliases are all depracated
1696 @cmdln.alias("editprj")
1697 @cmdln.alias("createprj")
1698 @cmdln.alias("editpac")
1699 @cmdln.alias("createpac")
1700 @cmdln.alias("edituser")
1701 @cmdln.alias("usermeta")
1703 def do_editmeta(self, subcmd, opts, *args):
1706 Obsolete command to edit metadata. Use 'meta' now.
1708 See the help output of 'meta'.
1712 print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
1713 print >>sys.stderr, 'See \'osc help meta\'.'
1714 #self.do_help([None, 'meta'])
1718 @cmdln.option('-r', '--revision', metavar='rev',
1719 help='use the specified revision.')
1720 @cmdln.option('-u', '--unset', action='store_true',
1721 help='remove revision in link, it will point always to latest revision')
1722 def do_setlinkrev(self, subcmd, opts, *args):
1723 """${cmd_name}: Updates a revision number in a source link.
1725 This command adds or updates a specified revision number in a source link.
1726 The current revision of the source is used, if no revision number is specified.
1730 osc setlinkrev PROJECT [PACKAGE]
1734 args = slash_split(args)
1735 apiurl = conf.config['apiurl']
1738 p = findpacs(os.curdir)[0]
1743 sys.exit('Local directory is no checked out source link package, aborting')
1744 elif len(args) == 2:
1747 elif len(args) == 1:
1750 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1751 + self.get_cmd_help('setlinkrev'))
1754 packages = [ package ]
1756 packages = meta_get_packagelist(apiurl, project)
1759 print "setting revision for package", p
1763 rev, dummy = parseRevisionOption(opts.revision)
1764 set_link_rev(apiurl, project, p, rev)
1767 def do_linktobranch(self, subcmd, opts, *args):
1768 """${cmd_name}: Convert a package containing a classic link with patch to a branch
1770 This command tells the server to convert a _link with or without a project.diff
1771 to a branch. This is a full copy with a _link file pointing to the branched place.
1774 osc linktobranch # can be used in checked out package
1775 osc linktobranch PROJECT PACKAGE
1778 args = slash_split(args)
1779 apiurl = self.get_api_url()
1783 project = store_read_project(wd)
1784 package = store_read_package(wd)
1785 update_local_dir = True
1787 raise oscerr.WrongArgs('Too few arguments (required none or two)')
1789 raise oscerr.WrongArgs('Too many arguments (required none or two)')
1793 update_local_dir = False
1796 link_to_branch(apiurl, project, package)
1797 if update_local_dir:
1799 pac.update(rev=pac.latest_rev())
1802 @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'],
1803 help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.')
1804 @cmdln.option('-c', '--current', action='store_true',
1805 help='link fixed against current revision.')
1806 @cmdln.option('-r', '--revision', metavar='rev',
1807 help='link the specified revision.')
1808 @cmdln.option('-f', '--force', action='store_true',
1809 help='overwrite an existing link file if it is there.')
1810 @cmdln.option('-d', '--disable-publish', action='store_true',
1811 help='disable publishing of the linked package')
1812 def do_linkpac(self, subcmd, opts, *args):
1813 """${cmd_name}: "Link" a package to another package
1815 A linked package is a clone of another package, but plus local
1816 modifications. It can be cross-project.
1818 The DESTPAC name is optional; the source packages' name will be used if
1821 Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
1823 To add a patch, add the patch as file and add it to the _link file.
1824 You can also specify text which will be inserted at the top of the spec file.
1826 See the examples in the _link file.
1829 osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1833 args = slash_split(args)
1835 if not args or len(args) < 3:
1836 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1837 + self.get_cmd_help('linkpac'))
1839 rev, dummy = parseRevisionOption(opts.revision)
1841 src_project = args[0]
1842 src_package = args[1]
1843 dst_project = args[2]
1845 dst_package = args[3]
1847 dst_package = src_package
1849 if src_project == dst_project and src_package == dst_package:
1850 raise oscerr.WrongArgs('Error: source and destination are the same.')
1852 if src_project == dst_project and not opts.cicount:
1853 # in this case, the user usually wants to build different spec
1854 # files from the same source
1855 opts.cicount = "copy"
1858 rev = show_upstream_rev(conf.config['apiurl'], src_project, src_package)
1860 if rev and not checkRevision(src_project, src_package, rev):
1861 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
1864 link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish)
1866 @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]',
1867 help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]')
1868 @cmdln.option('-d', '--disable-publish', action='store_true',
1869 help='disable publishing of the aggregated package')
1870 def do_aggregatepac(self, subcmd, opts, *args):
1871 """${cmd_name}: "Aggregate" a package to another package
1873 Aggregation of a package means that the build results (binaries) of a
1874 package are basically copied into another project.
1875 This can be used to make packages available from building that are
1876 needed in a project but available only in a different project. Note
1877 that this is done at the expense of disk space. See
1878 http://en.opensuse.org/Build_Service/Tips_and_Tricks#_link_and__aggregate
1879 for more information.
1881 The DESTPAC name is optional; the source packages' name will be used if
1885 osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1889 args = slash_split(args)
1891 if not args or len(args) < 3:
1892 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1893 + self.get_cmd_help('aggregatepac'))
1895 src_project = args[0]
1896 src_package = args[1]
1897 dst_project = args[2]
1899 dst_package = args[3]
1901 dst_package = src_package
1903 if src_project == dst_project and src_package == dst_package:
1904 raise oscerr.WrongArgs('Error: source and destination are the same.')
1908 for pair in opts.map_repo.split(','):
1909 src_tgt = pair.split('=')
1910 if len(src_tgt) != 2:
1911 raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo)
1912 repo_map[src_tgt[0]] = src_tgt[1]
1914 aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish)
1917 @cmdln.option('-c', '--client-side-copy', action='store_true',
1918 help='do a (slower) client-side copy')
1919 @cmdln.option('-k', '--keep-maintainers', action='store_true',
1920 help='keep original maintainers. Default is remove all and replace with the one calling the script.')
1921 @cmdln.option('-d', '--keep-develproject', action='store_true',
1922 help='keep develproject tag in the package metadata')
1923 @cmdln.option('-r', '--revision', metavar='rev',
1924 help='link the specified revision.')
1925 @cmdln.option('-t', '--to-apiurl', metavar='URL',
1926 help='URL of destination api server. Default is the source api server.')
1927 @cmdln.option('-m', '--message', metavar='TEXT',
1928 help='specify message TEXT')
1929 @cmdln.option('-e', '--expand', action='store_true',
1930 help='if the source package is a link then copy the expanded version of the link')
1931 def do_copypac(self, subcmd, opts, *args):
1932 """${cmd_name}: Copy a package
1934 A way to copy package to somewhere else.
1936 It can be done across buildservice instances, if the -t option is used.
1937 In that case, a client-side copy is implied.
1939 Using --client-side-copy always involves downloading all files, and
1940 uploading them to the target.
1942 The DESTPAC name is optional; the source packages' name will be used if
1946 osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1950 args = slash_split(args)
1952 if not args or len(args) < 3:
1953 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1954 + self.get_cmd_help('copypac'))
1956 src_project = args[0]
1957 src_package = args[1]
1958 dst_project = args[2]
1960 dst_package = args[3]
1962 dst_package = src_package
1964 src_apiurl = conf.config['apiurl']
1966 dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl)
1968 dst_apiurl = src_apiurl
1970 if src_apiurl != dst_apiurl:
1971 opts.client_side_copy = True
1973 rev, dummy = parseRevisionOption(opts.revision)
1976 comment = opts.message
1979 rev = show_upstream_rev(src_apiurl, src_project, src_package)
1980 comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev )
1982 if src_project == dst_project and \
1983 src_package == dst_package and \
1985 src_apiurl == dst_apiurl:
1986 raise oscerr.WrongArgs('Source and destination are the same.')
1988 r = copy_pac(src_apiurl, src_project, src_package,
1989 dst_apiurl, dst_project, dst_package,
1990 client_side_copy=opts.client_side_copy,
1991 keep_maintainers=opts.keep_maintainers,
1992 keep_develproject=opts.keep_develproject,
1999 @cmdln.option('-c', '--checkout', action='store_true',
2000 help='Checkout branched package afterwards ' \
2001 '(\'osc bco\' is a shorthand for this option)' )
2002 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
2003 help='Use this attribute to find affected packages (default is OBS:Maintained)')
2004 @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE',
2005 help='Use this attribute to find update projects (default is OBS:UpdateProject) ')
2006 def do_mbranch(self, subcmd, opts, *args):
2007 """${cmd_name}: Multiple branch of a package
2009 [See http://en.opensuse.org/Build_Service/Concepts/Maintenance for information
2012 This command is used for creating multiple links of defined version of a package
2013 in one project. This is esp. used for maintenance updates.
2015 The branched package will live in
2016 home:USERNAME:branches:ATTRIBUTE:PACKAGE
2017 if nothing else specified.
2020 osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ]
2023 args = slash_split(args)
2026 maintained_attribute = conf.config['maintained_attribute']
2027 maintained_update_project_attribute = conf.config['maintained_update_project_attribute']
2029 if not len(args) or len(args) > 2:
2030 raise oscerr.WrongArgs('Wrong number of arguments.')
2036 r = attribute_branch_pkg(conf.config['apiurl'], maintained_attribute, maintained_update_project_attribute, \
2040 print >>sys.stderr, 'ERROR: Attribute branch call came not back with a project.'
2043 print "Project " + r + " created."
2046 init_project_dir(conf.config['apiurl'], r, r)
2047 print statfrmt('A', r)
2050 for package in meta_get_packagelist(conf.config['apiurl'], r):
2052 checkout_package(conf.config['apiurl'], r, package, expand_link = True, prj_dir = r)
2054 print >>sys.stderr, 'Error while checkout package:\n', package
2056 if conf.config['verbose']:
2057 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
2060 @cmdln.alias('branchco')
2062 @cmdln.alias('getpac')
2063 @cmdln.option('--nodevelproject', action='store_true',
2064 help='do not follow a defined devel project ' \
2065 '(primary project where a package is developed)')
2066 @cmdln.option('-c', '--checkout', action='store_true',
2067 help='Checkout branched package afterwards ' \
2068 '(\'osc bco\' is a shorthand for this option)' )
2069 @cmdln.option('-f', '--force', default=False, action="store_true",
2070 help='force branch, overwrite target')
2071 @cmdln.option('-m', '--message', metavar='TEXT',
2072 help='specify message TEXT')
2073 @cmdln.option('-r', '--revision', metavar='rev',
2074 help='branch against a specific revision')
2075 def do_branch(self, subcmd, opts, *args):
2076 """${cmd_name}: Branch a package
2078 [See http://en.opensuse.org/Build_Service/Collaboration for information
2081 Create a source link from a package of an existing project to a new
2082 subproject of the requesters home project (home:branches:)
2084 The branched package will live in
2085 home:USERNAME:branches:PROJECT/PACKAGE
2086 if nothing else specified.
2088 With getpac or bco, the branched package will come from
2089 %(getpac_default_project)s
2090 if nothing else specified.
2094 osc branch SOURCEPROJECT SOURCEPACKAGE
2095 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT
2096 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE
2097 osc getpac SOURCEPACKAGE
2102 if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': opts.checkout = True
2103 args = slash_split(args)
2104 tproject = tpackage = None
2106 if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1:
2107 print >>sys.stderr, 'defaulting to %s/%s' % (conf.config['getpac_default_project'], args[0])
2108 # python has no args.unshift ???
2109 args = [ conf.config['getpac_default_project'] , args[0] ]
2111 if len(args) == 0 and is_package_dir('.'):
2112 args = (store_read_project('.'), store_read_package('.'))
2114 if len(args) < 2 or len(args) > 4:
2115 raise oscerr.WrongArgs('Wrong number of arguments.')
2117 expected = 'home:%s:branches:%s' % (conf.config['user'], args[0])
2119 expected = tproject = args[2]
2123 if not opts.message:
2124 footer='please specify the purpose of your branch'
2125 template='This package was branched from %s in order to ...\n' % args[0]
2126 opts.message = edit_message(footer, template)
2128 exists, targetprj, targetpkg, srcprj, srcpkg = \
2129 branch_pkg(conf.config['apiurl'], args[0], args[1],
2130 nodevelproject=opts.nodevelproject, rev=opts.revision,
2131 target_project=tproject, target_package=tpackage,
2132 return_existing=opts.checkout, msg=opts.message or '',
2135 print >>sys.stderr, 'Using existing branch project: %s' % targetprj
2138 if not exists and (srcprj is not None and srcprj != args[0] or \
2139 srcprj is None and targetprj != expected):
2140 devloc = srcprj or targetprj
2141 if not srcprj and 'branches:' in targetprj:
2142 devloc = targetprj.split('branches:')[1]
2143 print '\nNote: The branch has been created of a different project,\n' \
2145 ' which is the primary location of where development for\n' \
2146 ' that package takes place.\n' \
2147 ' That\'s also where you would normally make changes against.\n' \
2148 ' A direct branch of the specified package can be forced\n' \
2149 ' with the --nodevelproject option.\n' % devloc
2151 package = tpackage or args[1]
2153 checkout_package(conf.config['apiurl'], targetprj, package,
2154 expand_link=True, prj_dir=targetprj)
2155 if conf.config['verbose']:
2156 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
2159 if conf.get_configParser().get('general', 'apiurl') != conf.config['apiurl']:
2160 apiopt = '-A %s ' % conf.config['apiurl']
2161 print 'A working copy of the branched package can be checked out with:\n\n' \
2163 % (apiopt, targetprj, package)
2164 print_request_list(conf.config['apiurl'], args[0], args[1])
2166 print_request_list(conf.config['apiurl'], devloc, args[1])
2169 def do_undelete(self, subcmd, opts, *args):
2170 """${cmd_name}: Restores a deleted project or package on the server.
2172 The server restores a package including the sources and meta configuration.
2173 Binaries remain to be lost and will be rebuild.
2176 osc undelete PROJECT
2177 osc undelete PROJECT PACKAGE [PACKAGE ...]
2182 args = slash_split(args)
2184 raise oscerr.WrongArgs('Missing argument.')
2190 undelete_package(conf.config['apiurl'], prj, pkg)
2192 undelete_project(conf.config['apiurl'], prj)
2195 @cmdln.option('-f', '--force', action='store_true',
2196 help='deletes a package or an empty project')
2197 def do_rdelete(self, subcmd, opts, *args):
2198 """${cmd_name}: Delete a project or packages on the server.
2200 As a safety measure, project must be empty (i.e., you need to delete all
2201 packages first). If you are sure that you want to remove this project and all
2202 its packages use \'--force\' switch.
2205 osc rdelete -f PROJECT
2206 osc rdelete PROJECT PACKAGE [PACKAGE ...]
2211 args = slash_split(args)
2213 raise oscerr.WrongArgs('Missing argument.')
2219 # careful: if pkg is an empty string, the package delete request results
2220 # into a project delete request - which works recursively...
2222 delete_package(conf.config['apiurl'], prj, pkg)
2223 elif len(meta_get_packagelist(conf.config['apiurl'], prj)) >= 1 and not opts.force:
2224 print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
2225 'If you are sure that you want to remove this project and all its ' \
2226 'packages use the \'--force\' switch'
2229 delete_project(conf.config['apiurl'], prj)
2232 def do_deletepac(self, subcmd, opts, *args):
2233 print """${cmd_name} is obsolete !
2236 osc delete for checked out packages or projects
2238 osc rdelete for server side operations."""
2243 @cmdln.option('-f', '--force', action='store_true',
2244 help='deletes a project and its packages')
2245 def do_deleteprj(self, subcmd, opts, project):
2246 """${cmd_name} is obsolete !
2253 @cmdln.alias('metafromspec')
2254 @cmdln.option('', '--specfile', metavar='FILE',
2255 help='Path to specfile. (if you pass more than working copy this option is ignored)')
2256 def do_updatepacmetafromspec(self, subcmd, opts, *args):
2257 """${cmd_name}: Update package meta information from a specfile
2259 ARG, if specified, is a package working copy.
2265 args = parseargs(args)
2266 if opts.specfile and len(args) == 1:
2267 specfile = opts.specfile
2270 pacs = findpacs(args)
2272 p.read_meta_from_spec(specfile)
2273 p.update_package_meta()
2277 @cmdln.option('-c', '--change', metavar='rev',
2278 help='the change made by revision rev (like -r rev-1:rev).'
2279 'If rev is negative this is like -r rev:rev-1.')
2280 @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
2281 help='If rev1 is specified it will compare your working copy against '
2282 'the revision (rev1) on the server. '
2283 'If rev1 and rev2 are specified it will compare rev1 against rev2 '
2284 '(NOTE: changes in your working copy are ignored in this case)')
2285 @cmdln.option('-p', '--plain', action='store_true',
2286 help='output the diff in plain (not unified) diff format')
2287 @cmdln.option('--missingok', action='store_true',
2288 help='do not fail if the source or target project/package does not exist on the server')
2289 def do_diff(self, subcmd, opts, *args):
2290 """${cmd_name}: Generates a diff
2292 Generates a diff, comparing local changes against the repository
2295 ARG, specified, is a filename to include in the diff.
2301 args = parseargs(args)
2302 pacs = findpacs(args)
2306 rev = int(opts.change)
2316 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
2319 rev1, rev2 = parseRevisionOption(opts.revision)
2323 diff += ''.join(make_diff(pac, rev1))
2325 diff += server_diff(pac.apiurl, pac.prjname, pac.name, rev1,
2326 pac.prjname, pac.name, rev2, not opts.plain, opts.missingok)
2331 @cmdln.option('--oldprj', metavar='OLDPRJ',
2332 help='project to compare against'
2333 ' (deprecated, use 3 argument form)')
2334 @cmdln.option('--oldpkg', metavar='OLDPKG',
2335 help='package to compare against'
2336 ' (deprecated, use 3 argument form)')
2337 @cmdln.option('-r', '--revision', metavar='N[:M]',
2338 help='revision id, where N = old revision and M = new revision')
2339 @cmdln.option('-p', '--plain', action='store_true',
2340 help='output the diff in plain (not unified) diff format')
2341 @cmdln.option('-c', '--change', metavar='rev',
2342 help='the change made by revision rev (like -r rev-1:rev). '
2343 'If rev is negative this is like -r rev:rev-1.')
2344 @cmdln.option('--missingok', action='store_true',
2345 help='do not fail if the source or target project/package does not exist on the server')
2346 def do_rdiff(self, subcmd, opts, *args):
2347 """${cmd_name}: Server-side "pretty" diff of two packages
2349 Compares two packages (three or four arguments) or shows the
2350 changes of a specified revision of a package (two arguments)
2352 If no revision is specified the latest revision is used.
2354 Note that this command doesn't return a normal diff (which could be
2355 applied as patch), but a "pretty" diff, which also compares the content
2360 osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC]
2361 osc ${cmd_name} PROJECT PACKAGE
2365 args = slash_split(args)
2376 new_project = args[0]
2377 new_package = args[1]
2379 old_project = opts.oldprj
2381 old_package = opts.oldpkg
2382 elif len(args) == 3 or len(args) == 4:
2383 if opts.oldprj or opts.oldpkg:
2384 raise oscerr.WrongArgs('--oldpkg and --oldprj are only valid with two arguments')
2385 old_project = args[0]
2386 new_package = old_package = args[1]
2387 new_project = args[2]
2389 new_package = args[3]
2391 raise oscerr.WrongArgs('Wrong number of arguments')
2396 rev = int(opts.change)
2406 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
2410 rev1, rev2 = parseRevisionOption(opts.revision)
2412 rdiff = server_diff(conf.config['apiurl'],
2413 old_project, old_package, rev1,
2414 new_project, new_package, rev2, not opts.plain, opts.missingok)
2419 def do_install(self, subcmd, opts, *args):
2420 """${cmd_name}: install a package after build via zypper in -r
2422 Not implemented yet. Use osc repourls,
2423 select the url you best like (standard),
2424 chop off after the last /, this should work with zypper.
2431 args = slash_split(args)
2432 args = expand_proj_pack(args)
2435 ## if there is only one argument, and it ends in .ymp
2436 ## then fetch it, Parse XML to get the first
2437 ## metapackage.group.repositories.repository.url
2438 ## and construct zypper cmd's for all
2439 ## metapackage.group.software.item.name
2441 ## if args[0] is already an url, the use it as is.
2443 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])
2444 print self.do_install.__doc__
2445 print "Example: \n" + cmd
2448 def do_repourls(self, subcmd, opts, *args):
2449 """${cmd_name}: Shows URLs of .repo files
2451 Shows URLs on which to access the project .repos files (yum-style
2452 metadata) on download.opensuse.org.
2455 osc repourls [PROJECT]
2460 apiurl = self.get_api_url()
2464 elif len(args) == 0:
2465 project = store_read_project('.')
2467 raise oscerr.WrongArgs('Wrong number of arguments')
2469 # XXX: API should somehow tell that
2470 url_tmpl = 'http://download.opensuse.org/repositories/%s/%s/%s.repo'
2471 repos = get_repositories_of_project(apiurl, project)
2473 print url_tmpl % (project.replace(':', ':/'), repo, project)
2476 @cmdln.option('-r', '--revision', metavar='rev',
2477 help='checkout the specified revision. '
2478 'NOTE: if you checkout the complete project '
2479 'this option is ignored!')
2480 @cmdln.option('-e', '--expand-link', action='store_true',
2481 help='if a package is a link, check out the expanded '
2482 'sources (no-op, since this became the default)')
2483 @cmdln.option('-u', '--unexpand-link', action='store_true',
2484 help='if a package is a link, check out the _link file ' \
2485 'instead of the expanded sources')
2486 @cmdln.option('-M', '--meta', action='store_true',
2487 help='checkout out meta data instead of sources' )
2488 @cmdln.option('-c', '--current-dir', action='store_true',
2489 help='place PACKAGE folder in the current directory' \
2490 'instead of a PROJECT/PACKAGE directory')
2491 @cmdln.option('-s', '--source-service-files', action='store_true',
2492 help='Use server side generated sources instead of local generation.' )
2493 @cmdln.option('-S', '--server-side-source-service-files', action='store_true',
2494 help='Use server side generated sources instead of local generation.' )
2495 @cmdln.option('-l', '--limit-size', metavar='limit_size',
2496 help='Skip all files with a given size')
2498 def do_checkout(self, subcmd, opts, *args):
2499 """${cmd_name}: Check out content from the repository
2501 Check out content from the repository server, creating a local working
2504 When checking out a single package, the option --revision can be used
2505 to specify a revision of the package to be checked out.
2507 When a package is a source link, then it will be checked out in
2508 expanded form. If --unexpand-link option is used, the checkout will
2509 instead produce the raw _link file plus patches.
2512 osc co PROJECT [PACKAGE] [FILE]
2513 osc co PROJECT # entire project
2514 osc co PROJECT PACKAGE # a package
2515 osc co PROJECT PACKAGE FILE # single file -> to current dir
2517 while inside a project directory:
2518 osc co PACKAGE # check out PACKAGE from project
2523 if opts.unexpand_link:
2528 args = slash_split(args)
2529 project = package = filename = None
2531 apiurl = self.get_api_url()
2534 project = project_dir = args[0]
2540 if args and len(args) == 1:
2541 localdir = os.getcwd()
2542 if is_project_dir(localdir):
2543 project = store_read_project(localdir)
2544 project_dir = localdir
2547 rev, dummy = parseRevisionOption(opts.revision)
2551 if rev and rev != "latest" and not checkRevision(project, package, rev):
2552 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2556 get_source_file(apiurl, project, package, filename, revision=rev, progress_obj=self.download_progress)
2559 if opts.current_dir:
2561 checkout_package(apiurl, project, package, rev, expand_link=expand_link, \
2562 prj_dir=project_dir, service_files = opts.source_service_files, server_service_files=opts.server_side_source_service_files, progress_obj=self.download_progress, limit_size=opts.limit_size, meta=opts.meta)
2563 print_request_list(apiurl, project, package)
2567 if sys.platform[:3] == 'win':
2568 prj_dir = prj_dir.replace(':', ';')
2569 if os.path.exists(prj_dir):
2570 sys.exit('osc: project \'%s\' already exists' % project)
2572 # check if the project does exist (show_project_meta will throw an exception)
2573 show_project_meta(apiurl, project)
2575 init_project_dir(apiurl, prj_dir, project)
2576 print statfrmt('A', prj_dir)
2579 for package in meta_get_packagelist(apiurl, project):
2581 checkout_package(apiurl, project, package, expand_link = expand_link, \
2582 prj_dir = prj_dir, service_files = opts.source_service_files, server_service_files = opts.server_side_source_service_files, progress_obj=self.download_progress, limit_size=opts.limit_size, meta=opts.meta)
2583 except oscerr.LinkExpandError, e:
2584 print >>sys.stderr, 'Link cannot be expanded:\n', e
2585 print >>sys.stderr, 'Use "osc repairlink" for fixing merge conflicts:\n'
2586 # check out in unexpanded form at least
2587 checkout_package(apiurl, project, package, expand_link = False, \
2588 prj_dir = prj_dir, service_files = opts.source_service_files, server_service_files = opts.server_side_source_service_files, progress_obj=self.download_progress, limit_size=opts.limit_size, meta=opts.meta)
2589 print_request_list(apiurl, project)
2592 raise oscerr.WrongArgs('Missing argument.\n\n' \
2593 + self.get_cmd_help('checkout'))
2596 @cmdln.option('-q', '--quiet', action='store_true',
2597 help='print as little as possible')
2598 @cmdln.option('-v', '--verbose', action='store_true',
2599 help='print extra information')
2601 def do_status(self, subcmd, opts, *args):
2602 """${cmd_name}: Show status of files in working copy
2604 Show the status of files in a local working copy, indicating whether
2605 files have been changed locally, deleted, added, ...
2607 The first column in the output specifies the status and is one of the
2608 following characters:
2609 ' ' no modifications
2614 '?' item is not under version control
2615 '!' item is missing (removed by non-osc command) or incomplete
2620 osc st file1 file2 ...
2623 osc status [OPTS] [PATH...]
2627 args = parseargs(args)
2629 # storage for single Package() objects
2631 # storage for a project dir ( { prj_instance : [ package objects ] } )
2634 # when 'status' is run inside a project dir, it should
2635 # stat all packages existing in the wc
2636 if is_project_dir(arg):
2637 prj = Project(arg, False)
2639 if conf.config['do_package_tracking']:
2641 for pac in prj.pacs_have:
2642 # we cannot create package objects if the dir does not exist
2643 if not pac in prj.pacs_broken:
2644 prjpacs[prj].append(os.path.join(arg, pac))
2646 pacpaths += [arg + '/' + n for n in prj.pacs_have]
2647 elif is_package_dir(arg):
2648 pacpaths.append(arg)
2649 elif os.path.isfile(arg):
2650 pacpaths.append(arg)
2652 msg = '\'%s\' is neither a project or a package directory' % arg
2653 raise oscerr.NoWorkingCopy, msg
2655 # process single packages
2656 lines = getStatus(findpacs(pacpaths), None, opts.verbose, opts.quiet)
2657 # process project dirs
2658 for prj, pacs in prjpacs.iteritems():
2659 lines += getStatus(findpacs(pacs), prj, opts.verbose, opts.quiet)
2661 print '\n'.join(lines)
2664 def do_add(self, subcmd, opts, *args):
2665 """${cmd_name}: Mark files to be added upon the next commit
2667 In case a URL is given the file will get downloaded and registered to be downloaded
2668 by the server as well via the download_url source service.
2670 This is recommended for release tar balls to track their source and to help
2671 others to review your changes esp. on version upgrades.
2674 osc add URL [URL...]
2675 osc add FILE [FILE...]
2679 raise oscerr.WrongArgs('Missing argument.\n\n' \
2680 + self.get_cmd_help('add'))
2682 # Do some magic here, when adding a url. We want that the server to download the tar ball and to verify it
2683 for arg in parseargs(args):
2684 if arg.startswith('http://') or arg.startswith('https://') or arg.startswith('ftp://'):
2685 addDownloadUrlService(arg)
2690 def do_mkpac(self, subcmd, opts, *args):
2691 """${cmd_name}: Create a new package under version control
2694 osc mkpac new_package
2697 if not conf.config['do_package_tracking']:
2698 print >>sys.stderr, "to use this feature you have to enable \'do_package_tracking\' " \
2699 "in the [general] section in the configuration file"
2703 raise oscerr.WrongArgs('Wrong number of arguments.')
2705 createPackageDir(args[0])
2707 @cmdln.option('-r', '--recursive', action='store_true',
2708 help='If CWD is a project dir then scan all package dirs as well')
2710 def do_addremove(self, subcmd, opts, *args):
2711 """${cmd_name}: Adds new files, removes disappeared files
2713 Adds all files new in the local copy, and removes all disappeared files.
2715 ARG, if specified, is a package working copy.
2721 args = parseargs(args)
2723 for arg in arg_list:
2724 if is_project_dir(arg) and conf.config['do_package_tracking']:
2725 prj = Project(arg, False)
2726 for pac in prj.pacs_unvers:
2727 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2728 if os.path.isdir(pac_dir):
2729 addFiles([pac_dir], prj)
2730 for pac in prj.pacs_broken:
2731 if prj.get_state(pac) != 'D':
2732 prj.set_state(pac, 'D')
2733 print statfrmt('D', getTransActPath(os.path.join(prj.dir, pac)))
2735 for pac in prj.pacs_have:
2736 state = prj.get_state(pac)
2737 if state != None and state != 'D':
2738 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2739 args.append(pac_dir)
2741 prj.write_packages()
2742 elif is_project_dir(arg):
2743 print >>sys.stderr, 'osc: addremove is not supported in a project dir unless ' \
2744 '\'do_package_tracking\' is enabled in the configuration file'
2747 pacs = findpacs(args)
2749 p.todo = p.filenamelist + p.filenamelist_unvers
2751 for filename in p.todo:
2752 if os.path.isdir(filename):
2754 # ignore foo.rXX, foo.mine for files which are in 'C' state
2755 if os.path.splitext(filename)[0] in p.in_conflict:
2757 state = p.status(filename)
2760 # TODO: should ignore typical backup files suffix ~ or .orig
2762 print statfrmt('A', getTransActPath(os.path.join(p.dir, filename)))
2764 p.put_on_deletelist(filename)
2765 p.write_deletelist()
2766 os.unlink(os.path.join(p.storedir, filename))
2767 print statfrmt('D', getTransActPath(os.path.join(p.dir, filename)))
2772 @cmdln.alias('checkin')
2773 @cmdln.option('-m', '--message', metavar='TEXT',
2774 help='specify log message TEXT')
2775 @cmdln.option('-F', '--file', metavar='FILE',
2776 help='read log message from FILE')
2777 @cmdln.option('-f', '--force', default=False, action="store_true",
2778 help='force commit - do not tests a file list')
2779 @cmdln.option('--skip-validation', default=False, action="store_true",
2780 help='Skip the source validation')
2781 @cmdln.option('--verbose-validation', default=False, action="store_true",
2782 help='Run the source validation with verbose informations')
2783 def do_commit(self, subcmd, opts, *args):
2784 """${cmd_name}: Upload content to the repository server
2786 Upload content which is changed in your working copy, to the repository
2789 Optionally checks the state of a working copy, if found a file with
2790 unknown state, it requests an user input:
2791 * skip - don't change anything, just move to another file
2792 * remove - remove a file from dir
2793 * edit file list - edit filelist using EDITOR
2794 * commit - don't check anything and commit package
2795 * abort - abort commit - this is default value
2796 This can be supressed by check_filelist config item, or -f/--force
2797 command line option.
2800 osc ci # current dir
2802 osc ci file1 file2 ...
2808 args = parseargs(args)
2810 validators = conf.config['source_validator_directory']
2811 verbose_validation = None
2812 if opts.skip_validation:
2814 elif not os.path.exists(validators):
2815 print "WARNING: validator directory", validators, "configured, but not existing. Skipping ..."
2817 if opts.verbose_validation:
2818 verbose_validation = 1
2825 msg = open(opts.file).read()
2827 sys.exit('could not open file \'%s\'.' % opts.file)
2830 for arg in arg_list:
2831 if conf.config['do_package_tracking'] and is_project_dir(arg):
2833 msg = edit_message()
2835 Project(arg).commit(msg=msg, validators=validators, verbose_validation=verbose_validation)
2836 except oscerr.RuntimeError, e:
2837 print >>sys.stderr, "ERROR: source_validator failed", e
2841 pacs = findpacs(args)
2843 if conf.config['check_filelist'] and not opts.force:
2844 check_filelist_before_commit(pacs)
2847 template = store_read_file(os.path.abspath('.'), '_commit_msg')
2848 # open editor for commit message
2849 # but first, produce status and diff to append to the template
2853 changed = getStatus([pac], quiet=True)
2856 diffs += ['\nDiff for working copy: %s' % pac.dir]
2857 diffs += make_diff(pac, 0)
2858 lines.extend(get_commit_message_template(pac))
2859 if template == None:
2860 template='\n'.join(lines)
2861 # if footer is empty, there is nothing to commit, and no edit needed.
2863 msg = edit_message(footer='\n'.join(footer), template=template)
2866 store_write_string(os.path.abspath('.'), '_commit_msg', msg)
2868 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2870 if conf.config['do_package_tracking'] and len(pacs) > 0:
2874 # it is possible to commit packages from different projects at the same
2875 # time: iterate over all pacs and put each pac to the right project in the dict
2877 path = os.path.normpath(os.path.join(pac.dir, os.pardir))
2878 if is_project_dir(path):
2879 pac_path = os.path.basename(os.path.normpath(pac.absdir))
2880 prj_paths.setdefault(path, []).append(pac_path)
2881 files[pac_path] = pac.todo
2883 single_paths.append(pac.dir)
2884 for prj, packages in prj_paths.iteritems():
2886 Project(prj).commit(tuple(packages), msg=msg, files=files, validators=validators, verbose_validation=verbose_validation)
2887 except oscerr.RuntimeError, e:
2888 print >>sys.stderr, "ERROR: source_validator failed", e
2890 for pac in single_paths:
2892 Package(pac).commit(msg, validators=validators, verbose_validation=verbose_validation)
2893 except oscerr.RuntimeError, e:
2894 print >>sys.stderr, "ERROR: source_validator failed", e
2898 p.commit(msg, validators=validators, verbose_validation=verbose_validation)
2900 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2902 @cmdln.option('-r', '--revision', metavar='REV',
2903 help='update to specified revision (this option will be ignored '
2904 'if you are going to update the complete project or more than '
2906 @cmdln.option('-u', '--unexpand-link', action='store_true',
2907 help='if a package is an expanded link, update to the raw _link file')
2908 @cmdln.option('-e', '--expand-link', action='store_true',
2909 help='if a package is a link, update to the expanded sources')
2910 @cmdln.option('-s', '--source-service-files', action='store_true',
2911 help='Use server side generated sources instead of local generation.' )
2912 @cmdln.option('-S', '--server-side-source-service-files', action='store_true',
2913 help='Use server side generated sources instead of local generation.' )
2914 @cmdln.option('-l', '--limit-size', metavar='limit_size',
2915 help='Skip all files with a given size')
2917 def do_update(self, subcmd, opts, *args):
2918 """${cmd_name}: Update a working copy
2923 If the current working directory is a package, update it.
2924 If the directory is a project directory, update all contained
2925 packages, AND check out newly added packages.
2927 To update only checked out packages, without checking out new
2928 ones, you might want to use "osc up *" from within the project
2932 Update the packages specified by the path argument(s)
2934 When --expand-link is used with source link packages, the expanded
2935 sources will be checked out. Without this option, the _link file and
2936 patches will be checked out. The option --unexpand-link can be used to
2937 switch back to the "raw" source with a _link file plus patch(es).
2943 if (opts.expand_link and opts.unexpand_link) \
2944 or (opts.expand_link and opts.revision) \
2945 or (opts.unexpand_link and opts.revision):
2946 raise oscerr.WrongOptions('Sorry, the options --expand-link, --unexpand-link and '
2947 '--revision are mutually exclusive.')
2949 args = parseargs(args)
2952 for arg in arg_list:
2953 if is_project_dir(arg):
2954 prj = Project(arg, progress_obj=self.download_progress)
2956 if conf.config['do_package_tracking']:
2957 prj.update(expand_link=opts.expand_link,
2958 unexpand_link=opts.unexpand_link)
2961 # if not tracking package, and 'update' is run inside a project dir,
2962 # it should do the following:
2963 # (a) update all packages
2964 args += prj.pacs_have
2965 # (b) fetch new packages
2966 prj.checkout_missing_pacs(expand_link = not opts.unexpand_link)
2968 print_request_list(prj.apiurl, prj.name)
2971 pacs = findpacs(args, progress_obj=self.download_progress)
2973 if opts.revision and len(args) == 1:
2974 rev, dummy = parseRevisionOption(opts.revision)
2975 if not checkRevision(pacs[0].prjname, pacs[0].name, rev, pacs[0].apiurl):
2976 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2983 print 'Updating %s' % p.name
2985 # FIXME: ugly workaround for #399247
2986 if opts.expand_link or opts.unexpand_link:
2987 if [ i for i in p.filenamelist+p.filenamelist_unvers if p.status(i) != ' ' and p.status(i) != '?']:
2988 print >>sys.stderr, 'osc: cannot expand/unexpand because your working ' \
2989 'copy has local modifications.\nPlease revert/commit them ' \
2994 if opts.expand_link and p.islink() and not p.isexpanded():
2995 if p.haslinkerror():
2997 rev = p.show_upstream_xsrcmd5()
2999 rev = p.show_upstream_xsrcmd5(linkrev="base")
3002 p.update(rev, opts.server_side_source_service_files, opts.limit_size)
3003 rev = p.linkinfo.xsrcmd5
3004 print 'Expanding to rev', rev
3005 elif opts.unexpand_link and p.islink() and p.isexpanded():
3006 print 'Unexpanding to rev', p.linkinfo.lsrcmd5
3007 p.update(rev, opts.server_side_source_service_files, opts.limit_size)
3008 rev = p.linkinfo.lsrcmd5
3009 elif p.islink() and p.isexpanded():
3010 rev = p.latest_rev()
3012 p.update(rev, opts.server_side_source_service_files, opts.limit_size)
3013 if opts.source_service_files:
3014 print 'Running local source services'
3015 p.run_source_services()
3016 if opts.unexpand_link:
3019 print_request_list(p.apiurl, p.prjname, p.name)
3022 @cmdln.option('-f', '--force', action='store_true',
3023 help='forces removal of entire package and its files')
3026 @cmdln.alias('remove')
3027 def do_delete(self, subcmd, opts, *args):
3028 """${cmd_name}: Mark files or package directories to be deleted upon the next 'checkin'
3031 cd .../PROJECT/PACKAGE
3032 osc delete FILE [...]
3034 osc delete PACKAGE [...]
3036 This command works on check out copies. Use "rdelete" for working on server
3037 side only. This is needed for removing the entire project.
3039 As a safety measure, projects must be empty (i.e., you need to delete all
3042 If you are sure that you want to remove a package and all
3043 its files use \'--force\' switch. Sometimes this also works without --force.
3049 raise oscerr.WrongArgs('Missing argument.\n\n' \
3050 + self.get_cmd_help('delete'))
3052 args = parseargs(args)
3053 # check if args contains a package which was removed by
3054 # a non-osc command and mark it with the 'D'-state
3057 if not os.path.exists(i):
3058 prj_dir, pac_dir = getPrjPacPaths(i)
3059 if is_project_dir(prj_dir):
3060 prj = Project(prj_dir, False)