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='server side generated files of source services' \
2481 'gets downloaded as well' )
2482 @cmdln.option('-l', '--limit-size', metavar='limit_size',
2483 help='Skip all files with a given size')
2485 def do_checkout(self, subcmd, opts, *args):
2486 """${cmd_name}: Check out content from the repository
2488 Check out content from the repository server, creating a local working
2491 When checking out a single package, the option --revision can be used
2492 to specify a revision of the package to be checked out.
2494 When a package is a source link, then it will be checked out in
2495 expanded form. If --unexpand-link option is used, the checkout will
2496 instead produce the raw _link file plus patches.
2499 osc co PROJECT [PACKAGE] [FILE]
2500 osc co PROJECT # entire project
2501 osc co PROJECT PACKAGE # a package
2502 osc co PROJECT PACKAGE FILE # single file -> to current dir
2504 while inside a project directory:
2505 osc co PACKAGE # check out PACKAGE from project
2510 if opts.unexpand_link:
2514 if opts.source_service_files:
2515 service_files = True
2517 service_files = False
2519 args = slash_split(args)
2520 project = package = filename = None
2522 apiurl = self.get_api_url()
2525 project = project_dir = args[0]
2531 if args and len(args) == 1:
2532 localdir = os.getcwd()
2533 if is_project_dir(localdir):
2534 project = store_read_project(localdir)
2535 project_dir = localdir
2538 rev, dummy = parseRevisionOption(opts.revision)
2542 if rev and rev != "latest" and not checkRevision(project, package, rev):
2543 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2547 get_source_file(apiurl, project, package, filename, revision=rev, progress_obj=self.download_progress)
2550 if opts.current_dir:
2552 checkout_package(apiurl, project, package, rev, expand_link=expand_link, \
2553 prj_dir=project_dir, service_files=service_files, progress_obj=self.download_progress, limit_size=opts.limit_size, meta=opts.meta)
2554 print_request_list(apiurl, project, package)
2558 if sys.platform[:3] == 'win':
2559 prj_dir = prj_dir.replace(':', ';')
2560 if os.path.exists(prj_dir):
2561 sys.exit('osc: project \'%s\' already exists' % project)
2563 # check if the project does exist (show_project_meta will throw an exception)
2564 show_project_meta(apiurl, project)
2566 init_project_dir(apiurl, prj_dir, project)
2567 print statfrmt('A', prj_dir)
2570 for package in meta_get_packagelist(apiurl, project):
2572 checkout_package(apiurl, project, package, expand_link = expand_link, \
2573 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress, limit_size=opts.limit_size, meta=opts.meta)
2574 except oscerr.LinkExpandError, e:
2575 print >>sys.stderr, 'Link cannot be expanded:\n', e
2576 print >>sys.stderr, 'Use "osc repairlink" for fixing merge conflicts:\n'
2577 # check out in unexpanded form at least
2578 checkout_package(apiurl, project, package, expand_link = False, \
2579 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress, limit_size=opts.limit_size, meta=opts.meta)
2580 print_request_list(apiurl, project)
2583 raise oscerr.WrongArgs('Missing argument.\n\n' \
2584 + self.get_cmd_help('checkout'))
2587 @cmdln.option('-q', '--quiet', action='store_true',
2588 help='print as little as possible')
2589 @cmdln.option('-v', '--verbose', action='store_true',
2590 help='print extra information')
2592 def do_status(self, subcmd, opts, *args):
2593 """${cmd_name}: Show status of files in working copy
2595 Show the status of files in a local working copy, indicating whether
2596 files have been changed locally, deleted, added, ...
2598 The first column in the output specifies the status and is one of the
2599 following characters:
2600 ' ' no modifications
2605 '?' item is not under version control
2606 '!' item is missing (removed by non-osc command) or incomplete
2611 osc st file1 file2 ...
2614 osc status [OPTS] [PATH...]
2618 args = parseargs(args)
2620 # storage for single Package() objects
2622 # storage for a project dir ( { prj_instance : [ package objects ] } )
2625 # when 'status' is run inside a project dir, it should
2626 # stat all packages existing in the wc
2627 if is_project_dir(arg):
2628 prj = Project(arg, False)
2630 if conf.config['do_package_tracking']:
2632 for pac in prj.pacs_have:
2633 # we cannot create package objects if the dir does not exist
2634 if not pac in prj.pacs_broken:
2635 prjpacs[prj].append(os.path.join(arg, pac))
2637 pacpaths += [arg + '/' + n for n in prj.pacs_have]
2638 elif is_package_dir(arg):
2639 pacpaths.append(arg)
2640 elif os.path.isfile(arg):
2641 pacpaths.append(arg)
2643 msg = '\'%s\' is neither a project or a package directory' % arg
2644 raise oscerr.NoWorkingCopy, msg
2646 # process single packages
2647 lines = getStatus(findpacs(pacpaths), None, opts.verbose, opts.quiet)
2648 # process project dirs
2649 for prj, pacs in prjpacs.iteritems():
2650 lines += getStatus(findpacs(pacs), prj, opts.verbose, opts.quiet)
2652 print '\n'.join(lines)
2655 def do_add(self, subcmd, opts, *args):
2656 """${cmd_name}: Mark files to be added upon the next commit
2658 In case a URL is given the file will get downloaded and registered to be downloaded
2659 by the server as well via the download_url source service.
2661 This is recommended for release tar balls to track their source and to help
2662 others to review your changes esp. on version upgrades.
2665 osc add URL [URL...]
2666 osc add FILE [FILE...]
2670 raise oscerr.WrongArgs('Missing argument.\n\n' \
2671 + self.get_cmd_help('add'))
2673 # Do some magic here, when adding a url. We want that the server to download the tar ball and to verify it
2674 for arg in parseargs(args):
2675 if arg.startswith('http://') or arg.startswith('https://') or arg.startswith('ftp://'):
2676 addDownloadUrlService(arg)
2681 def do_mkpac(self, subcmd, opts, *args):
2682 """${cmd_name}: Create a new package under version control
2685 osc mkpac new_package
2688 if not conf.config['do_package_tracking']:
2689 print >>sys.stderr, "to use this feature you have to enable \'do_package_tracking\' " \
2690 "in the [general] section in the configuration file"
2694 raise oscerr.WrongArgs('Wrong number of arguments.')
2696 createPackageDir(args[0])
2698 @cmdln.option('-r', '--recursive', action='store_true',
2699 help='If CWD is a project dir then scan all package dirs as well')
2701 def do_addremove(self, subcmd, opts, *args):
2702 """${cmd_name}: Adds new files, removes disappeared files
2704 Adds all files new in the local copy, and removes all disappeared files.
2706 ARG, if specified, is a package working copy.
2712 args = parseargs(args)
2714 for arg in arg_list:
2715 if is_project_dir(arg) and conf.config['do_package_tracking']:
2716 prj = Project(arg, False)
2717 for pac in prj.pacs_unvers:
2718 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2719 if os.path.isdir(pac_dir):
2720 addFiles([pac_dir], prj)
2721 for pac in prj.pacs_broken:
2722 if prj.get_state(pac) != 'D':
2723 prj.set_state(pac, 'D')
2724 print statfrmt('D', getTransActPath(os.path.join(prj.dir, pac)))
2726 for pac in prj.pacs_have:
2727 state = prj.get_state(pac)
2728 if state != None and state != 'D':
2729 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2730 args.append(pac_dir)
2732 prj.write_packages()
2733 elif is_project_dir(arg):
2734 print >>sys.stderr, 'osc: addremove is not supported in a project dir unless ' \
2735 '\'do_package_tracking\' is enabled in the configuration file'
2738 pacs = findpacs(args)
2740 p.todo = p.filenamelist + p.filenamelist_unvers
2742 for filename in p.todo:
2743 if os.path.isdir(filename):
2745 # ignore foo.rXX, foo.mine for files which are in 'C' state
2746 if os.path.splitext(filename)[0] in p.in_conflict:
2748 state = p.status(filename)
2751 # TODO: should ignore typical backup files suffix ~ or .orig
2753 print statfrmt('A', getTransActPath(os.path.join(p.dir, filename)))
2755 p.put_on_deletelist(filename)
2756 p.write_deletelist()
2757 os.unlink(os.path.join(p.storedir, filename))
2758 print statfrmt('D', getTransActPath(os.path.join(p.dir, filename)))
2763 @cmdln.alias('checkin')
2764 @cmdln.option('-m', '--message', metavar='TEXT',
2765 help='specify log message TEXT')
2766 @cmdln.option('-F', '--file', metavar='FILE',
2767 help='read log message from FILE')
2768 @cmdln.option('-f', '--force', default=False, action="store_true",
2769 help='force commit - do not tests a file list')
2770 @cmdln.option('--skip-validation', default=False, action="store_true",
2771 help='Skip the source validation')
2772 @cmdln.option('--verbose-validation', default=False, action="store_true",
2773 help='Run the source validation with verbose informations')
2774 def do_commit(self, subcmd, opts, *args):
2775 """${cmd_name}: Upload content to the repository server
2777 Upload content which is changed in your working copy, to the repository
2780 Optionally checks the state of a working copy, if found a file with
2781 unknown state, it requests an user input:
2782 * skip - don't change anything, just move to another file
2783 * remove - remove a file from dir
2784 * edit file list - edit filelist using EDITOR
2785 * commit - don't check anything and commit package
2786 * abort - abort commit - this is default value
2787 This can be supressed by check_filelist config item, or -f/--force
2788 command line option.
2791 osc ci # current dir
2793 osc ci file1 file2 ...
2799 args = parseargs(args)
2801 validators = conf.config['source_validator_directory']
2802 verbose_validation = None
2803 if opts.skip_validation:
2805 elif not os.path.exists(validators):
2806 print "WARNING: validator directory", validators, "configured, but not existing. Skipping ..."
2808 if opts.verbose_validation:
2809 verbose_validation = 1
2816 msg = open(opts.file).read()
2818 sys.exit('could not open file \'%s\'.' % opts.file)
2821 for arg in arg_list:
2822 if conf.config['do_package_tracking'] and is_project_dir(arg):
2824 msg = edit_message()
2826 Project(arg).commit(msg=msg, validators=validators, verbose_validation=verbose_validation)
2827 except oscerr.RuntimeError, e:
2828 print >>sys.stderr, "ERROR: source_validator failed", e
2832 pacs = findpacs(args)
2834 if conf.config['check_filelist'] and not opts.force:
2835 check_filelist_before_commit(pacs)
2838 template = store_read_file(os.path.abspath('.'), '_commit_msg')
2839 # open editor for commit message
2840 # but first, produce status and diff to append to the template
2844 changed = getStatus([pac], quiet=True)
2847 diffs += ['\nDiff for working copy: %s' % pac.dir]
2848 diffs += make_diff(pac, 0)
2849 lines.extend(get_commit_message_template(pac))
2850 if template == None:
2851 template='\n'.join(lines)
2852 # if footer is empty, there is nothing to commit, and no edit needed.
2854 msg = edit_message(footer='\n'.join(footer), template=template)
2857 store_write_string(os.path.abspath('.'), '_commit_msg', msg)
2859 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2861 if conf.config['do_package_tracking'] and len(pacs) > 0:
2865 # it is possible to commit packages from different projects at the same
2866 # time: iterate over all pacs and put each pac to the right project in the dict
2868 path = os.path.normpath(os.path.join(pac.dir, os.pardir))
2869 if is_project_dir(path):
2870 pac_path = os.path.basename(os.path.normpath(pac.absdir))
2871 prj_paths.setdefault(path, []).append(pac_path)
2872 files[pac_path] = pac.todo
2874 single_paths.append(pac.dir)
2875 for prj, packages in prj_paths.iteritems():
2877 Project(prj).commit(tuple(packages), msg=msg, files=files, validators=validators, verbose_validation=verbose_validation)
2878 except oscerr.RuntimeError, e:
2879 print >>sys.stderr, "ERROR: source_validator failed", e
2881 for pac in single_paths:
2883 Package(pac).commit(msg, validators=validators, verbose_validation=verbose_validation)
2884 except oscerr.RuntimeError, e:
2885 print >>sys.stderr, "ERROR: source_validator failed", e
2889 p.commit(msg, validators=validators, verbose_validation=verbose_validation)
2891 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2893 @cmdln.option('-r', '--revision', metavar='REV',
2894 help='update to specified revision (this option will be ignored '
2895 'if you are going to update the complete project or more than '
2897 @cmdln.option('-u', '--unexpand-link', action='store_true',
2898 help='if a package is an expanded link, update to the raw _link file')
2899 @cmdln.option('-e', '--expand-link', action='store_true',
2900 help='if a package is a link, update to the expanded sources')
2901 @cmdln.option('-s', '--source-service-files', action='store_true',
2902 help='Use server side generated sources instead of local generation.' )
2903 @cmdln.option('-l', '--limit-size', metavar='limit_size',
2904 help='Skip all files with a given size')
2906 def do_update(self, subcmd, opts, *args):
2907 """${cmd_name}: Update a working copy
2912 If the current working directory is a package, update it.
2913 If the directory is a project directory, update all contained
2914 packages, AND check out newly added packages.
2916 To update only checked out packages, without checking out new
2917 ones, you might want to use "osc up *" from within the project
2921 Update the packages specified by the path argument(s)
2923 When --expand-link is used with source link packages, the expanded
2924 sources will be checked out. Without this option, the _link file and
2925 patches will be checked out. The option --unexpand-link can be used to
2926 switch back to the "raw" source with a _link file plus patch(es).
2932 if (opts.expand_link and opts.unexpand_link) \
2933 or (opts.expand_link and opts.revision) \
2934 or (opts.unexpand_link and opts.revision):
2935 raise oscerr.WrongOptions('Sorry, the options --expand-link, --unexpand-link and '
2936 '--revision are mutually exclusive.')
2938 if opts.source_service_files: service_files = True
2939 else: service_files = False
2941 args = parseargs(args)
2944 for arg in arg_list:
2945 if is_project_dir(arg):
2946 prj = Project(arg, progress_obj=self.download_progress)
2948 if conf.config['do_package_tracking']:
2949 prj.update(expand_link=opts.expand_link,
2950 unexpand_link=opts.unexpand_link)
2953 # if not tracking package, and 'update' is run inside a project dir,
2954 # it should do the following:
2955 # (a) update all packages
2956 args += prj.pacs_have
2957 # (b) fetch new packages
2958 prj.checkout_missing_pacs(expand_link = not opts.unexpand_link)
2960 print_request_list(prj.apiurl, prj.name)
2963 pacs = findpacs(args, progress_obj=self.download_progress)
2965 if opts.revision and len(args) == 1:
2966 rev, dummy = parseRevisionOption(opts.revision)
2967 if not checkRevision(pacs[0].prjname, pacs[0].name, rev, pacs[0].apiurl):
2968 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2975 print 'Updating %s' % p.name
2977 # FIXME: ugly workaround for #399247
2978 if opts.expand_link or opts.unexpand_link:
2979 if [ i for i in p.filenamelist+p.filenamelist_unvers if p.status(i) != ' ' and p.status(i) != '?']:
2980 print >>sys.stderr, 'osc: cannot expand/unexpand because your working ' \
2981 'copy has local modifications.\nPlease revert/commit them ' \
2986 if opts.expand_link and p.islink() and not p.isexpanded():
2987 if p.haslinkerror():
2989 rev = p.show_upstream_xsrcmd5()
2991 rev = p.show_upstream_xsrcmd5(linkrev="base")
2994 p.update(rev, service_files, opts.limit_size)
2995 rev = p.linkinfo.xsrcmd5
2996 print 'Expanding to rev', rev
2997 elif opts.unexpand_link and p.islink() and p.isexpanded():
2998 print 'Unexpanding to rev', p.linkinfo.lsrcmd5
2999 p.update(rev, service_files, opts.limit_size)
3000 rev = p.linkinfo.lsrcmd5
3001 elif p.islink() and p.isexpanded():
3002 rev = p.latest_rev()
3004 p.update(rev, service_files, opts.limit_size)
3005 if opts.unexpand_link:
3008 print_request_list(p.apiurl, p.prjname, p.name)
3011 @cmdln.option('-f', '--force', action='store_true',
3012 help='forces removal of entire package and its files')
3015 @cmdln.alias('remove')
3016 def do_delete(self, subcmd, opts, *args):
3017 """${cmd_name}: Mark files or package directories to be deleted upon the next 'checkin'
3020 cd .../PROJECT/PACKAGE
3021 osc delete FILE [...]
3023 osc delete PACKAGE [...]
3025 This command works on check out copies. Use "rdelete" for working on server
3026 side only. This is needed for removing the entire project.
3028 As a safety measure, projects must be empty (i.e., you need to delete all
3031 If you are sure that you want to remove a package and all
3032 its files use \'--force\' switch. Sometimes this also works without --force.
3038 raise oscerr.WrongArgs('Missing argument.\n\n' \
3039 + self.get_cmd_help('delete'))
3041 args = parseargs(args)
3042 # check if args contains a package which was removed by
3043 # a non-osc command and mark it with the 'D'-state
3046 if not os.path.exists(i):
3047 prj_dir, pac_dir = getPrjPacPaths(i)
3048 if is_project_dir(prj_dir):
3049 prj = Project(prj_dir, False)
3050 if i in prj.pacs_broken:
3051 if prj.get_state(i) != 'A':
3052 prj.set_state(pac_dir, 'D')
3054 prj.del_package_node(i)
3055 print statfrmt('D', getTransActPath(i))
3057 prj.write_packages()
3058 pacs = findpacs(args)
3062 prj_dir, pac_dir = getPrjPacPaths(p.absdir)
3063 if is_project_dir(prj_dir):