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))
503 min_args, max_args = 0, 2
504 elif cmd in ['pattern']:
505 min_args, max_args = 1, 2
506 elif cmd in ['attribute']:
507 min_args, max_args = 1, 3
508 elif cmd in ['prj', 'prjconf']:
509 min_args, max_args = 0, 1
511 min_args, max_args = 1, 1
513 if len(args) < min_args:
514 raise oscerr.WrongArgs('Too few arguments.')
515 if len(args) > max_args:
516 raise oscerr.WrongArgs('Too many arguments.')
520 if cmd in ['pkg', 'prj', 'prjconf' ]:
522 project = store_read_project(os.curdir)
528 package = store_read_package(os.curdir)
532 elif cmd == 'attribute':
538 if opts.attribute_project:
539 raise oscerr.WrongOptions('--attribute-project works only when also a package is given')
544 attributepath.append('source')
545 attributepath.append(project)
547 attributepath.append(package)
549 attributepath.append(subpackage)
550 attributepath.append('_attribute')
553 elif cmd == 'pattern':
559 # enforce pattern argument if needed
560 if opts.edit or opts.file:
561 raise oscerr.WrongArgs('A pattern file argument is required.')
564 if not opts.edit and not opts.file and not opts.delete and not opts.create and not opts.set:
566 sys.stdout.write(''.join(show_project_meta(conf.config['apiurl'], project)))
568 sys.stdout.write(''.join(show_package_meta(conf.config['apiurl'], project, package)))
569 elif cmd == 'attribute':
570 sys.stdout.write(''.join(show_attribute_meta(conf.config['apiurl'], project, package, subpackage, opts.attribute, opts.attribute_defaults, opts.attribute_project)))
571 elif cmd == 'prjconf':
572 sys.stdout.write(''.join(show_project_conf(conf.config['apiurl'], project)))
574 r = get_user_meta(conf.config['apiurl'], user)
576 sys.stdout.write(''.join(r))
577 elif cmd == 'pattern':
579 r = show_pattern_meta(conf.config['apiurl'], project, pattern)
581 sys.stdout.write(''.join(r))
583 r = show_pattern_metalist(conf.config['apiurl'], project)
585 sys.stdout.write('\n'.join(r) + '\n')
588 if opts.edit and not opts.file:
590 edit_meta(metatype='prj',
592 path_args=quote_plus(project),
595 'user': conf.config['user']}))
597 edit_meta(metatype='pkg',
599 path_args=(quote_plus(project), quote_plus(package)),
602 'user': conf.config['user']}))
603 elif cmd == 'prjconf':
604 edit_meta(metatype='prjconf',
606 path_args=quote_plus(project),
609 edit_meta(metatype='user',
611 path_args=(quote_plus(user)),
612 template_args=({'user': user}))
613 elif cmd == 'pattern':
614 edit_meta(metatype='pattern',
616 path_args=(project, pattern),
619 # create attribute entry
620 if (opts.create or opts.set) and cmd == 'attribute':
621 if not opts.attribute:
622 raise oscerr.WrongOptions('no attribute given to create')
625 opts.set = opts.set.replace('&', '&').replace('<', '<').replace('>', '>')
626 for i in opts.set.split(','):
627 values += '<value>%s</value>' % i
628 aname = opts.attribute.split(":")
629 d = '<attributes><attribute namespace=\'%s\' name=\'%s\' >%s</attribute></attributes>' % (aname[0], aname[1], values)
630 url = makeurl(conf.config['apiurl'], attributepath)
631 for data in streamfile(url, http_POST, data=d):
632 sys.stdout.write(data)
641 f = open(opts.file).read()
643 sys.exit('could not open file \'%s\'.' % opts.file)
646 edit_meta(metatype='prj',
649 path_args=quote_plus(project))
651 edit_meta(metatype='pkg',
654 path_args=(quote_plus(project), quote_plus(package)))
655 elif cmd == 'prjconf':
656 edit_meta(metatype='prjconf',
659 path_args=quote_plus(project))
661 edit_meta(metatype='user',
664 path_args=(quote_plus(user)))
665 elif cmd == 'pattern':
666 edit_meta(metatype='pattern',
669 path_args=(project, pattern))
674 path = metatypes[cmd]['path']
676 path = path % (project, pattern)
677 u = makeurl(conf.config['apiurl'], [path])
679 elif cmd == 'attribute':
680 if not opts.attribute:
681 raise oscerr.WrongOptions('no attribute given to create')
682 attributepath.append(opts.attribute)
683 u = makeurl(conf.config['apiurl'], attributepath)
684 for data in streamfile(u, http_DELETE):
685 sys.stdout.write(data)
687 raise oscerr.WrongOptions('The --delete switch is only for pattern metadata or attributes.')
690 @cmdln.option('-m', '--message', metavar='TEXT',
691 help='specify message TEXT')
692 @cmdln.option('-r', '--revision', metavar='REV',
693 help='for "create", specify a certain source revision ID (the md5 sum)')
694 @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
695 help='Superseding another request by this one')
696 @cmdln.option('--nodevelproject', action='store_true',
697 help='do not follow a defined devel project ' \
698 '(primary project where a package is developed)')
699 @cmdln.option('--cleanup', action='store_true',
700 help='remove package if submission gets accepted (default for home:<id>:branch projects)')
701 @cmdln.option('--no-cleanup', action='store_true',
702 help='never remove source package on accept, but update its content')
703 @cmdln.option('--no-update', action='store_true',
704 help='never touch source package on accept (will break source links)')
705 @cmdln.option('-d', '--diff', action='store_true',
706 help='show diff only instead of creating the actual request')
707 @cmdln.option('--yes', action='store_true',
708 help='proceed without asking.')
710 @cmdln.alias("submitreq")
711 @cmdln.alias("submitpac")
712 def do_submitrequest(self, subcmd, opts, *args):
713 """${cmd_name}: Create request to submit source into another Project
715 [See http://en.opensuse.org/Build_Service/Collaboration for information
718 See the "request" command for showing and modifing existing requests.
721 osc submitreq [OPTIONS]
722 osc submitreq [OPTIONS] DESTPRJ [DESTPKG]
723 osc submitreq [OPTIONS] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
727 src_update = conf.config['submitrequest_on_accept_action'] or None
728 # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
730 src_update = "cleanup"
731 elif opts.no_cleanup:
732 src_update = "update"
734 src_update = "noupdate"
736 args = slash_split(args)
738 # remove this block later again
739 oldcmds = ['create', 'list', 'log', 'show', 'decline', 'accept', 'delete', 'revoke']
740 if args and args[0] in oldcmds:
741 print "************************************************************************"
742 print "* WARNING: It looks that you are using this command with a *"
743 print "* deprecated syntax. *"
744 print "* Please run \"osc sr --help\" and \"osc rq --help\" *"
745 print "* to see the new syntax. *"
746 print "************************************************************************"
747 if args[0] == 'create':
753 raise oscerr.WrongArgs('Too many arguments.')
755 if len(args) > 0 and len(args) <= 2 and is_project_dir(os.getcwd()):
756 sys.exit('osc submitrequest from project directory is only working without target specs and for source linked files\n')
758 apiurl = self.get_api_url()
760 if len(args) == 0 and is_project_dir(os.getcwd()):
762 # submit requests for multiple packages are currently handled via multiple requests
763 # They could be also one request with multiple actions, but that avoids to accepts parts of it.
764 project = store_read_project(os.curdir)
770 # loop via all packages for checking their state
771 for p in meta_get_packagelist(apiurl, project):
772 if p.startswith("_patchinfo:"):
775 # get _link info from server, that knows about the local state ...
776 u = makeurl(apiurl, ['source', project, p])
778 root = ET.parse(f).getroot()
779 linkinfo = root.find('linkinfo')
781 print "Package ", p, " is not a source link."
782 sys.exit("This is currently not supported.")
783 if linkinfo.get('error'):
784 print "Package ", p, " is a broken source link."
785 sys.exit("Please fix this first")
786 t = linkinfo.get('project')
788 if len(root.findall('entry')) > 1: # This is not really correct, but should work mostly
789 # Real fix is to ask the api if sources are modificated
790 # but there is no such call yet.
791 targetprojects.append(t)
793 print "Submitting package ", p
795 print " Skipping package ", p
797 print "Skipping package ", p, " since it is a source link pointing inside the project."
801 print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
802 print "\nEverything fine? Can we create the requests ? [y/n]"
803 if sys.stdin.read(1) != "y":
804 print >>sys.stderr, 'Aborted...'
805 raise oscerr.UserAbort()
807 # loop via all packages to do the action
809 result = create_submit_request(apiurl, project, p)
812 sys.exit("submit request creation failed")
813 sr_ids.append(result)
815 # create submit requests for all found patchinfos
819 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
822 for t in targetprojects:
823 s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \
824 (project, p, t, p, options_block)
828 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
829 (actionxml, cgi.escape(opts.message or ""))
830 u = makeurl(apiurl, ['request'], query='cmd=create')
831 f = http_POST(u, data=xml)
833 root = ET.parse(f).getroot()
834 sr_ids.append(root.get('id'))
836 print "Requests created: ",
839 sys.exit('Successfull finished')
842 # try using the working copy at hand
843 p = findpacs(os.curdir)[0]
844 src_project = p.prjname
847 if len(args) == 0 and p.islink():
848 dst_project = p.linkinfo.project
849 dst_package = p.linkinfo.package
851 dst_project = args[0]
853 dst_package = args[1]
855 dst_package = src_package
857 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
858 'Please provide it the target via commandline arguments.' % p.name)
860 modified = [i for i in p.filenamelist if p.status(i) != ' ' and p.status(i) != '?']
861 if len(modified) > 0:
862 print 'Your working copy has local modifications.'
863 repl = raw_input('Proceed without committing the local changes? (y|N) ')
865 raise oscerr.UserAbort()
867 # get the arguments from the commandline
868 src_project, src_package, dst_project = args[0:3]
870 dst_package = args[3]
872 dst_package = src_package
874 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
875 + self.get_cmd_help('request'))
877 if not opts.nodevelproject:
880 devloc = show_develproject(apiurl, dst_project, dst_package)
881 except urllib2.HTTPError:
882 print >>sys.stderr, """\
883 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
884 % (dst_project, dst_package)
888 dst_project != devloc and \
889 src_project != devloc:
891 A different project, %s, is defined as the place where development
892 of the package %s primarily takes place.
893 Please submit there instead, or use --nodevelproject to force direct submission.""" \
894 % (devloc, dst_package)
899 if opts.diff or not opts.message:
901 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
902 rdiff += server_diff(apiurl,
903 dst_project, dst_package, opts.revision,
904 src_project, src_package, None, True)
910 reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit')
911 user = conf.get_apiurl_usr(apiurl)
912 myreqs = [ i for i in reqs if i.state.who == user ]
915 print 'You already created the following submit request: %s.' % \
916 ', '.join([str(i.reqid) for i in myreqs ])
917 repl = raw_input('Supersede the old requests? (y/n/c) ')
918 if repl.lower() == 'c':
919 print >>sys.stderr, 'Aborting'
920 raise oscerr.UserAbort()
925 changes_re = re.compile(r'^--- .*\.changes ')
926 for line in rdiff.split('\n'):
927 if line.startswith('--- '):
928 if changes_re.match(line):
933 difflines.append(line)
934 opts.message = edit_message(footer=rdiff, template='\n'.join(parse_diff_for_commit_message('\n'.join(difflines))))
936 result = create_submit_request(apiurl,
937 src_project, src_package,
938 dst_project, dst_package,
939 opts.message, orev=opts.revision, src_update=src_update)
940 if repl.lower() == 'y':
942 change_request_state(apiurl, str(req.reqid), 'superseded',
943 'superseded by %s' % result, result)
946 r = change_request_state(conf.config['apiurl'],
947 opts.supersede, 'superseded', opts.message or '', result)
949 print 'created request id', result
951 def _actionparser(option, opt_str, value, parser):
953 if not hasattr(parser.values, 'actiondata'):
954 setattr(parser.values, 'actiondata', [])
955 if parser.values.actions == None:
956 parser.values.actions = []
961 if ((arg[:2] == "--" and len(arg) > 2) or
962 (arg[:1] == "-" and len(arg) > 1 and arg[1] != "-")):
968 parser.values.actions.append(value[0])
970 parser.values.actiondata.append(value)
972 def _submit_request(self, args, opts, options_block):
974 apiurl = self.get_api_url()
975 if len(args) == 0 and is_project_dir(os.getcwd()):
977 # submit requests for multiple packages are currently handled via multiple requests
978 # They could be also one request with multiple actions, but that avoids to accepts parts of it.
979 project = store_read_project(os.curdir)
985 # loop via all packages for checking their state
986 for p in meta_get_packagelist(apiurl, project):
987 if p.startswith("_patchinfo:"):
990 # get _link info from server, that knows about the local state ...
991 u = makeurl(apiurl, ['source', project, p])
993 root = ET.parse(f).getroot()
994 linkinfo = root.find('linkinfo')
996 print "Package ", p, " is not a source link."
997 sys.exit("This is currently not supported.")
998 if linkinfo.get('error'):
999 print "Package ", p, " is a broken source link."
1000 sys.exit("Please fix this first")
1001 t = linkinfo.get('project')
1005 rdiff = server_diff(apiurl, t, p, opts.revision, project, p, None, True)
1010 targetprojects.append(t)
1012 rdiffmsg.append("old: %s/%s\nnew: %s/%s\n%s" %(t, p, project, p,rdiff))
1014 print "Skipping package ", p, " since it has no difference with the target package."
1016 print "Skipping package ", p, " since it is a source link pointing inside the project."
1018 print ''.join(rdiffmsg)
1023 print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
1024 print "\nEverything fine? Can we create the requests ? [y/n]"
1025 if sys.stdin.read(1) != "y":
1026 sys.exit("Aborted...")
1028 # loop via all packages to do the action
1030 s = """<action type="submit"> <source project="%s" package="%s" rev="%s"/> <target project="%s" package="%s"/> %s </action>""" % \
1031 (project, p, opts.revision or show_upstream_rev(apiurl, project, p), t, p, options_block)
1034 # create submit requests for all found patchinfos
1036 for t in targetprojects:
1037 s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \
1038 (project, p, t, p, options_block)
1043 elif len(args) <= 2:
1044 # try using the working copy at hand
1045 p = findpacs(os.curdir)[0]
1046 src_project = p.prjname
1047 src_package = p.name
1048 if len(args) == 0 and p.islink():
1049 dst_project = p.linkinfo.project
1050 dst_package = p.linkinfo.package
1052 dst_project = args[0]
1054 dst_package = args[1]
1056 dst_package = src_package
1058 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
1059 'Please provide it the target via commandline arguments.' % p.name)
1061 modified = [i for i in p.filenamelist if p.status(i) != ' ' and p.status(i) != '?']
1062 if len(modified) > 0:
1063 print 'Your working copy has local modifications.'
1064 repl = raw_input('Proceed without committing the local changes? (y|N) ')
1067 elif len(args) >= 3:
1068 # get the arguments from the commandline
1069 src_project, src_package, dst_project = args[0:3]
1071 dst_package = args[3]
1073 dst_package = src_package
1075 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1076 + self.get_cmd_help('request'))
1078 if not opts.nodevelproject:
1081 devloc = show_develproject(apiurl, dst_project, dst_package)
1082 except urllib2.HTTPError:
1083 print >>sys.stderr, """\
1084 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
1085 % (dst_project, dst_package)
1089 dst_project != devloc and \
1090 src_project != devloc:
1092 A different project, %s, is defined as the place where development
1093 of the package %s primarily takes place.
1094 Please submit there instead, or use --nodevelproject to force direct submission.""" \
1095 % (devloc, dst_package)
1102 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
1103 rdiff += server_diff(apiurl,
1104 dst_project, dst_package, opts.revision,
1105 src_project, src_package, None, True)
1111 reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit')
1112 user = conf.get_apiurl_usr(apiurl)
1113 myreqs = [ i for i in reqs if i.state.who == user ]
1116 print 'You already created the following submit request: %s.' % \
1117 ', '.join([str(i.reqid) for i in myreqs ])
1118 repl = raw_input('Supersede the old requests? (y/n/c) ')
1119 if repl.lower() == 'c':
1120 print >>sys.stderr, 'Aborting'
1123 actionxml = """<action type="submit"> <source project="%s" package="%s" rev="%s"/> <target project="%s" package="%s"/> %s </action>""" % \
1124 (src_project, src_package, opts.revision or show_upstream_rev(apiurl, src_project, src_package), dst_project, dst_package, options_block)
1125 if repl.lower() == 'y':
1127 change_request_state(apiurl, str(req.reqid), 'superseded',
1128 'superseded by %s' % result, result)
1131 r = change_request_state(apiurl,
1132 opts.supersede, 'superseded', '', result)
1134 #print 'created request id', result
1137 def _delete_request(self, args, opts):
1139 raise oscerr.WrongArgs('Please specify at least a project.')
1141 raise oscerr.WrongArgs('Too many arguments.')
1145 package = """package="%s" """ % (args[1])
1146 actionxml = """<action type="delete"> <target project="%s" %s/> </action> """ % (args[0], package)
1149 def _changedevel_request(self, args, opts):
1151 raise oscerr.WrongArgs('Too many arguments.')
1153 if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
1155 devel_project = store_read_project(wd)
1156 devel_package = package = store_read_package(wd)
1157 project = conf.config['getpac_default_project']
1160 raise oscerr.WrongArgs('Too few arguments.')
1162 devel_project = args[2]
1165 devel_package = package
1167 devel_package = args[3]
1169 actionxml = """ <action type="change_devel"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> </action> """ % \
1170 (devel_project, devel_package, project, package)
1174 def _add_role(self, args, opts):
1176 raise oscerr.WrongArgs('Too many arguments.')
1178 raise oscerr.WrongArgs('Too few arguments.')
1180 apiurl = self.get_api_url()
1188 if get_user_meta(apiurl, user) == None:
1189 raise oscerr.WrongArgs('osc: an error occured.')
1191 actionxml = """ <action type="add_role"> <target project="%s" package="%s" /> <person name="%s" role="%s" /> </action> """ % \
1192 (project, package, user, role)
1196 def _set_bugowner(self, args, opts):
1198 raise oscerr.WrongArgs('Too many arguments.')
1200 raise oscerr.WrongArgs('Too few arguments.')
1202 apiurl = self.get_api_url()
1209 if get_user_meta(apiurl, user) == None:
1210 raise oscerr.WrongArgs('osc: an error occured.')
1212 actionxml = """ <action type="set_bugowner"> <target project="%s" package="%s" /> <person name="%s" /> </action> """ % \
1213 (project, package, user)
1217 @cmdln.option('-a', '--action', action='callback', callback = _actionparser,dest = 'actions',
1218 help='specify action type of a request, can be : submit/delete/change_devel/add_role/set_bugowner')
1219 @cmdln.option('-m', '--message', metavar='TEXT',
1220 help='specify message TEXT')
1221 @cmdln.option('-r', '--revision', metavar='REV',
1222 help='for "create", specify a certain source revision ID (the md5 sum)')
1223 @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
1224 help='Superseding another request by this one')
1225 @cmdln.option('--nodevelproject', action='store_true',
1226 help='do not follow a defined devel project ' \
1227 '(primary project where a package is developed)')
1228 @cmdln.option('--cleanup', action='store_true',
1229 help='remove package if submission gets accepted (default for home:<id>:branch projects)')
1230 @cmdln.option('--no-cleanup', action='store_true',
1231 help='never remove source package on accept, but update its content')
1232 @cmdln.option('--no-update', action='store_true',
1233 help='never touch source package on accept (will break source links)')
1234 @cmdln.option('-d', '--diff', action='store_true',
1235 help='show diff only instead of creating the actual request')
1236 @cmdln.option('--yes', action='store_true',
1237 help='proceed without asking.')
1238 @cmdln.alias("creq")
1239 def do_createrequest(self, subcmd, opts, *args):
1240 """${cmd_name}: create multiple requests with a single command
1243 osc creq [OPTIONS] [
1244 -a submit SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
1245 -a delete PROJECT [PACKAGE]
1246 -a change_devel PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
1247 -a add_role USER ROLE PROJECT [PACKAGE]
1248 -a set_bugowner USER PROJECT [PACKAGE]
1251 Option -m works for all types of request, the rest work only for submit.
1253 osc creq -a submit -a delete home:someone:branches:openSUSE:Tools -a change_devel openSUSE:Tools osc home:someone:branches:openSUSE:Tools -m ok
1255 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.
1258 src_update = conf.config['submitrequest_on_accept_action'] or None
1259 # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
1261 src_update = "cleanup"
1262 elif opts.no_cleanup:
1263 src_update = "update"
1264 elif opts.no_update:
1265 src_update = "noupdate"
1269 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
1271 args = slash_split(args)
1273 apiurl = self.get_api_url()
1277 for ai in opts.actions:
1279 args = opts.actiondata[i]
1281 actionsxml += self._submit_request(args,opts, options_block)
1282 elif ai == 'delete':
1283 args = opts.actiondata[i]
1284 actionsxml += self._delete_request(args,opts)
1286 elif ai == 'change_devel':
1287 args = opts.actiondata[i]
1288 actionsxml += self._changedevel_request(args,opts)
1290 elif ai == 'add_role':
1291 args = opts.actiondata[i]
1292 actionsxml += self._add_role(args,opts)
1294 elif ai == 'set_bugowner':
1295 args = opts.actiondata[i]
1296 actionsxml += self._set_bugowner(args,opts)
1299 raise oscerr.WrongArgs('Unsupported action %s' % ai)
1300 if actionsxml == "":
1301 sys.exit('No actions need to be taken.')
1303 if not opts.message:
1304 opts.message = edit_message()
1307 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
1308 (actionsxml, cgi.escape(opts.message or ""))
1309 u = makeurl(apiurl, ['request'], query='cmd=create')
1310 f = http_POST(u, data=xml)
1312 root = ET.parse(f).getroot()
1313 return root.get('id')
1316 @cmdln.option('-m', '--message', metavar='TEXT',
1317 help='specify message TEXT')
1319 @cmdln.alias("deletereq")
1320 def do_deleterequest(self, subcmd, opts, *args):
1321 """${cmd_name}: Create request to delete a package or project
1325 osc deletereq [-m TEXT] PROJECT [PACKAGE]
1329 args = slash_split(args)
1332 raise oscerr.WrongArgs('Please specify at least a project.')
1334 raise oscerr.WrongArgs('Too many arguments.')
1336 apiurl = conf.config['apiurl']
1343 if not opts.message:
1344 opts.message = edit_message()
1346 result = create_delete_request(apiurl, project, package, opts.message)
1350 @cmdln.option('-m', '--message', metavar='TEXT',
1351 help='specify message TEXT')
1353 @cmdln.alias("changedevelreq")
1354 def do_changedevelrequest(self, subcmd, opts, *args):
1355 """${cmd_name}: Create request to change the devel package definition.
1357 [See http://en.opensuse.org/Build_Service/Collaboration for information
1360 See the "request" command for showing and modifing existing requests.
1362 osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
1366 raise oscerr.WrongArgs('Too many arguments.')
1368 apiurl = self.get_api_url()
1370 if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
1372 devel_project = store_read_project(wd)
1373 devel_package = package = store_read_package(wd)
1374 project = conf.config['getpac_default_project']
1377 raise oscerr.WrongArgs('Too few arguments.')
1379 devel_project = args[2]
1382 devel_package = package
1384 devel_package = args[3]
1386 if not opts.message:
1388 footer=textwrap.TextWrapper(width = 66).fill(
1389 'please explain why you like to change the devel project of %s/%s to %s/%s'
1390 % (project,package,devel_project,devel_package))
1391 opts.message = edit_message(footer)
1393 result = create_change_devel_request(apiurl,
1394 devel_project, devel_package,
1400 @cmdln.option('-d', '--diff', action='store_true',
1401 help='generate a diff')
1402 @cmdln.option('-u', '--unified', action='store_true',
1403 help='output the diff in the unified diff format')
1404 @cmdln.option('-m', '--message', metavar='TEXT',
1405 help='specify message TEXT')
1406 @cmdln.option('-t', '--type', metavar='TYPE',
1407 help='limit to requests which contain a given action type (submit/delete/change_devel)')
1408 @cmdln.option('-a', '--all', action='store_true',
1409 help='all states. Same as\'-s all\'')
1410 @cmdln.option('-s', '--state', default='', # default is 'all' if no args given, 'new' otherwise
1411 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]')
1412 @cmdln.option('-D', '--days', metavar='DAYS',
1413 help='only list requests in state "new" or changed in the last DAYS. [default=%(request_list_days)s]')
1414 @cmdln.option('-U', '--user', metavar='USER',
1415 help='same as -M, but for the specified USER')
1416 @cmdln.option('-b', '--brief', action='store_true', default=False,
1417 help='print output in list view as list subcommand')
1418 @cmdln.option('-M', '--mine', action='store_true',
1419 help='only show requests created by yourself')
1420 @cmdln.option('-B', '--bugowner', action='store_true',
1421 help='also show requests about packages where I am bugowner')
1422 @cmdln.option('-i', '--interactive', action='store_true',
1423 help='interactive review of request')
1424 @cmdln.option('--non-interactive', action='store_true',
1425 help='non-interactive review of request')
1426 @cmdln.option('--exclude-target-project', action='append',
1427 help='exclude target project from request list')
1428 @cmdln.option('--involved-projects', action='store_true',
1429 help='show all requests for project/packages where USER is involved')
1431 @cmdln.alias("review")
1432 def do_request(self, subcmd, opts, *args):
1433 """${cmd_name}: Show and modify requests
1435 [See http://en.opensuse.org/Build_Service/Collaboration for information
1438 This command shows and modifies existing requests. To create new requests
1439 you need to call one of the following:
1442 osc changedevelrequest
1443 To send low level requests to the buildservice API, use:
1446 This command has the following sub commands:
1448 "list" lists open requests attached to a project or package or person.
1449 Uses the project/package of the current directory if none of
1450 -M, -U USER, project/package are given.
1452 "log" will show the history of the given ID
1454 "show" will show the request itself, and generate a diff for review, if
1455 used with the --diff option. The keyword show can be omitted if the ID is numeric.
1457 "decline" will change the request state to "declined" and append a
1458 message that you specify with the --message option.
1460 "wipe" will permanently delete a request.
1462 "revoke" will set the request state to "revoked" and append a
1463 message that you specify with the --message option.
1465 "accept" will change the request state to "accepted" and will trigger
1466 the actual submit process. That would normally be a server-side copy of
1467 the source package to the target package.
1469 "checkout" will checkout the request's source package. This only works for "submit" requests.
1472 osc request list [-M] [-U USER] [-s state] [-D DAYS] [-t type] [-B] [PRJ [PKG]]
1474 osc request [show] [-d] [-b] ID
1475 osc request accept [-m TEXT] ID
1476 osc request approvenew [-m TEXT] PROJECT
1477 osc request decline [-m TEXT] ID
1478 osc request revoke [-m TEXT] ID
1480 osc request checkout/co ID
1481 osc review accept [-m TEXT] ID
1482 osc review decline [-m TEXT] ID
1483 osc review new [-m TEXT] ID # for setting a temporary comment without changing the state
1487 args = slash_split(args)
1489 if opts.all and opts.state:
1490 raise oscerr.WrongOptions('Sorry, the options \'--all\' and \'--state\' ' \
1491 'are mutually exclusive.')
1492 if opts.mine and opts.user:
1493 raise oscerr.WrongOptions('Sorry, the options \'--user\' and \'--mine\' ' \
1494 'are mutually exclusive.')
1495 if opts.interactive and opts.non_interactive:
1496 raise oscerr.WrongOptions('Sorry, the options \'--interactive\' and ' \
1497 '\'--non-interactive\' are mutually exclusive')
1502 if opts.state == '':
1505 if opts.state == '':
1508 cmds = ['list', 'log', 'show', 'decline', 'accept', 'approvenew', 'wipe', 'revoke', 'checkout', 'co', 'help']
1509 if not args or args[0] not in cmds:
1510 raise oscerr.WrongArgs('Unknown request action %s. Choose one of %s.' \
1511 % (args[0],', '.join(cmds)))
1517 return self.do_help(['help', 'request'])
1520 min_args, max_args = 0, 2
1522 min_args, max_args = 1, 1
1523 if len(args) < min_args:
1524 raise oscerr.WrongArgs('Too few arguments.')
1525 if len(args) > max_args:
1526 raise oscerr.WrongArgs('Too many arguments.')
1528 apiurl = self.get_api_url()
1530 if cmd == 'list' or cmd == 'approvenew':
1535 elif not opts.mine and not opts.user:
1537 project = store_read_project(os.curdir)
1538 package = store_read_package(os.curdir)
1539 except oscerr.NoWorkingCopy:
1544 elif cmd in ['log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co']:
1547 # list and approvenew
1548 if cmd == 'list' or cmd == 'approvenew':
1549 states = ('new', 'accepted', 'revoked', 'declined')
1551 if cmd == 'approvenew':
1553 results = get_request_list(apiurl, project, package, '', ['new'])
1555 state_list = opts.state.split(',')
1556 if opts.state == 'all':
1557 state_list = ['all']
1559 for s in state_list:
1561 raise oscerr.WrongArgs('Unknown state \'%s\', try one of %s' % (s, ','.join(states)))
1563 who = conf.get_apiurl_usr(apiurl)
1567 state_list = ['all']
1569 ## FIXME -B not implemented!
1571 if (self.options.debug):
1572 print 'list: option --bugowner ignored: not impl.'
1574 if opts.involved_projects:
1575 who = who or conf.get_apiurl_usr(apiurl)
1576 results = get_user_projpkgs_request_list(apiurl, who, req_state=state_list,
1577 req_type=opts.type, exclude_projects=opts.exclude_target_project or [])
1579 results = get_request_list(apiurl, project, package, who,
1580 state_list, opts.type, opts.exclude_target_project or [])
1582 results.sort(reverse=True)
1584 days = opts.days or conf.config['request_list_days']
1591 since = time.strftime('%Y-%m-%dT%H:%M:%S',time.localtime(time.time()-days*24*3600))
1594 ## bs has received 2009-09-20 a new xquery compare() function
1595 ## which allows us to limit the list inside of get_request_list
1596 ## That would be much faster for coolo. But counting the remainder
1597 ## would not be possible with current xquery implementation.
1598 ## Workaround: fetch all, and filter on client side.
1600 ## FIXME: date filtering should become implemented on server side
1601 for result in results:
1602 if days == 0 or result.state.when > since or result.state.name == 'new':
1603 print result.list_view()
1607 print "There are %d requests older than %s days.\n" % (skipped, days)
1609 if cmd == 'approvenew':
1610 print "\n *** Approve them all ? [y/n] ***"
1611 if sys.stdin.read(1) == "y":
1613 if not opts.message:
1614 opts.message = edit_message()
1615 for result in results:
1616 print result.reqid, ": ",
1617 r = change_request_state(conf.config['apiurl'],
1618 str(result.reqid), 'accepted', opts.message or '')
1621 print >>sys.stderr, 'Aborted...'
1622 raise oscerr.UserAbort()
1625 for l in get_request_log(conf.config['apiurl'], reqid):
1630 r = get_request(conf.config['apiurl'], reqid)
1633 elif (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive:
1634 return request_interactive_review(conf.config['apiurl'], r)
1637 # fixme: will inevitably fail if the given target doesn't exist
1638 if opts.diff and r.actions[0].type != 'submit':
1639 raise oscerr.WrongOptions('\'--diff\' is not possible for request type: \'%s\'' % r.actions[0].type)
1642 print server_diff(conf.config['apiurl'],
1643 r.actions[0].dst_project, r.actions[0].dst_package, None,
1644 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, True)
1645 except urllib2.HTTPError, e:
1647 e.osc_msg = 'Diff not possible'
1649 # backward compatiblity: only a recent api/backend supports the missingok parameter
1651 print server_diff(conf.config['apiurl'],
1652 r.actions[0].dst_project, r.actions[0].dst_package, None,
1653 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, False)
1654 except urllib2.HTTPError, e:
1655 e.osc_msg = 'Diff not possible'
1659 elif cmd == 'checkout' or cmd == 'co':
1660 r = get_request(conf.config['apiurl'], reqid)
1661 submits = [ i for i in r.actions if i.type == 'submit' ]
1662 if not len(submits):
1663 raise oscerr.WrongArgs('\'checkout\' only works for \'submit\' requests')
1664 checkout_package(conf.config['apiurl'], submits[0].src_project, submits[0].src_package, \
1665 submits[0].src_rev, expand_link=True, prj_dir=submits[0].src_project)
1668 if not opts.message:
1669 opts.message = edit_message()
1670 state_map = {'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked'}
1671 # Change review state only
1672 if subcmd == 'review':
1673 if cmd in ['accept', 'decline', 'new']:
1674 r = change_review_state(conf.config['apiurl'],
1675 reqid, state_map[cmd], conf.config['user'], '', opts.message or '')
1677 # Change state of entire request
1678 elif cmd in ['accept', 'decline', 'wipe', 'revoke']:
1679 r = change_request_state(conf.config['apiurl'],
1680 reqid, state_map[cmd], opts.message or '')
1683 # editmeta and its aliases are all depracated
1684 @cmdln.alias("editprj")
1685 @cmdln.alias("createprj")
1686 @cmdln.alias("editpac")
1687 @cmdln.alias("createpac")
1688 @cmdln.alias("edituser")
1689 @cmdln.alias("usermeta")
1691 def do_editmeta(self, subcmd, opts, *args):
1694 Obsolete command to edit metadata. Use 'meta' now.
1696 See the help output of 'meta'.
1700 print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
1701 print >>sys.stderr, 'See \'osc help meta\'.'
1702 #self.do_help([None, 'meta'])
1706 @cmdln.option('-r', '--revision', metavar='rev',
1707 help='use the specified revision.')
1708 @cmdln.option('-u', '--unset', action='store_true',
1709 help='remove revision in link, it will point always to latest revision')
1710 def do_setlinkrev(self, subcmd, opts, *args):
1711 """${cmd_name}: Updates a revision number in a source link.
1713 This command adds or updates a specified revision number in a source link.
1714 The current revision of the source is used, if no revision number is specified.
1718 osc setlinkrev PROJECT [PACKAGE]
1722 args = slash_split(args)
1723 apiurl = conf.config['apiurl']
1726 p = findpacs(os.curdir)[0]
1731 sys.exit('Local directory is no checked out source link package, aborting')
1732 elif len(args) == 2:
1735 elif len(args) == 1:
1738 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1739 + self.get_cmd_help('setlinkrev'))
1742 packages = [ package ]
1744 packages = meta_get_packagelist(apiurl, project)
1747 print "setting revision for package", p
1751 rev, dummy = parseRevisionOption(opts.revision)
1752 set_link_rev(apiurl, project, p, rev)
1755 def do_linktobranch(self, subcmd, opts, *args):
1756 """${cmd_name}: Convert a package containing a classic link with patch to a branch
1758 This command tells the server to convert a _link with or without a project.diff
1759 to a branch. This is a full copy with a _link file pointing to the branched place.
1762 osc linktobranch # can be used in checked out package
1763 osc linktobranch PROJECT PACKAGE
1766 args = slash_split(args)
1767 apiurl = self.get_api_url()
1771 project = store_read_project(wd)
1772 package = store_read_package(wd)
1773 update_local_dir = True
1775 raise oscerr.WrongArgs('Too few arguments (required none or two)')
1777 raise oscerr.WrongArgs('Too many arguments (required none or two)')
1781 update_local_dir = False
1784 link_to_branch(apiurl, project, package)
1785 if update_local_dir:
1787 pac.update(rev=pac.latest_rev())
1790 @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'],
1791 help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.')
1792 @cmdln.option('-c', '--current', action='store_true',
1793 help='link fixed against current revision.')
1794 @cmdln.option('-r', '--revision', metavar='rev',
1795 help='link the specified revision.')
1796 @cmdln.option('-f', '--force', action='store_true',
1797 help='overwrite an existing link file if it is there.')
1798 @cmdln.option('-d', '--disable-publish', action='store_true',
1799 help='disable publishing of the linked package')
1800 def do_linkpac(self, subcmd, opts, *args):
1801 """${cmd_name}: "Link" a package to another package
1803 A linked package is a clone of another package, but plus local
1804 modifications. It can be cross-project.
1806 The DESTPAC name is optional; the source packages' name will be used if
1809 Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
1811 To add a patch, add the patch as file and add it to the _link file.
1812 You can also specify text which will be inserted at the top of the spec file.
1814 See the examples in the _link file.
1817 osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1821 args = slash_split(args)
1823 if not args or len(args) < 3:
1824 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1825 + self.get_cmd_help('linkpac'))
1827 rev, dummy = parseRevisionOption(opts.revision)
1829 src_project = args[0]
1830 src_package = args[1]
1831 dst_project = args[2]
1833 dst_package = args[3]
1835 dst_package = src_package
1837 if src_project == dst_project and src_package == dst_package:
1838 raise oscerr.WrongArgs('Error: source and destination are the same.')
1840 if src_project == dst_project and not opts.cicount:
1841 # in this case, the user usually wants to build different spec
1842 # files from the same source
1843 opts.cicount = "copy"
1846 rev = show_upstream_rev(conf.config['apiurl'], src_project, src_package)
1848 if rev and not checkRevision(src_project, src_package, rev):
1849 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
1852 link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish)
1854 @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]',
1855 help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]')
1856 @cmdln.option('-d', '--disable-publish', action='store_true',
1857 help='disable publishing of the aggregated package')
1858 def do_aggregatepac(self, subcmd, opts, *args):
1859 """${cmd_name}: "Aggregate" a package to another package
1861 Aggregation of a package means that the build results (binaries) of a
1862 package are basically copied into another project.
1863 This can be used to make packages available from building that are
1864 needed in a project but available only in a different project. Note
1865 that this is done at the expense of disk space. See
1866 http://en.opensuse.org/Build_Service/Tips_and_Tricks#_link_and__aggregate
1867 for more information.
1869 The DESTPAC name is optional; the source packages' name will be used if
1873 osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1877 args = slash_split(args)
1879 if not args or len(args) < 3:
1880 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1881 + self.get_cmd_help('aggregatepac'))
1883 src_project = args[0]
1884 src_package = args[1]
1885 dst_project = args[2]
1887 dst_package = args[3]
1889 dst_package = src_package
1891 if src_project == dst_project and src_package == dst_package:
1892 raise oscerr.WrongArgs('Error: source and destination are the same.')
1896 for pair in opts.map_repo.split(','):
1897 src_tgt = pair.split('=')
1898 if len(src_tgt) != 2:
1899 raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo)
1900 repo_map[src_tgt[0]] = src_tgt[1]
1902 aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish)
1905 @cmdln.option('-c', '--client-side-copy', action='store_true',
1906 help='do a (slower) client-side copy')
1907 @cmdln.option('-k', '--keep-maintainers', action='store_true',
1908 help='keep original maintainers. Default is remove all and replace with the one calling the script.')
1909 @cmdln.option('-d', '--keep-develproject', action='store_true',
1910 help='keep develproject tag in the package metadata')
1911 @cmdln.option('-r', '--revision', metavar='rev',
1912 help='link the specified revision.')
1913 @cmdln.option('-t', '--to-apiurl', metavar='URL',
1914 help='URL of destination api server. Default is the source api server.')
1915 @cmdln.option('-m', '--message', metavar='TEXT',
1916 help='specify message TEXT')
1917 @cmdln.option('-e', '--expand', action='store_true',
1918 help='if the source package is a link then copy the expanded version of the link')
1919 def do_copypac(self, subcmd, opts, *args):
1920 """${cmd_name}: Copy a package
1922 A way to copy package to somewhere else.
1924 It can be done across buildservice instances, if the -t option is used.
1925 In that case, a client-side copy is implied.
1927 Using --client-side-copy always involves downloading all files, and
1928 uploading them to the target.
1930 The DESTPAC name is optional; the source packages' name will be used if
1934 osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1938 args = slash_split(args)
1940 if not args or len(args) < 3:
1941 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1942 + self.get_cmd_help('copypac'))
1944 src_project = args[0]
1945 src_package = args[1]
1946 dst_project = args[2]
1948 dst_package = args[3]
1950 dst_package = src_package
1952 src_apiurl = conf.config['apiurl']
1954 dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl)
1956 dst_apiurl = src_apiurl
1958 if src_apiurl != dst_apiurl:
1959 opts.client_side_copy = True
1961 rev, dummy = parseRevisionOption(opts.revision)
1964 comment = opts.message
1967 rev = show_upstream_rev(src_apiurl, src_project, src_package)
1968 comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev )
1970 if src_project == dst_project and \
1971 src_package == dst_package and \
1973 src_apiurl == dst_apiurl:
1974 raise oscerr.WrongArgs('Source and destination are the same.')
1976 r = copy_pac(src_apiurl, src_project, src_package,
1977 dst_apiurl, dst_project, dst_package,
1978 client_side_copy=opts.client_side_copy,
1979 keep_maintainers=opts.keep_maintainers,
1980 keep_develproject=opts.keep_develproject,
1987 @cmdln.option('-c', '--checkout', action='store_true',
1988 help='Checkout branched package afterwards ' \
1989 '(\'osc bco\' is a shorthand for this option)' )
1990 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
1991 help='Use this attribute to find affected packages (default is OBS:Maintained)')
1992 @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE',
1993 help='Use this attribute to find update projects (default is OBS:UpdateProject) ')
1994 def do_mbranch(self, subcmd, opts, *args):
1995 """${cmd_name}: Multiple branch of a package
1997 [See http://en.opensuse.org/Build_Service/Concepts/Maintenance for information
2000 This command is used for creating multiple links of defined version of a package
2001 in one project. This is esp. used for maintenance updates.
2003 The branched package will live in
2004 home:USERNAME:branches:ATTRIBUTE:PACKAGE
2005 if nothing else specified.
2008 osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ]
2011 args = slash_split(args)
2014 maintained_attribute = conf.config['maintained_attribute']
2015 maintained_update_project_attribute = conf.config['maintained_update_project_attribute']
2017 if not len(args) or len(args) > 2:
2018 raise oscerr.WrongArgs('Wrong number of arguments.')
2024 r = attribute_branch_pkg(conf.config['apiurl'], maintained_attribute, maintained_update_project_attribute, \
2028 print >>sys.stderr, 'ERROR: Attribute branch call came not back with a project.'
2031 print "Project " + r + " created."
2034 init_project_dir(conf.config['apiurl'], r, r)
2035 print statfrmt('A', r)
2038 for package in meta_get_packagelist(conf.config['apiurl'], r):
2040 checkout_package(conf.config['apiurl'], r, package, expand_link = True, prj_dir = r)
2042 print >>sys.stderr, 'Error while checkout package:\n', package
2044 if conf.config['verbose']:
2045 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
2048 @cmdln.alias('branchco')
2050 @cmdln.alias('getpac')
2051 @cmdln.option('--nodevelproject', action='store_true',
2052 help='do not follow a defined devel project ' \
2053 '(primary project where a package is developed)')
2054 @cmdln.option('-c', '--checkout', action='store_true',
2055 help='Checkout branched package afterwards ' \
2056 '(\'osc bco\' is a shorthand for this option)' )
2057 @cmdln.option('-f', '--force', default=False, action="store_true",
2058 help='force branch, overwrite target')
2059 @cmdln.option('-m', '--message', metavar='TEXT',
2060 help='specify message TEXT')
2061 @cmdln.option('-r', '--revision', metavar='rev',
2062 help='branch against a specific revision')
2063 def do_branch(self, subcmd, opts, *args):
2064 """${cmd_name}: Branch a package
2066 [See http://en.opensuse.org/Build_Service/Collaboration for information
2069 Create a source link from a package of an existing project to a new
2070 subproject of the requesters home project (home:branches:)
2072 The branched package will live in
2073 home:USERNAME:branches:PROJECT/PACKAGE
2074 if nothing else specified.
2076 With getpac or bco, the branched package will come from
2077 %(getpac_default_project)s
2078 if nothing else specified.
2082 osc branch SOURCEPROJECT SOURCEPACKAGE
2083 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT
2084 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE
2085 osc getpac SOURCEPACKAGE
2090 if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': opts.checkout = True
2091 args = slash_split(args)
2092 tproject = tpackage = None
2094 if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1:
2095 print >>sys.stderr, 'defaulting to %s/%s' % (conf.config['getpac_default_project'], args[0])
2096 # python has no args.unshift ???
2097 args = [ conf.config['getpac_default_project'] , args[0] ]
2099 if len(args) == 0 and is_package_dir('.'):
2100 args = (store_read_project('.'), store_read_package('.'))
2102 if len(args) < 2 or len(args) > 4:
2103 raise oscerr.WrongArgs('Wrong number of arguments.')
2105 expected = 'home:%s:branches:%s' % (conf.config['user'], args[0])
2107 expected = tproject = args[2]
2111 if not opts.message:
2112 footer='please specify the purpose of your branch'
2113 template='This package was branched from %s in order to ...\n' % args[0]
2114 opts.message = edit_message(footer, template)
2116 exists, targetprj, targetpkg, srcprj, srcpkg = \
2117 branch_pkg(conf.config['apiurl'], args[0], args[1],
2118 nodevelproject=opts.nodevelproject, rev=opts.revision,
2119 target_project=tproject, target_package=tpackage,
2120 return_existing=opts.checkout, msg=opts.message or '',
2123 print >>sys.stderr, 'Using existing branch project: %s' % targetprj
2126 if not exists and (srcprj is not None and srcprj != args[0] or \
2127 srcprj is None and targetprj != expected):
2128 devloc = srcprj or targetprj
2129 if not srcprj and 'branches:' in targetprj:
2130 devloc = targetprj.split('branches:')[1]
2131 print '\nNote: The branch has been created of a different project,\n' \
2133 ' which is the primary location of where development for\n' \
2134 ' that package takes place.\n' \
2135 ' That\'s also where you would normally make changes against.\n' \
2136 ' A direct branch of the specified package can be forced\n' \
2137 ' with the --nodevelproject option.\n' % devloc
2139 package = tpackage or args[1]
2141 checkout_package(conf.config['apiurl'], targetprj, package,
2142 expand_link=True, prj_dir=targetprj)
2143 if conf.config['verbose']:
2144 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
2147 if conf.get_configParser().get('general', 'apiurl') != conf.config['apiurl']:
2148 apiopt = '-A %s ' % conf.config['apiurl']
2149 print 'A working copy of the branched package can be checked out with:\n\n' \
2151 % (apiopt, targetprj, package)
2152 print_request_list(conf.config['apiurl'], args[0], args[1])
2154 print_request_list(conf.config['apiurl'], devloc, args[1])
2157 def do_undelete(self, subcmd, opts, *args):
2158 """${cmd_name}: Restores a deleted project or package on the server.
2160 The server restores a package including the sources and meta configuration.
2161 Binaries remain to be lost and will be rebuild.
2164 osc undelete PROJECT
2165 osc undelete PROJECT PACKAGE [PACKAGE ...]
2170 args = slash_split(args)
2172 raise oscerr.WrongArgs('Missing argument.')
2178 undelete_package(conf.config['apiurl'], prj, pkg)
2180 undelete_project(conf.config['apiurl'], prj)
2183 @cmdln.option('-f', '--force', action='store_true',
2184 help='deletes a package or an empty project')
2185 def do_rdelete(self, subcmd, opts, *args):
2186 """${cmd_name}: Delete a project or packages on the server.
2188 As a safety measure, project must be empty (i.e., you need to delete all
2189 packages first). If you are sure that you want to remove this project and all
2190 its packages use \'--force\' switch.
2193 osc rdelete -f PROJECT
2194 osc rdelete PROJECT PACKAGE [PACKAGE ...]
2199 args = slash_split(args)
2201 raise oscerr.WrongArgs('Missing argument.')
2207 # careful: if pkg is an empty string, the package delete request results
2208 # into a project delete request - which works recursively...
2210 delete_package(conf.config['apiurl'], prj, pkg)
2211 elif len(meta_get_packagelist(conf.config['apiurl'], prj)) >= 1 and not opts.force:
2212 print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
2213 'If you are sure that you want to remove this project and all its ' \
2214 'packages use the \'--force\' switch'
2217 delete_project(conf.config['apiurl'], prj)
2220 def do_deletepac(self, subcmd, opts, *args):
2221 print """${cmd_name} is obsolete !
2224 osc delete for checked out packages or projects
2226 osc rdelete for server side operations."""
2231 @cmdln.option('-f', '--force', action='store_true',
2232 help='deletes a project and its packages')
2233 def do_deleteprj(self, subcmd, opts, project):
2234 """${cmd_name} is obsolete !
2241 @cmdln.alias('metafromspec')
2242 @cmdln.option('', '--specfile', metavar='FILE',
2243 help='Path to specfile. (if you pass more than working copy this option is ignored)')
2244 def do_updatepacmetafromspec(self, subcmd, opts, *args):
2245 """${cmd_name}: Update package meta information from a specfile
2247 ARG, if specified, is a package working copy.
2253 args = parseargs(args)
2254 if opts.specfile and len(args) == 1:
2255 specfile = opts.specfile
2258 pacs = findpacs(args)
2260 p.read_meta_from_spec(specfile)
2261 p.update_package_meta()
2265 @cmdln.option('-c', '--change', metavar='rev',
2266 help='the change made by revision rev (like -r rev-1:rev).'
2267 'If rev is negative this is like -r rev:rev-1.')
2268 @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
2269 help='If rev1 is specified it will compare your working copy against '
2270 'the revision (rev1) on the server. '
2271 'If rev1 and rev2 are specified it will compare rev1 against rev2 '
2272 '(NOTE: changes in your working copy are ignored in this case)')
2273 @cmdln.option('-p', '--plain', action='store_true',
2274 help='output the diff in plain (not unified) diff format')
2275 @cmdln.option('--missingok', action='store_true',
2276 help='do not fail if the source or target project/package does not exist on the server')
2277 def do_diff(self, subcmd, opts, *args):
2278 """${cmd_name}: Generates a diff
2280 Generates a diff, comparing local changes against the repository
2283 ARG, specified, is a filename to include in the diff.
2289 args = parseargs(args)
2290 pacs = findpacs(args)
2294 rev = int(opts.change)
2304 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
2307 rev1, rev2 = parseRevisionOption(opts.revision)
2311 diff += ''.join(make_diff(pac, rev1))
2313 diff += server_diff(pac.apiurl, pac.prjname, pac.name, rev1,
2314 pac.prjname, pac.name, rev2, not opts.plain, opts.missingok)
2319 @cmdln.option('--oldprj', metavar='OLDPRJ',
2320 help='project to compare against'
2321 ' (deprecated, use 3 argument form)')
2322 @cmdln.option('--oldpkg', metavar='OLDPKG',
2323 help='package to compare against'
2324 ' (deprecated, use 3 argument form)')
2325 @cmdln.option('-r', '--revision', metavar='N[:M]',
2326 help='revision id, where N = old revision and M = new revision')
2327 @cmdln.option('-p', '--plain', action='store_true',
2328 help='output the diff in plain (not unified) diff format')
2329 @cmdln.option('-c', '--change', metavar='rev',
2330 help='the change made by revision rev (like -r rev-1:rev). '
2331 'If rev is negative this is like -r rev:rev-1.')
2332 @cmdln.option('--missingok', action='store_true',
2333 help='do not fail if the source or target project/package does not exist on the server')
2334 def do_rdiff(self, subcmd, opts, *args):
2335 """${cmd_name}: Server-side "pretty" diff of two packages
2337 Compares two packages (three or four arguments) or shows the
2338 changes of a specified revision of a package (two arguments)
2340 If no revision is specified the latest revision is used.
2342 Note that this command doesn't return a normal diff (which could be
2343 applied as patch), but a "pretty" diff, which also compares the content
2348 osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC]
2349 osc ${cmd_name} PROJECT PACKAGE
2353 args = slash_split(args)
2364 new_project = args[0]
2365 new_package = args[1]
2367 old_project = opts.oldprj
2369 old_package = opts.oldpkg
2370 elif len(args) == 3 or len(args) == 4:
2371 if opts.oldprj or opts.oldpkg:
2372 raise oscerr.WrongArgs('--oldpkg and --oldprj are only valid with two arguments')
2373 old_project = args[0]
2374 new_package = old_package = args[1]
2375 new_project = args[2]
2377 new_package = args[3]
2379 raise oscerr.WrongArgs('Wrong number of arguments')
2384 rev = int(opts.change)
2394 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
2398 rev1, rev2 = parseRevisionOption(opts.revision)
2400 rdiff = server_diff(conf.config['apiurl'],
2401 old_project, old_package, rev1,
2402 new_project, new_package, rev2, not opts.plain, opts.missingok)
2407 def do_install(self, subcmd, opts, *args):
2408 """${cmd_name}: install a package after build via zypper in -r
2410 Not implemented yet. Use osc repourls,
2411 select the url you best like (standard),
2412 chop off after the last /, this should work with zypper.
2419 args = slash_split(args)
2420 args = expand_proj_pack(args)
2423 ## if there is only one argument, and it ends in .ymp
2424 ## then fetch it, Parse XML to get the first
2425 ## metapackage.group.repositories.repository.url
2426 ## and construct zypper cmd's for all
2427 ## metapackage.group.software.item.name
2429 ## if args[0] is already an url, the use it as is.
2431 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])
2432 print self.do_install.__doc__
2433 print "Example: \n" + cmd
2436 def do_repourls(self, subcmd, opts, *args):
2437 """${cmd_name}: Shows URLs of .repo files
2439 Shows URLs on which to access the project .repos files (yum-style
2440 metadata) on download.opensuse.org.
2443 osc repourls [PROJECT]
2448 apiurl = self.get_api_url()
2452 elif len(args) == 0:
2453 project = store_read_project('.')
2455 raise oscerr.WrongArgs('Wrong number of arguments')
2457 # XXX: API should somehow tell that
2458 url_tmpl = 'http://download.opensuse.org/repositories/%s/%s/%s.repo'
2459 repos = get_repositories_of_project(apiurl, project)
2461 print url_tmpl % (project.replace(':', ':/'), repo, project)
2464 @cmdln.option('-r', '--revision', metavar='rev',
2465 help='checkout the specified revision. '
2466 'NOTE: if you checkout the complete project '
2467 'this option is ignored!')
2468 @cmdln.option('-e', '--expand-link', action='store_true',
2469 help='if a package is a link, check out the expanded '
2470 'sources (no-op, since this became the default)')
2471 @cmdln.option('-u', '--unexpand-link', action='store_true',
2472 help='if a package is a link, check out the _link file ' \
2473 'instead of the expanded sources')
2474 @cmdln.option('-M', '--meta', action='store_true',
2475 help='checkout out meta data instead of sources' )
2476 @cmdln.option('-c', '--current-dir', action='store_true',
2477 help='place PACKAGE folder in the current directory' \
2478 'instead of a PROJECT/PACKAGE directory')
2479 @cmdln.option('-s', '--source-service-files', action='store_true',
2480 help='Use server side generated sources instead of local generation.' )
2481 @cmdln.option('-S', '--server-side-source-service-files', action='store_true',
2482 help='Use server side generated sources instead of local generation.' )
2483 @cmdln.option('-l', '--limit-size', metavar='limit_size',
2484 help='Skip all files with a given size')
2486 def do_checkout(self, subcmd, opts, *args):
2487 """${cmd_name}: Check out content from the repository
2489 Check out content from the repository server, creating a local working
2492 When checking out a single package, the option --revision can be used
2493 to specify a revision of the package to be checked out.
2495 When a package is a source link, then it will be checked out in
2496 expanded form. If --unexpand-link option is used, the checkout will
2497 instead produce the raw _link file plus patches.
2500 osc co PROJECT [PACKAGE] [FILE]
2501 osc co PROJECT # entire project
2502 osc co PROJECT PACKAGE # a package
2503 osc co PROJECT PACKAGE FILE # single file -> to current dir
2505 while inside a project directory:
2506 osc co PACKAGE # check out PACKAGE from project
2511 if opts.unexpand_link:
2516 args = slash_split(args)
2517 project = package = filename = None
2519 apiurl = self.get_api_url()
2522 project = project_dir = args[0]
2528 if args and len(args) == 1:
2529 localdir = os.getcwd()
2530 if is_project_dir(localdir):
2531 project = store_read_project(localdir)
2532 project_dir = localdir
2535 rev, dummy = parseRevisionOption(opts.revision)
2539 if rev and rev != "latest" and not checkRevision(project, package, rev):
2540 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2544 get_source_file(apiurl, project, package, filename, revision=rev, progress_obj=self.download_progress)
2547 if opts.current_dir:
2549 checkout_package(apiurl, project, package, rev, expand_link=expand_link, \
2550 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)
2551 print_request_list(apiurl, project, package)
2555 if sys.platform[:3] == 'win':
2556 prj_dir = prj_dir.replace(':', ';')
2557 if os.path.exists(prj_dir):
2558 sys.exit('osc: project \'%s\' already exists' % project)
2560 # check if the project does exist (show_project_meta will throw an exception)
2561 show_project_meta(apiurl, project)
2563 init_project_dir(apiurl, prj_dir, project)
2564 print statfrmt('A', prj_dir)
2567 for package in meta_get_packagelist(apiurl, project):
2569 checkout_package(apiurl, project, package, expand_link = expand_link, \
2570 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)
2571 except oscerr.LinkExpandError, e:
2572 print >>sys.stderr, 'Link cannot be expanded:\n', e
2573 print >>sys.stderr, 'Use "osc repairlink" for fixing merge conflicts:\n'
2574 # check out in unexpanded form at least
2575 checkout_package(apiurl, project, package, expand_link = False, \
2576 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)
2577 print_request_list(apiurl, project)
2580 raise oscerr.WrongArgs('Missing argument.\n\n' \
2581 + self.get_cmd_help('checkout'))
2584 @cmdln.option('-q', '--quiet', action='store_true',
2585 help='print as little as possible')
2586 @cmdln.option('-v', '--verbose', action='store_true',
2587 help='print extra information')
2589 def do_status(self, subcmd, opts, *args):
2590 """${cmd_name}: Show status of files in working copy
2592 Show the status of files in a local working copy, indicating whether
2593 files have been changed locally, deleted, added, ...
2595 The first column in the output specifies the status and is one of the
2596 following characters:
2597 ' ' no modifications
2602 '?' item is not under version control
2603 '!' item is missing (removed by non-osc command) or incomplete
2608 osc st file1 file2 ...
2611 osc status [OPTS] [PATH...]
2615 args = parseargs(args)
2617 # storage for single Package() objects
2619 # storage for a project dir ( { prj_instance : [ package objects ] } )
2622 # when 'status' is run inside a project dir, it should
2623 # stat all packages existing in the wc
2624 if is_project_dir(arg):
2625 prj = Project(arg, False)
2627 if conf.config['do_package_tracking']:
2629 for pac in prj.pacs_have:
2630 # we cannot create package objects if the dir does not exist
2631 if not pac in prj.pacs_broken:
2632 prjpacs[prj].append(os.path.join(arg, pac))
2634 pacpaths += [arg + '/' + n for n in prj.pacs_have]
2635 elif is_package_dir(arg):
2636 pacpaths.append(arg)
2637 elif os.path.isfile(arg):
2638 pacpaths.append(arg)
2640 msg = '\'%s\' is neither a project or a package directory' % arg
2641 raise oscerr.NoWorkingCopy, msg
2643 # process single packages
2644 lines = getStatus(findpacs(pacpaths), None, opts.verbose, opts.quiet)
2645 # process project dirs
2646 for prj, pacs in prjpacs.iteritems():
2647 lines += getStatus(findpacs(pacs), prj, opts.verbose, opts.quiet)
2649 print '\n'.join(lines)
2652 def do_add(self, subcmd, opts, *args):
2653 """${cmd_name}: Mark files to be added upon the next commit
2655 In case a URL is given the file will get downloaded and registered to be downloaded
2656 by the server as well via the download_url source service.
2658 This is recommended for release tar balls to track their source and to help
2659 others to review your changes esp. on version upgrades.
2662 osc add URL [URL...]
2663 osc add FILE [FILE...]
2667 raise oscerr.WrongArgs('Missing argument.\n\n' \
2668 + self.get_cmd_help('add'))
2670 # Do some magic here, when adding a url. We want that the server to download the tar ball and to verify it
2671 for arg in parseargs(args):
2672 if arg.startswith('http://') or arg.startswith('https://') or arg.startswith('ftp://'):
2673 addDownloadUrlService(arg)
2678 def do_mkpac(self, subcmd, opts, *args):
2679 """${cmd_name}: Create a new package under version control
2682 osc mkpac new_package
2685 if not conf.config['do_package_tracking']:
2686 print >>sys.stderr, "to use this feature you have to enable \'do_package_tracking\' " \
2687 "in the [general] section in the configuration file"
2691 raise oscerr.WrongArgs('Wrong number of arguments.')
2693 createPackageDir(args[0])
2695 @cmdln.option('-r', '--recursive', action='store_true',
2696 help='If CWD is a project dir then scan all package dirs as well')
2698 def do_addremove(self, subcmd, opts, *args):
2699 """${cmd_name}: Adds new files, removes disappeared files
2701 Adds all files new in the local copy, and removes all disappeared files.
2703 ARG, if specified, is a package working copy.
2709 args = parseargs(args)
2711 for arg in arg_list:
2712 if is_project_dir(arg) and conf.config['do_package_tracking']:
2713 prj = Project(arg, False)
2714 for pac in prj.pacs_unvers:
2715 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2716 if os.path.isdir(pac_dir):
2717 addFiles([pac_dir], prj)
2718 for pac in prj.pacs_broken:
2719 if prj.get_state(pac) != 'D':
2720 prj.set_state(pac, 'D')
2721 print statfrmt('D', getTransActPath(os.path.join(prj.dir, pac)))
2723 for pac in prj.pacs_have:
2724 state = prj.get_state(pac)
2725 if state != None and state != 'D':
2726 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2727 args.append(pac_dir)
2729 prj.write_packages()
2730 elif is_project_dir(arg):
2731 print >>sys.stderr, 'osc: addremove is not supported in a project dir unless ' \
2732 '\'do_package_tracking\' is enabled in the configuration file'
2735 pacs = findpacs(args)
2737 p.todo = p.filenamelist + p.filenamelist_unvers
2739 for filename in p.todo:
2740 if os.path.isdir(filename):
2742 # ignore foo.rXX, foo.mine for files which are in 'C' state
2743 if os.path.splitext(filename)[0] in p.in_conflict:
2745 state = p.status(filename)
2748 # TODO: should ignore typical backup files suffix ~ or .orig
2750 print statfrmt('A', getTransActPath(os.path.join(p.dir, filename)))
2752 p.put_on_deletelist(filename)
2753 p.write_deletelist()
2754 os.unlink(os.path.join(p.storedir, filename))
2755 print statfrmt('D', getTransActPath(os.path.join(p.dir, filename)))
2760 @cmdln.alias('checkin')
2761 @cmdln.option('-m', '--message', metavar='TEXT',
2762 help='specify log message TEXT')
2763 @cmdln.option('-F', '--file', metavar='FILE',
2764 help='read log message from FILE')
2765 @cmdln.option('-f', '--force', default=False, action="store_true",
2766 help='force commit - do not tests a file list')
2767 @cmdln.option('--skip-validation', default=False, action="store_true",
2768 help='Skip the source validation')
2769 @cmdln.option('--verbose-validation', default=False, action="store_true",
2770 help='Run the source validation with verbose informations')
2771 def do_commit(self, subcmd, opts, *args):
2772 """${cmd_name}: Upload content to the repository server
2774 Upload content which is changed in your working copy, to the repository
2777 Optionally checks the state of a working copy, if found a file with
2778 unknown state, it requests an user input:
2779 * skip - don't change anything, just move to another file
2780 * remove - remove a file from dir
2781 * edit file list - edit filelist using EDITOR
2782 * commit - don't check anything and commit package
2783 * abort - abort commit - this is default value
2784 This can be supressed by check_filelist config item, or -f/--force
2785 command line option.
2788 osc ci # current dir
2790 osc ci file1 file2 ...
2796 args = parseargs(args)
2798 validators = conf.config['source_validator_directory']
2799 verbose_validation = None
2800 if opts.skip_validation:
2802 elif not os.path.exists(validators):
2803 print "WARNING: validator directory", validators, "configured, but not existing. Skipping ..."
2805 if opts.verbose_validation:
2806 verbose_validation = 1
2813 msg = open(opts.file).read()
2815 sys.exit('could not open file \'%s\'.' % opts.file)
2818 for arg in arg_list:
2819 if conf.config['do_package_tracking'] and is_project_dir(arg):
2821 msg = edit_message()
2823 Project(arg).commit(msg=msg, validators=validators, verbose_validation=verbose_validation)
2824 except oscerr.RuntimeError, e:
2825 print >>sys.stderr, "ERROR: source_validator failed", e
2829 pacs = findpacs(args)
2831 if conf.config['check_filelist'] and not opts.force:
2832 check_filelist_before_commit(pacs)
2835 template = store_read_file(os.path.abspath('.'), '_commit_msg')
2836 # open editor for commit message
2837 # but first, produce status and diff to append to the template
2841 changed = getStatus([pac], quiet=True)
2844 diffs += ['\nDiff for working copy: %s' % pac.dir]
2845 diffs += make_diff(pac, 0)
2846 lines.extend(get_commit_message_template(pac))
2847 if template == None:
2848 template='\n'.join(lines)
2849 # if footer is empty, there is nothing to commit, and no edit needed.
2851 msg = edit_message(footer='\n'.join(footer), template=template)
2854 store_write_string(os.path.abspath('.'), '_commit_msg', msg)
2856 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2858 if conf.config['do_package_tracking'] and len(pacs) > 0:
2862 # it is possible to commit packages from different projects at the same
2863 # time: iterate over all pacs and put each pac to the right project in the dict
2865 path = os.path.normpath(os.path.join(pac.dir, os.pardir))
2866 if is_project_dir(path):
2867 pac_path = os.path.basename(os.path.normpath(pac.absdir))
2868 prj_paths.setdefault(path, []).append(pac_path)
2869 files[pac_path] = pac.todo
2871 single_paths.append(pac.dir)
2872 for prj, packages in prj_paths.iteritems():
2874 Project(prj).commit(tuple(packages), msg=msg, files=files, validators=validators, verbose_validation=verbose_validation)
2875 except oscerr.RuntimeError, e:
2876 print >>sys.stderr, "ERROR: source_validator failed", e
2878 for pac in single_paths:
2880 Package(pac).commit(msg, validators=validators, verbose_validation=verbose_validation)
2881 except oscerr.RuntimeError, e:
2882 print >>sys.stderr, "ERROR: source_validator failed", e
2886 p.commit(msg, validators=validators, verbose_validation=verbose_validation)
2888 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2890 @cmdln.option('-r', '--revision', metavar='REV',
2891 help='update to specified revision (this option will be ignored '
2892 'if you are going to update the complete project or more than '
2894 @cmdln.option('-u', '--unexpand-link', action='store_true',
2895 help='if a package is an expanded link, update to the raw _link file')
2896 @cmdln.option('-e', '--expand-link', action='store_true',
2897 help='if a package is a link, update to the expanded sources')
2898 @cmdln.option('-s', '--source-service-files', action='store_true',
2899 help='Use server side generated sources instead of local generation.' )
2900 @cmdln.option('-S', '--server-side-source-service-files', action='store_true',
2901 help='Use server side generated sources instead of local generation.' )
2902 @cmdln.option('-l', '--limit-size', metavar='limit_size',
2903 help='Skip all files with a given size')
2905 def do_update(self, subcmd, opts, *args):
2906 """${cmd_name}: Update a working copy
2911 If the current working directory is a package, update it.
2912 If the directory is a project directory, update all contained
2913 packages, AND check out newly added packages.
2915 To update only checked out packages, without checking out new
2916 ones, you might want to use "osc up *" from within the project
2920 Update the packages specified by the path argument(s)
2922 When --expand-link is used with source link packages, the expanded
2923 sources will be checked out. Without this option, the _link file and
2924 patches will be checked out. The option --unexpand-link can be used to
2925 switch back to the "raw" source with a _link file plus patch(es).
2931 if (opts.expand_link and opts.unexpand_link) \
2932 or (opts.expand_link and opts.revision) \
2933 or (opts.unexpand_link and opts.revision):
2934 raise oscerr.WrongOptions('Sorry, the options --expand-link, --unexpand-link and '
2935 '--revision are mutually exclusive.')
2937 args = parseargs(args)
2940 for arg in arg_list:
2941 if is_project_dir(arg):
2942 prj = Project(arg, progress_obj=self.download_progress)
2944 if conf.config['do_package_tracking']:
2945 prj.update(expand_link=opts.expand_link,
2946 unexpand_link=opts.unexpand_link)
2949 # if not tracking package, and 'update' is run inside a project dir,
2950 # it should do the following:
2951 # (a) update all packages
2952 args += prj.pacs_have
2953 # (b) fetch new packages
2954 prj.checkout_missing_pacs(expand_link = not opts.unexpand_link)
2956 print_request_list(prj.apiurl, prj.name)
2959 pacs = findpacs(args, progress_obj=self.download_progress)
2961 if opts.revision and len(args) == 1:
2962 rev, dummy = parseRevisionOption(opts.revision)
2963 if not checkRevision(pacs[0].prjname, pacs[0].name, rev, pacs[0].apiurl):
2964 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2971 print 'Updating %s' % p.name
2973 # FIXME: ugly workaround for #399247
2974 if opts.expand_link or opts.unexpand_link:
2975 if [ i for i in p.filenamelist+p.filenamelist_unvers if p.status(i) != ' ' and p.status(i) != '?']:
2976 print >>sys.stderr, 'osc: cannot expand/unexpand because your working ' \
2977 'copy has local modifications.\nPlease revert/commit them ' \
2982 if opts.expand_link and p.islink() and not p.isexpanded():
2983 if p.haslinkerror():
2985 rev = p.show_upstream_xsrcmd5()
2987 rev = p.show_upstream_xsrcmd5(linkrev="base")
2990 p.update(rev, opts.server_side_source_service_files, opts.limit_size)
2991 rev = p.linkinfo.xsrcmd5
2992 print 'Expanding to rev', rev
2993 elif opts.unexpand_link and p.islink() and p.isexpanded():
2994 print 'Unexpanding to rev', p.linkinfo.lsrcmd5
2995 p.update(rev, opts.server_side_source_service_files, opts.limit_size)
2996 rev = p.linkinfo.lsrcmd5
2997 elif p.islink() and p.isexpanded():
2998 rev = p.latest_rev()
3000 p.update(rev, opts.server_side_source_service_files, opts.limit_size)
3001 if opts.source_service_files:
3002 print 'Running local source services'
3003 p.run_source_services()
3004 if opts.unexpand_link:
3007 print_request_list(p.apiurl, p.prjname, p.name)
3010 @cmdln.option('-f', '--force', action='store_true',
3011 help='forces removal of entire package and its files')
3014 @cmdln.alias('remove')
3015 def do_delete(self, subcmd, opts, *args):
3016 """${cmd_name}: Mark files or package directories to be deleted upon the next 'checkin'
3019 cd .../PROJECT/PACKAGE
3020 osc delete FILE [...]
3022 osc delete PACKAGE [...]
3024 This command works on check out copies. Use "rdelete" for working on server
3025 side only. This is needed for removing the entire project.
3027 As a safety measure, projects must be empty (i.e., you need to delete all
3030 If you are sure that you want to remove a package and all
3031 its files use \'--force\' switch. Sometimes this also works without --force.
3037 raise oscerr.WrongArgs('Missing argument.\n\n' \
3038 + self.get_cmd_help('delete'))
3040 args = parseargs(args)
3041 # check if args contains a package which was removed by
3042 # a non-osc command and mark it with the 'D'-state
3045 if not os.path.exists(i):
3046 prj_dir, pac_dir = getPrjPacPaths(i)
3047 if is_project_dir(prj_dir):
3048 prj = Project(prj_dir, False)
3049 if i in prj.pacs_broken:
3050 if prj.get_state(i) != 'A':
3051 prj.set_state(pac_dir, 'D')
3053 prj.del_package_node(i)
3054 print statfrmt('D', getTransActPath(i))
3056 prj.write_packages()
3057 pacs = findpacs(args)