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 from optparse import SUPPRESS_HELP
13 MAN_HEADER = r""".TH %(ucname)s "1" "%(date)s" "%(name)s %(version)s" "User Commands"
15 %(name)s \- openSUSE build service command-line tool.
18 [\fIGLOBALOPTS\fR] \fISUBCOMMAND \fR[\fIOPTS\fR] [\fIARGS\fR...]
23 openSUSE build service command-line tool.
27 Type 'osc help <subcommand>' for more detailed help on a specific subcommand.
29 For additional information, see
30 * http://en.opensuse.org/openSUSE:Build_Service_Tutorial
31 * http://en.opensuse.org/openSUSE:OSC
33 You can modify osc commands, or roll you own, via the plugin API:
34 * http://en.opensuse.org/openSUSE:OSC_plugins
36 osc was written by several authors. This man page is automatically generated.
39 class Osc(cmdln.Cmdln):
40 """Usage: osc [GLOBALOPTS] SUBCOMMAND [OPTS] [ARGS...]
41 or: osc help SUBCOMMAND
43 openSUSE build service command-line tool.
44 Type 'osc help <subcommand>' for help on a specific subcommand.
49 For additional information, see
50 * http://en.opensuse.org/openSUSE:Build_Service_Tutorial
51 * http://en.opensuse.org/openSUSE:OSC
53 You can modify osc commands, or roll you own, via the plugin API:
54 * http://en.opensuse.org/openSUSE:OSC_plugins
59 man_header = MAN_HEADER
60 man_footer = MAN_FOOTER
62 def __init__(self, *args, **kwargs):
63 cmdln.Cmdln.__init__(self, *args, **kwargs)
64 cmdln.Cmdln.do_help.aliases.append('h')
66 def get_version(self):
67 return get_osc_version()
69 def get_optparser(self):
70 """this is the parser for "global" options (not specific to subcommand)"""
72 optparser = cmdln.CmdlnOptionParser(self, version=get_osc_version())
73 optparser.add_option('--debugger', action='store_true',
74 help='jump into the debugger before executing anything')
75 optparser.add_option('--post-mortem', action='store_true',
76 help='jump into the debugger in case of errors')
77 optparser.add_option('-t', '--traceback', action='store_true',
78 help='print call trace in case of errors')
79 optparser.add_option('-H', '--http-debug', action='store_true',
80 help='debug HTTP traffic (filters some headers)')
81 optparser.add_option('--http-full-debug', action='store_true',
82 help='debug HTTP traffic (filters no headers)'),
83 optparser.add_option('-d', '--debug', action='store_true',
84 help='print info useful for debugging')
85 optparser.add_option('-A', '--apiurl', dest='apiurl',
87 help='specify URL to access API server at or an alias')
88 optparser.add_option('-c', '--config', dest='conffile',
90 help='specify alternate configuration file')
91 optparser.add_option('--no-keyring', action='store_true',
92 help='disable usage of desktop keyring system')
93 optparser.add_option('--no-gnome-keyring', action='store_true',
94 help='disable usage of GNOME Keyring')
95 optparser.add_option('-v', '--verbose', dest='verbose', action='count', default=0,
96 help='increase verbosity')
97 optparser.add_option('-q', '--quiet', dest='verbose', action='store_const', const=-1,
98 help='be quiet, not verbose')
102 def postoptparse(self, try_again = True):
103 """merge commandline options into the config"""
105 conf.get_config(override_conffile = self.options.conffile,
106 override_apiurl = self.options.apiurl,
107 override_debug = self.options.debug,
108 override_http_debug = self.options.http_debug,
109 override_http_full_debug = self.options.http_full_debug,
110 override_traceback = self.options.traceback,
111 override_post_mortem = self.options.post_mortem,
112 override_no_keyring = self.options.no_keyring,
113 override_no_gnome_keyring = self.options.no_gnome_keyring,
114 override_verbose = self.options.verbose)
115 except oscerr.NoConfigfile, e:
116 print >>sys.stderr, e.msg
117 print >>sys.stderr, 'Creating osc configuration file %s ...' % e.file
120 config['user'] = raw_input('Username: ')
121 config['pass'] = getpass.getpass()
122 if self.options.no_keyring:
123 config['use_keyring'] = '0'
124 if self.options.no_gnome_keyring:
125 config['gnome_keyring'] = '0'
126 if self.options.apiurl:
127 config['apiurl'] = self.options.apiurl
129 conf.write_initial_config(e.file, config)
130 print >>sys.stderr, 'done'
131 if try_again: self.postoptparse(try_again = False)
132 except oscerr.ConfigMissingApiurl, e:
133 print >>sys.stderr, e.msg
135 user = raw_input('Username: ')
136 passwd = getpass.getpass()
137 conf.add_section(e.file, e.url, user, passwd)
138 if try_again: self.postoptparse(try_again = False)
140 self.options.verbose = conf.config['verbose']
141 self.download_progress = None
142 if conf.config.get('show_download_progress', False):
143 from meter import TextMeter
144 self.download_progress = TextMeter(hide_finished=True)
147 def get_cmd_help(self, cmdname):
148 doc = self._get_cmd_handler(cmdname).__doc__
149 doc = self._help_reindent(doc)
150 doc = self._help_preprocess(doc, cmdname)
151 doc = doc.rstrip() + '\n' # trim down trailing space
152 return self._str(doc)
154 def get_api_url(self):
155 localdir = os.getcwd()
156 if (is_package_dir(localdir) or is_project_dir(localdir)) and not self.options.apiurl:
157 return store_read_apiurl(os.curdir)
159 return conf.config['apiurl']
161 # overridden from class Cmdln() to use config variables in help texts
162 def _help_preprocess(self, help, cmdname):
163 help_msg = cmdln.Cmdln._help_preprocess(self, help, cmdname)
164 return help_msg % conf.config
167 def do_init(self, subcmd, opts, project, package=None):
168 """${cmd_name}: Initialize a directory as working copy
170 Initialize an existing directory to be a working copy of an
171 (already existing) buildservice project/package.
173 (This is the same as checking out a package and then copying sources
174 into the directory. It does NOT create a new package. To create a
175 package, use 'osc meta pkg ... ...')
177 You wouldn't normally use this command.
179 To get a working copy of a package (e.g. for building it or working on
180 it, you would normally use the checkout command. Use "osc help
181 checkout" to get help for it.
189 apiurl = self.get_api_url()
192 Project.init_project(apiurl, os.curdir, project, conf.config['do_package_tracking'])
193 print 'Initializing %s (Project: %s)' % (os.curdir, project)
195 Package.init_package(apiurl, project, package, os.curdir)
196 store_write_string(os.curdir, '_files', show_files_meta(apiurl, project, package) + '\n')
197 print 'Initializing %s (Project: %s, Package: %s)' % (os.curdir, project, package)
203 @cmdln.option('-a', '--arch', metavar='ARCH',
204 help='specify architecture (only for binaries)')
205 @cmdln.option('-r', '--repo', metavar='REPO',
206 help='specify repository (only for binaries)')
207 @cmdln.option('-b', '--binaries', action='store_true',
208 help='list built binaries instead of sources')
209 @cmdln.option('-R', '--revision', metavar='REVISION',
210 help='specify revision (only for sources)')
211 @cmdln.option('-e', '--expand', action='store_true',
212 help='expand linked package (only for sources)')
213 @cmdln.option('-u', '--unexpand', action='store_true',
214 help='always work with unexpanded (source) packages')
215 @cmdln.option('-v', '--verbose', action='store_true',
216 help='print extra information')
217 @cmdln.option('-l', '--long', action='store_true', dest='verbose',
218 help='print extra information')
219 @cmdln.option('-D', '--deleted', action='store_true',
220 help='show only the former deleted projects or packages')
221 def do_list(self, subcmd, opts, *args):
222 """${cmd_name}: List sources or binaries on the server
224 Examples for listing sources:
225 ls # list all projects
226 ls PROJECT # list packages in a project
227 ls PROJECT PACKAGE # list source files of package of a project
228 ls PROJECT PACKAGE <file> # list <file> if this file exists
229 ls -v PROJECT PACKAGE # verbosely list source files of package
230 ls -l PROJECT PACKAGE # verbosely list source files of package
231 ll PROJECT PACKAGE # verbosely list source files of package
232 LL PROJECT PACKAGE # verbosely list source files of expanded link
234 With --verbose, the following fields will be shown for each item:
236 Revision number of the last commit
238 Date and time of the last commit
240 Examples for listing binaries:
241 ls -b PROJECT # list all binaries of a project
242 ls -b PROJECT -a ARCH # list ARCH binaries of a project
243 ls -b PROJECT -r REPO # list binaries in REPO
244 ls -b PROJECT PACKAGE REPO ARCH
247 ${cmd_name} [PROJECT [PACKAGE]]
248 ${cmd_name} -b [PROJECT [PACKAGE [REPO [ARCH]]]]
252 args = slash_split(args)
255 if subcmd == 'lL' or subcmd == 'LL':
267 raise oscerr.WrongArgs("Too many arguments when listing deleted packages")
270 raise oscerr.WrongArgs("Too many arguments when listing deleted packages")
273 if opts.repo != args[2]:
274 raise oscerr.WrongArgs("conflicting repos specified ('%s' vs '%s')"%(opts.repo, args[2]))
281 if not opts.binaries:
282 raise oscerr.WrongArgs('Too many arguments')
284 if opts.arch != args[3]:
285 raise oscerr.WrongArgs("conflicting archs specified ('%s' vs '%s')"%(opts.arch, args[3]))
290 if opts.binaries and opts.expand:
291 raise oscerr.WrongOptions('Sorry, --binaries and --expand are mutual exclusive.')
293 apiurl = self.get_api_url()
297 # ls -b toplevel doesn't make sense, so use info from
298 # current dir if available
301 if is_project_dir(cwd):
302 project = store_read_project(cwd)
303 elif is_package_dir(cwd):
304 project = store_read_project(cwd)
305 package = store_read_package(cwd)
308 raise oscerr.WrongArgs('There are no binaries to list above project level.')
310 raise oscerr.WrongOptions('Sorry, the --revision option is not supported for binaries.')
314 if opts.repo and opts.arch:
315 repos.append(Repo(opts.repo, opts.arch))
316 elif opts.repo and not opts.arch:
317 repos = [repo for repo in get_repos_of_project(apiurl, project) if repo.name == opts.repo]
318 elif opts.arch and not opts.repo:
319 repos = [repo for repo in get_repos_of_project(apiurl, project) if repo.arch == opts.arch]
321 repos = get_repos_of_project(apiurl, project)
325 results.append((repo, get_binarylist(apiurl, project, repo.name, repo.arch, package=package, verbose=opts.verbose)))
327 for result in results:
330 print '%s/%s' % (result[0].name, result[0].arch)
335 print "%9d %s %-40s" % (f.size, shorttime(f.mtime), f.name)
341 elif not opts.binaries:
343 print '\n'.join(meta_get_project_list(apiurl, opts.deleted))
347 if self.options.verbose:
348 print >>sys.stderr, 'Sorry, the --verbose option is not implemented for projects.'
350 raise oscerr.WrongOptions('Sorry, the --expand option is not implemented for projects.')
352 print '\n'.join(meta_get_packagelist(apiurl, project, opts.deleted))
354 elif len(args) == 2 or len(args) == 3:
356 print_not_found = True
359 l = meta_get_filelist(apiurl,
362 verbose=opts.verbose,
365 link_seen = '_link' in l
367 out = [ '%s %7s %9d %s %s' % (i.md5, i.rev, i.size, shorttime(i.mtime), i.name) \
368 for i in l if not fname or fname == i.name ]
370 print_not_found = False
375 print_not_found = False
378 if opts.expand or opts.unexpand or not link_seen: break
379 m = show_files_meta(apiurl, project, package)
381 li.read(ET.fromstring(''.join(m)).find('linkinfo'))
383 raise oscerr.LinkExpandError(project, package, li.error)
384 project, package, rev = li.project, li.package, li.rev
386 print '# -> %s %s (%s)' % (project, package, rev)
388 print '# -> %s %s (latest)' % (project, package)
390 if fname and print_not_found:
391 print 'file \'%s\' does not exist' % fname
394 @cmdln.option('-f', '--force', action='store_true',
395 help='force generation of new patchinfo file')
396 @cmdln.option('--force-update', action='store_true',
397 help='drops away collected packages from an already built patch and let it collect again')
398 def do_patchinfo(self, subcmd, opts, *args):
399 """${cmd_name}: Generate and edit a patchinfo file.
401 A patchinfo file describes the packages for an update and the kind of
406 osc patchinfo PATCH_NAME
410 project_dir = localdir = os.getcwd()
411 if is_project_dir(localdir):
412 project = store_read_project(localdir)
413 apiurl = self.get_api_url()
415 sys.exit('This command must be called in a checked out project.')
417 for p in meta_get_packagelist(apiurl, project):
418 if p.startswith("_patchinfo:"):
421 if opts.force or not patchinfo:
422 print "Creating initial patchinfo..."
423 query='cmd=createpatchinfo'
425 query += "&name=" + args[0]
426 url = makeurl(apiurl, ['source', project], query=query)
428 for p in meta_get_packagelist(apiurl, project):
429 if p.startswith("_patchinfo:"):
432 if not os.path.exists(project_dir + "/" + patchinfo):
433 checkout_package(apiurl, project, patchinfo, prj_dir=project_dir)
435 filename = project_dir + "/" + patchinfo + "/_patchinfo"
439 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
440 help='affect only a given attribute')
441 @cmdln.option('--attribute-defaults', action='store_true',
442 help='include defined attribute defaults')
443 @cmdln.option('--attribute-project', action='store_true',
444 help='include project values, if missing in packages ')
445 @cmdln.option('-F', '--file', metavar='FILE',
446 help='read metadata from FILE, instead of opening an editor. '
447 '\'-\' denotes standard input. ')
448 @cmdln.option('-e', '--edit', action='store_true',
449 help='edit metadata')
450 @cmdln.option('-c', '--create', action='store_true',
451 help='create attribute without values')
452 @cmdln.option('-s', '--set', metavar='ATTRIBUTE_VALUES',
453 help='set attribute values')
454 @cmdln.option('--delete', action='store_true',
455 help='delete a pattern or attribute')
456 def do_meta(self, subcmd, opts, *args):
457 """${cmd_name}: Show meta information, or edit it
459 Show or edit build service metadata of type <prj|pkg|prjconf|user|pattern>.
461 This command displays metadata on buildservice objects like projects,
462 packages, or users. The type of metadata is specified by the word after
463 "meta", like e.g. "meta prj".
465 prj denotes metadata of a buildservice project.
466 prjconf denotes the (build) configuration of a project.
467 pkg denotes metadata of a buildservice package.
468 user denotes the metadata of a user.
469 pattern denotes installation patterns defined for a project.
471 To list patterns, use 'osc meta pattern PRJ'. An additional argument
472 will be the pattern file to view or edit.
474 With the --edit switch, the metadata can be edited. Per default, osc
475 opens the program specified by the environmental variable EDITOR with a
476 temporary file. Alternatively, content to be saved can be supplied via
477 the --file switch. If the argument is '-', input is taken from stdin:
478 osc meta prjconf home:user | sed ... | osc meta prjconf home:user -F -
480 When trying to edit a non-existing resource, it is created implicitly.
486 osc meta pkg PRJ PKG -e
487 osc meta attribute PRJ [PKG [SUBPACKAGE]] [--attribute ATTRIBUTE] [--create|--delete|--set [value_list]]
490 osc meta <prj|pkg|prjconf|user|pattern|attribute> ARGS...
491 osc meta <prj|pkg|prjconf|user|pattern|attribute> -e|--edit ARGS...
492 osc meta <prj|pkg|prjconf|user|pattern|attribute> -F|--file ARGS...
493 osc meta pattern --delete PRJ PATTERN
497 args = slash_split(args)
499 if not args or args[0] not in metatypes.keys():
500 raise oscerr.WrongArgs('Unknown meta type. Choose one of %s.' \
501 % ', '.join(metatypes))
506 apiurl = self.get_api_url()
509 min_args, max_args = 0, 2
510 elif cmd in ['pattern']:
511 min_args, max_args = 1, 2
512 elif cmd in ['attribute']:
513 min_args, max_args = 1, 3
514 elif cmd in ['prj', 'prjconf']:
515 min_args, max_args = 0, 1
517 min_args, max_args = 1, 1
519 if len(args) < min_args:
520 raise oscerr.WrongArgs('Too few arguments.')
521 if len(args) > max_args:
522 raise oscerr.WrongArgs('Too many arguments.')
526 if cmd in ['pkg', 'prj', 'prjconf' ]:
528 project = store_read_project(os.curdir)
534 package = store_read_package(os.curdir)
538 elif cmd == 'attribute':
544 if opts.attribute_project:
545 raise oscerr.WrongOptions('--attribute-project works only when also a package is given')
550 attributepath.append('source')
551 attributepath.append(project)
553 attributepath.append(package)
555 attributepath.append(subpackage)
556 attributepath.append('_attribute')
559 elif cmd == 'pattern':
565 # enforce pattern argument if needed
566 if opts.edit or opts.file:
567 raise oscerr.WrongArgs('A pattern file argument is required.')
570 if not opts.edit and not opts.file and not opts.delete and not opts.create and not opts.set:
572 sys.stdout.write(''.join(show_project_meta(apiurl, project)))
574 sys.stdout.write(''.join(show_package_meta(apiurl, project, package)))
575 elif cmd == 'attribute':
576 sys.stdout.write(''.join(show_attribute_meta(apiurl, project, package, subpackage, opts.attribute, opts.attribute_defaults, opts.attribute_project)))
577 elif cmd == 'prjconf':
578 sys.stdout.write(''.join(show_project_conf(apiurl, project)))
580 r = get_user_meta(apiurl, user)
582 sys.stdout.write(''.join(r))
583 elif cmd == 'pattern':
585 r = show_pattern_meta(apiurl, project, pattern)
587 sys.stdout.write(''.join(r))
589 r = show_pattern_metalist(apiurl, project)
591 sys.stdout.write('\n'.join(r) + '\n')
594 if opts.edit and not opts.file:
596 edit_meta(metatype='prj',
598 path_args=quote_plus(project),
602 'user': conf.get_apiurl_usr(apiurl)}))
604 edit_meta(metatype='pkg',
606 path_args=(quote_plus(project), quote_plus(package)),
610 'user': conf.get_apiurl_usr(apiurl)}))
611 elif cmd == 'prjconf':
612 edit_meta(metatype='prjconf',
614 path_args=quote_plus(project),
618 edit_meta(metatype='user',
620 path_args=(quote_plus(user)),
622 template_args=({'user': user}))
623 elif cmd == 'pattern':
624 edit_meta(metatype='pattern',
626 path_args=(project, pattern),
630 # create attribute entry
631 if (opts.create or opts.set) and cmd == 'attribute':
632 if not opts.attribute:
633 raise oscerr.WrongOptions('no attribute given to create')
636 opts.set = opts.set.replace('&', '&').replace('<', '<').replace('>', '>')
637 for i in opts.set.split(','):
638 values += '<value>%s</value>' % i
639 aname = opts.attribute.split(":")
640 d = '<attributes><attribute namespace=\'%s\' name=\'%s\' >%s</attribute></attributes>' % (aname[0], aname[1], values)
641 url = makeurl(apiurl, attributepath)
642 for data in streamfile(url, http_POST, data=d):
643 sys.stdout.write(data)
652 f = open(opts.file).read()
654 sys.exit('could not open file \'%s\'.' % opts.file)
657 edit_meta(metatype='prj',
661 path_args=quote_plus(project))
663 edit_meta(metatype='pkg',
667 path_args=(quote_plus(project), quote_plus(package)))
668 elif cmd == 'prjconf':
669 edit_meta(metatype='prjconf',
673 path_args=quote_plus(project))
675 edit_meta(metatype='user',
679 path_args=(quote_plus(user)))
680 elif cmd == 'pattern':
681 edit_meta(metatype='pattern',
685 path_args=(project, pattern))
690 path = metatypes[cmd]['path']
692 path = path % (project, pattern)
693 u = makeurl(apiurl, [path])
695 elif cmd == 'attribute':
696 if not opts.attribute:
697 raise oscerr.WrongOptions('no attribute given to create')
698 attributepath.append(opts.attribute)
699 u = makeurl(apiurl, attributepath)
700 for data in streamfile(u, http_DELETE):
701 sys.stdout.write(data)
703 raise oscerr.WrongOptions('The --delete switch is only for pattern metadata or attributes.')
706 @cmdln.option('-m', '--message', metavar='TEXT',
707 help='specify message TEXT')
708 @cmdln.option('-r', '--revision', metavar='REV',
709 help='specify a certain source revision ID (the md5 sum) for the source package')
710 @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
711 help='Superseding another request by this one')
712 @cmdln.option('--nodevelproject', action='store_true',
713 help='do not follow a defined devel project ' \
714 '(primary project where a package is developed)')
715 @cmdln.option('--cleanup', action='store_true',
716 help='remove package if submission gets accepted (default for home:<id>:branch projects)')
717 @cmdln.option('--no-cleanup', action='store_true',
718 help='never remove source package on accept, but update its content')
719 @cmdln.option('--no-update', action='store_true',
720 help='never touch source package on accept (will break source links)')
721 @cmdln.option('-d', '--diff', action='store_true',
722 help='show diff only instead of creating the actual request')
723 @cmdln.option('--yes', action='store_true',
724 help='proceed without asking.')
726 @cmdln.alias("submitreq")
727 @cmdln.alias("submitpac")
728 def do_submitrequest(self, subcmd, opts, *args):
729 """${cmd_name}: Create request to submit source into another Project
731 [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration for information
734 See the "request" command for showing and modifing existing requests.
737 osc submitreq [OPTIONS]
738 osc submitreq [OPTIONS] DESTPRJ [DESTPKG]
739 osc submitreq [OPTIONS] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
741 osc submitpac ... is a shorthand for osc submitreq --cleanup ...
746 if opts.cleanup and opts.no_cleanup:
747 raise oscerr.WrongOptions('\'--cleanup\' and \'--no-cleanup\' are mutually exclusive')
749 src_update = conf.config['submitrequest_on_accept_action'] or None
750 # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
752 if subcmd == 'submitpac' and not opts.no_cleanup:
756 src_update = "cleanup"
757 elif opts.no_cleanup:
758 src_update = "update"
760 src_update = "noupdate"
762 args = slash_split(args)
764 # remove this block later again
765 oldcmds = ['create', 'list', 'log', 'show', 'decline', 'accept', 'delete', 'revoke']
766 if args and args[0] in oldcmds:
767 print "************************************************************************"
768 print "* WARNING: It looks that you are using this command with a *"
769 print "* deprecated syntax. *"
770 print "* Please run \"osc sr --help\" and \"osc rq --help\" *"
771 print "* to see the new syntax. *"
772 print "************************************************************************"
773 if args[0] == 'create':
779 raise oscerr.WrongArgs('Too many arguments.')
781 if len(args) > 0 and len(args) <= 2 and is_project_dir(os.getcwd()):
782 sys.exit('osc submitrequest from project directory is only working without target specs and for source linked files\n')
784 apiurl = self.get_api_url()
786 if len(args) == 0 and is_project_dir(os.getcwd()):
788 # submit requests for multiple packages are currently handled via multiple requests
789 # They could be also one request with multiple actions, but that avoids to accepts parts of it.
790 project = store_read_project(os.curdir)
796 # loop via all packages for checking their state
797 for p in meta_get_packagelist(apiurl, project):
798 if p.startswith("_patchinfo:"):
801 # get _link info from server, that knows about the local state ...
802 u = makeurl(apiurl, ['source', project, p])
804 root = ET.parse(f).getroot()
805 linkinfo = root.find('linkinfo')
807 print "Package ", p, " is not a source link."
808 sys.exit("This is currently not supported.")
809 if linkinfo.get('error'):
810 print "Package ", p, " is a broken source link."
811 sys.exit("Please fix this first")
812 t = linkinfo.get('project')
814 if len(root.findall('entry')) > 1: # This is not really correct, but should work mostly
815 # Real fix is to ask the api if sources are modificated
816 # but there is no such call yet.
817 targetprojects.append(t)
819 print "Submitting package ", p
821 print " Skipping package ", p
823 print "Skipping package ", p, " since it is a source link pointing inside the project."
825 # was this project created by clone request ?
826 u = makeurl(apiurl, ['source', project, '_attribute', 'OBS:RequestCloned'])
828 root = ET.parse(f).getroot()
829 value = root.findtext('attribute/value')
836 print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
837 repl = raw_input('\nEverything fine? Can we create the requests ? (y/n) ')
838 if repl.lower() != 'y':
839 print >>sys.stderr, 'Aborted...'
840 raise oscerr.UserAbort()
843 # loop via all packages to do the action
845 result = create_submit_request(apiurl, project, p)
848 sys.exit("submit request creation failed")
849 sr_ids.append(result)
851 # create submit requests for all found patchinfos
855 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
858 for t in targetprojects:
859 s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \
860 (project, p, t, p, options_block)
864 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
865 (actionxml, cgi.escape(opts.message or ""))
866 u = makeurl(apiurl, ['request'], query='cmd=create')
867 f = http_POST(u, data=xml)
869 root = ET.parse(f).getroot()
870 sr_ids.append(root.get('id'))
872 print "Requests created: ",
878 print '\n\nThere are already following submit request: %s.' % \
879 ', '.join([str(i) for i in myreqs ])
880 repl = raw_input('\nSupersede the old requests? (y/n) ')
881 if repl.lower() == 'y':
883 change_request_state(apiurl, str(req), 'superseded',
884 'superseded by %s' % result, result)
886 sys.exit('Successfully finished')
889 # try using the working copy at hand
890 p = findpacs(os.curdir)[0]
891 src_project = p.prjname
894 if len(args) == 0 and p.islink():
895 dst_project = p.linkinfo.project
896 dst_package = p.linkinfo.package
898 dst_project = args[0]
900 dst_package = args[1]
902 dst_package = src_package
904 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
905 'Please provide it the target via commandline arguments.' % p.name)
907 modified = [i for i in p.filenamelist if not p.status(i) in (' ', '?', 'S')]
908 if len(modified) > 0:
909 print 'Your working copy has local modifications.'
910 repl = raw_input('Proceed without committing the local changes? (y|N) ')
912 raise oscerr.UserAbort()
914 # get the arguments from the commandline
915 src_project, src_package, dst_project = args[0:3]
917 dst_package = args[3]
919 dst_package = src_package
921 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
922 + self.get_cmd_help('request'))
924 if not opts.nodevelproject:
927 devloc = show_develproject(apiurl, dst_project, dst_package)
928 except urllib2.HTTPError:
929 print >>sys.stderr, """\
930 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
931 % (dst_project, dst_package)
935 dst_project != devloc and \
936 src_project != devloc:
938 A different project, %s, is defined as the place where development
939 of the package %s primarily takes place.
940 Please submit there instead, or use --nodevelproject to force direct submission.""" \
941 % (devloc, dst_package)
946 if opts.diff or not opts.message:
948 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
949 rdiff += server_diff(apiurl,
950 dst_project, dst_package, opts.revision,
951 src_project, src_package, None, True)
959 # Are there already requests to this package ?
960 reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit')
961 user = conf.get_apiurl_usr(apiurl)
962 myreqs = [ i for i in reqs if i.state.who == user ]
966 print 'There are already following submit request: %s.' % \
967 ', '.join([str(i.reqid) for i in myreqs ])
968 repl = raw_input('Supersede the old requests? (y/n/c) ')
969 if repl.lower() == 'c':
970 print >>sys.stderr, 'Aborting'
971 raise oscerr.UserAbort()
976 changes_re = re.compile(r'^--- .*\.changes ')
977 for line in rdiff.split('\n'):
978 if line.startswith('--- '):
979 if changes_re.match(line):
984 difflines.append(line)
985 opts.message = edit_message(footer=rdiff, template='\n'.join(parse_diff_for_commit_message('\n'.join(difflines))))
987 result = create_submit_request(apiurl,
988 src_project, src_package,
989 dst_project, dst_package,
990 opts.message, orev=opts.revision, src_update=src_update)
991 if repl.lower() == 'y':
993 change_request_state(apiurl, str(req.reqid), 'superseded',
994 'superseded by %s' % result, result)
997 change_request_state(apiurl, opts.supersede, 'superseded',
998 opts.message or '', result)
1000 print 'created request id', result
1002 def _actionparser(self, opt_str, value, parser):
1004 if not hasattr(parser.values, 'actiondata'):
1005 setattr(parser.values, 'actiondata', [])
1006 if parser.values.actions == None:
1007 parser.values.actions = []
1009 rargs = parser.rargs
1012 if ((arg[:2] == "--" and len(arg) > 2) or
1013 (arg[:1] == "-" and len(arg) > 1 and arg[1] != "-")):
1019 parser.values.actions.append(value[0])
1021 parser.values.actiondata.append(value)
1023 def _submit_request(self, args, opts, options_block):
1025 apiurl = self.get_api_url()
1026 if len(args) == 0 and is_project_dir(os.getcwd()):
1027 # submit requests for multiple packages are currently handled via multiple requests
1028 # They could be also one request with multiple actions, but that avoids to accepts parts of it.
1029 project = store_read_project(os.curdir)
1035 # loop via all packages for checking their state
1036 for p in meta_get_packagelist(apiurl, project):
1037 if p.startswith("_patchinfo:"):
1040 # get _link info from server, that knows about the local state ...
1041 u = makeurl(apiurl, ['source', project, p])
1043 root = ET.parse(f).getroot()
1044 linkinfo = root.find('linkinfo')
1045 if linkinfo == None:
1046 print "Package ", p, " is not a source link."
1047 sys.exit("This is currently not supported.")
1048 if linkinfo.get('error'):
1049 print "Package ", p, " is a broken source link."
1050 sys.exit("Please fix this first")
1051 t = linkinfo.get('project')
1055 rdiff = server_diff(apiurl, t, p, opts.revision, project, p, None, True)
1060 targetprojects.append(t)
1062 rdiffmsg.append("old: %s/%s\nnew: %s/%s\n%s" %(t, p, project, p,rdiff))
1064 print "Skipping package ", p, " since it has no difference with the target package."
1066 print "Skipping package ", p, " since it is a source link pointing inside the project."
1068 print ''.join(rdiffmsg)
1073 print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
1074 print "\nEverything fine? Can we create the requests ? [y/n]"
1075 if sys.stdin.read(1) != "y":
1076 sys.exit("Aborted...")
1078 # loop via all packages to do the action
1080 s = """<action type="submit"> <source project="%s" package="%s" rev="%s"/> <target project="%s" package="%s"/> %s </action>""" % \
1081 (project, p, opts.revision or show_upstream_rev(apiurl, project, p), t, p, options_block)
1084 # create submit requests for all found patchinfos
1086 for t in targetprojects:
1087 s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \
1088 (project, p, t, p, options_block)
1093 elif len(args) <= 2:
1094 # try using the working copy at hand
1095 p = findpacs(os.curdir)[0]
1096 src_project = p.prjname
1097 src_package = p.name
1098 if len(args) == 0 and p.islink():
1099 dst_project = p.linkinfo.project
1100 dst_package = p.linkinfo.package
1102 dst_project = args[0]
1104 dst_package = args[1]
1106 dst_package = src_package
1108 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
1109 'Please provide it the target via commandline arguments.' % p.name)
1111 modified = [i for i in p.filenamelist if p.status(i) != ' ' and p.status(i) != '?']
1112 if len(modified) > 0:
1113 print 'Your working copy has local modifications.'
1114 repl = raw_input('Proceed without committing the local changes? (y|N) ')
1117 elif len(args) >= 3:
1118 # get the arguments from the commandline
1119 src_project, src_package, dst_project = args[0:3]
1121 dst_package = args[3]
1123 dst_package = src_package
1125 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1126 + self.get_cmd_help('request'))
1128 if not opts.nodevelproject:
1131 devloc = show_develproject(apiurl, dst_project, dst_package)
1132 except urllib2.HTTPError:
1133 print >>sys.stderr, """\
1134 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
1135 % (dst_project, dst_package)
1139 dst_project != devloc and \
1140 src_project != devloc:
1142 A different project, %s, is defined as the place where development
1143 of the package %s primarily takes place.
1144 Please submit there instead, or use --nodevelproject to force direct submission.""" \
1145 % (devloc, dst_package)
1152 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
1153 rdiff += server_diff(apiurl,
1154 dst_project, dst_package, opts.revision,
1155 src_project, src_package, None, True)
1161 reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit')
1162 user = conf.get_apiurl_usr(apiurl)
1163 myreqs = [ i for i in reqs if i.state.who == user ]
1166 print 'You already created the following submit request: %s.' % \
1167 ', '.join([str(i.reqid) for i in myreqs ])
1168 repl = raw_input('Supersede the old requests? (y/n/c) ')
1169 if repl.lower() == 'c':
1170 print >>sys.stderr, 'Aborting'
1173 actionxml = """<action type="submit"> <source project="%s" package="%s" rev="%s"/> <target project="%s" package="%s"/> %s </action>""" % \
1174 (src_project, src_package, opts.revision or show_upstream_rev(apiurl, src_project, src_package), dst_project, dst_package, options_block)
1175 if repl.lower() == 'y':
1177 change_request_state(apiurl, str(req.reqid), 'superseded',
1178 'superseded by %s' % result, result)
1181 change_request_state(apiurl, opts.supersede, 'superseded', '', result)
1183 #print 'created request id', result
1186 def _delete_request(self, args, opts):
1188 raise oscerr.WrongArgs('Please specify at least a project.')
1190 raise oscerr.WrongArgs('Too many arguments.')
1194 package = """package="%s" """ % (args[1])
1195 actionxml = """<action type="delete"> <target project="%s" %s/> </action> """ % (args[0], package)
1198 def _changedevel_request(self, args, opts):
1200 raise oscerr.WrongArgs('Too many arguments.')
1202 if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
1204 devel_project = store_read_project(wd)
1205 devel_package = package = store_read_package(wd)
1206 project = conf.config['getpac_default_project']
1209 raise oscerr.WrongArgs('Too few arguments.')
1211 devel_project = args[2]
1214 devel_package = package
1216 devel_package = args[3]
1218 actionxml = """ <action type="change_devel"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> </action> """ % \
1219 (devel_project, devel_package, project, package)
1223 def _add_role(self, args, opts):
1225 raise oscerr.WrongArgs('Too many arguments.')
1227 raise oscerr.WrongArgs('Too few arguments.')
1229 apiurl = self.get_api_url()
1234 actionxml = """ <action type="add_role"> <target project="%s" /> <person name="%s" role="%s" /> </action> """ % \
1235 (project, user, role)
1239 actionxml = """ <action type="add_role"> <target project="%s" package="%s" /> <person name="%s" role="%s" /> </action> """ % \
1240 (project, package, user, role)
1242 if get_user_meta(apiurl, user) == None:
1243 raise oscerr.WrongArgs('osc: an error occured.')
1247 def _set_bugowner(self, args, opts):
1249 raise oscerr.WrongArgs('Too many arguments.')
1251 raise oscerr.WrongArgs('Too few arguments.')
1253 apiurl = self.get_api_url()
1260 if get_user_meta(apiurl, user) == None:
1261 raise oscerr.WrongArgs('osc: an error occured.')
1263 actionxml = """ <action type="set_bugowner"> <target project="%s" package="%s" /> <person name="%s" /> </action> """ % \
1264 (project, package, user)
1268 @cmdln.option('-a', '--action', action='callback', callback = _actionparser,dest = 'actions',
1269 help='specify action type of a request, can be : submit/delete/change_devel/add_role/set_bugowner')
1270 @cmdln.option('-m', '--message', metavar='TEXT',
1271 help='specify message TEXT')
1272 @cmdln.option('-r', '--revision', metavar='REV',
1273 help='for "create", specify a certain source revision ID (the md5 sum)')
1274 @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
1275 help='Superseding another request by this one')
1276 @cmdln.option('--nodevelproject', action='store_true',
1277 help='do not follow a defined devel project ' \
1278 '(primary project where a package is developed)')
1279 @cmdln.option('--cleanup', action='store_true',
1280 help='remove package if submission gets accepted (default for home:<id>:branch projects)')
1281 @cmdln.option('--no-cleanup', action='store_true',
1282 help='never remove source package on accept, but update its content')
1283 @cmdln.option('--no-update', action='store_true',
1284 help='never touch source package on accept (will break source links)')
1285 @cmdln.option('-d', '--diff', action='store_true',
1286 help='show diff only instead of creating the actual request')
1287 @cmdln.option('--yes', action='store_true',
1288 help='proceed without asking.')
1289 @cmdln.alias("creq")
1290 def do_createrequest(self, subcmd, opts, *args):
1291 """${cmd_name}: create multiple requests with a single command
1294 osc creq [OPTIONS] [
1295 -a submit SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
1296 -a delete PROJECT [PACKAGE]
1297 -a change_devel PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
1298 -a add_role USER ROLE PROJECT [PACKAGE]
1299 -a set_bugowner USER PROJECT [PACKAGE]
1302 Option -m works for all types of request, the rest work only for submit.
1304 osc creq -a submit -a delete home:someone:branches:openSUSE:Tools -a change_devel openSUSE:Tools osc home:someone:branches:openSUSE:Tools -m ok
1306 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.
1309 src_update = conf.config['submitrequest_on_accept_action'] or None
1310 # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
1312 src_update = "cleanup"
1313 elif opts.no_cleanup:
1314 src_update = "update"
1315 elif opts.no_update:
1316 src_update = "noupdate"
1320 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
1322 args = slash_split(args)
1324 apiurl = self.get_api_url()
1328 for ai in opts.actions:
1330 args = opts.actiondata[i]
1332 actionsxml += self._submit_request(args,opts, options_block)
1333 elif ai == 'delete':
1334 args = opts.actiondata[i]
1335 actionsxml += self._delete_request(args,opts)
1337 elif ai == 'change_devel':
1338 args = opts.actiondata[i]
1339 actionsxml += self._changedevel_request(args,opts)
1341 elif ai == 'add_role':
1342 args = opts.actiondata[i]
1343 actionsxml += self._add_role(args,opts)
1345 elif ai == 'set_bugowner':
1346 args = opts.actiondata[i]
1347 actionsxml += self._set_bugowner(args,opts)
1350 raise oscerr.WrongArgs('Unsupported action %s' % ai)
1351 if actionsxml == "":
1352 sys.exit('No actions need to be taken.')
1354 if not opts.message:
1355 opts.message = edit_message()
1358 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
1359 (actionsxml, cgi.escape(opts.message or ""))
1360 u = makeurl(apiurl, ['request'], query='cmd=create')
1361 f = http_POST(u, data=xml)
1363 root = ET.parse(f).getroot()
1364 return root.get('id')
1367 @cmdln.option('-m', '--message', metavar='TEXT',
1368 help='specify message TEXT')
1369 @cmdln.option('-r', '--role', metavar='role', default='maintainer',
1370 help='specify user role (default: maintainer)')
1371 @cmdln.alias("reqmaintainership")
1372 @cmdln.alias("reqms")
1373 def do_requestmaintainership(self, subcmd, opts, *args):
1374 """${cmd_name}: requests to add user as maintainer
1377 osc requestmaintainership # for current user in checked out package
1378 osc requestmaintainership USER # for specified user in checked out package
1379 osc requestmaintainership PROJECT PACKAGE # for current user
1380 osc requestmaintainership PROJECT PACKAGE USER # request for specified user
1384 args = slash_split(args)
1385 apiurl = self.get_api_url()
1388 if is_package_dir(os.getcwd()):
1389 project = store_read_project(os.curdir)
1390 package = store_read_package(os.curdir)
1392 user = conf.get_apiurl_usr(apiurl)
1396 raise oscerr.WrongArgs('Wrong number of arguments.')
1397 elif len(args) == 2:
1400 user = conf.get_apiurl_usr(apiurl)
1401 elif len(args) == 3:
1406 raise oscerr.WrongArgs('Wrong number of arguments.')
1408 if not opts.role in ('maintainer', 'bugowner'):
1409 raise oscerr.WrongOptions('invalid \'--role\': either specify \'maintainer\' or \'bugowner\'')
1411 arg = [ user, opts.role, project, package ]
1413 actionsxml = self._add_role(arg, None)
1415 if not opts.message:
1416 opts.message = edit_message()
1419 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
1420 (actionsxml, cgi.escape(opts.message or ""))
1421 u = makeurl(apiurl, ['request'], query='cmd=create')
1422 f = http_POST(u, data=xml)
1424 root = ET.parse(f).getroot()
1425 return root.get('id')
1427 @cmdln.option('-m', '--message', metavar='TEXT',
1428 help='specify message TEXT')
1430 @cmdln.alias("deletereq")
1431 def do_deleterequest(self, subcmd, opts, *args):
1432 """${cmd_name}: Create request to delete a package or project
1435 osc deletereq [-m TEXT] # works in checked out project/package
1436 osc deletereq [-m TEXT] PROJECT [PACKAGE]
1440 args = slash_split(args)
1446 if is_project_dir(os.getcwd()):
1447 project = store_read_project(os.curdir)
1448 elif is_package_dir(os.getcwd()):
1449 project = store_read_project(os.curdir)
1450 package = store_read_package(os.curdir)
1452 raise oscerr.WrongArgs('Please specify at least a project.')
1453 elif len(args) == 1:
1459 raise oscerr.WrongArgs('Too many arguments.')
1461 apiurl = self.get_api_url()
1463 if not opts.message:
1465 if package is not None:
1466 footer=textwrap.TextWrapper(width = 66).fill(
1467 'please explain why you like to delete package %s of project %s'
1468 % (package,project))
1470 footer=textwrap.TextWrapper(width = 66).fill(
1471 'please explain why you like to delete project %s' % project)
1472 opts.message = edit_message(footer)
1474 result = create_delete_request(apiurl, project, package, opts.message)
1478 @cmdln.option('-m', '--message', metavar='TEXT',
1479 help='specify message TEXT')
1481 @cmdln.alias("changedevelreq")
1482 def do_changedevelrequest(self, subcmd, opts, *args):
1483 """${cmd_name}: Create request to change the devel package definition.
1485 [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration
1486 for information on this topic.]
1488 See the "request" command for showing and modifing existing requests.
1490 osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
1494 raise oscerr.WrongArgs('Too many arguments.')
1496 apiurl = self.get_api_url()
1498 if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
1500 devel_project = store_read_project(wd)
1501 devel_package = package = store_read_package(wd)
1502 project = conf.config['getpac_default_project']
1505 raise oscerr.WrongArgs('Too few arguments.')
1507 devel_project = args[2]
1510 devel_package = package
1512 devel_package = args[3]
1514 if not opts.message:
1516 footer=textwrap.TextWrapper(width = 66).fill(
1517 'please explain why you like to change the devel project of %s/%s to %s/%s'
1518 % (project,package,devel_project,devel_package))
1519 opts.message = edit_message(footer)
1521 result = create_change_devel_request(apiurl,
1522 devel_project, devel_package,
1528 @cmdln.option('-d', '--diff', action='store_true',
1529 help='generate a diff')
1530 @cmdln.option('-u', '--unified', action='store_true',
1531 help='output the diff in the unified diff format')
1532 @cmdln.option('-m', '--message', metavar='TEXT',
1533 help='specify message TEXT')
1534 @cmdln.option('-t', '--type', metavar='TYPE',
1535 help='limit to requests which contain a given action type (submit/delete/change_devel)')
1536 @cmdln.option('-a', '--all', action='store_true',
1537 help='all states. Same as\'-s all\'')
1538 @cmdln.option('-s', '--state', default='', # default is 'all' if no args given, 'new' otherwise
1539 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]')
1540 @cmdln.option('-D', '--days', metavar='DAYS',
1541 help='only list requests in state "new" or changed in the last DAYS. [default=%(request_list_days)s]')
1542 @cmdln.option('-U', '--user', metavar='USER',
1543 help='requests or reviews limited for the specified USER')
1544 @cmdln.option('-G', '--group', metavar='GROUP',
1545 help='requests or reviews limited for the specified GROUP')
1546 @cmdln.option('-b', '--brief', action='store_true', default=False,
1547 help='print output in list view as list subcommand')
1548 @cmdln.option('-M', '--mine', action='store_true',
1549 help='only show requests created by yourself')
1550 @cmdln.option('-B', '--bugowner', action='store_true',
1551 help='also show requests about packages where I am bugowner')
1552 @cmdln.option('-i', '--interactive', action='store_true',
1553 help='interactive review of request')
1554 @cmdln.option('--non-interactive', action='store_true',
1555 help='non-interactive review of request')
1556 @cmdln.option('--exclude-target-project', action='append',
1557 help='exclude target project from request list')
1558 @cmdln.option('--involved-projects', action='store_true',
1559 help='show all requests for project/packages where USER is involved')
1560 @cmdln.option('--source-buildstatus', action='store_true',
1561 help='print the buildstatus of the source package (only works with "show")')
1563 @cmdln.alias("review")
1564 # FIXME: rewrite this mess and split request and review
1565 def do_request(self, subcmd, opts, *args):
1566 """${cmd_name}: Show or modify requests and reviews
1568 [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration
1569 for information on this topic.]
1571 The 'request' command has the following sub commands:
1573 "list" lists open requests attached to a project or package or person.
1574 Uses the project/package of the current directory if none of
1575 -M, -U USER, project/package are given.
1577 "log" will show the history of the given ID
1579 "show" will show the request itself, and generate a diff for review, if
1580 used with the --diff option. The keyword show can be omitted if the ID is numeric.
1582 "decline" will change the request state to "declined"
1584 "reopen" will set the request back to new or review.
1586 "wipe" will permanently delete a request
1588 "revoke" will set the request state to "revoked"
1590 "accept" will change the request state to "accepted" and will trigger
1591 the actual submit process. That would normally be a server-side copy of
1592 the source package to the target package.
1594 "checkout" will checkout the request's source package ("submit" requests only).
1596 The 'review' command has the following sub commands:
1598 "list" lists open requests that need to be reviewed by the
1599 specified user or group
1601 "add" adds a person or group as reviewer to a request
1603 "accept" mark the review positive
1605 "decline" mark the review negative. A negative review will
1606 decline the request.
1609 osc request list [-M] [-U USER] [-s state] [-D DAYS] [-t type] [-B] [PRJ [PKG]]
1611 osc request [show] [-d] [-b] ID
1613 osc request accept [-m TEXT] ID
1614 osc request decline [-m TEXT] ID
1615 osc request revoke [-m TEXT] ID
1617 osc request reopen [-m TEXT] ID
1618 osc request approvenew [-m TEXT] PROJECT
1620 osc request checkout/co ID
1621 osc request clone [-m TEXT] ID
1623 osc review list [-U USER] [-G GROUP] [-s state]
1624 osc review add [-m TEXT] [-U USER] [-G GROUP] ID
1625 osc review accept [-m TEXT] ID
1626 osc review decline [-m TEXT] ID
1627 osc review reopen [-m TEXT] ID
1632 args = slash_split(args)
1634 if opts.all and opts.state:
1635 raise oscerr.WrongOptions('Sorry, the options \'--all\' and \'--state\' ' \
1636 'are mutually exclusive.')
1637 if opts.mine and opts.user:
1638 raise oscerr.WrongOptions('Sorry, the options \'--user\' and \'--mine\' ' \
1639 'are mutually exclusive.')
1640 if opts.interactive and opts.non_interactive:
1641 raise oscerr.WrongOptions('Sorry, the options \'--interactive\' and ' \
1642 '\'--non-interactive\' are mutually exclusive')
1647 if opts.state == '':
1650 if opts.state == '':
1653 if args[0] == 'help':
1654 return self.do_help(['help', 'request'])
1656 cmds = [ 'list', 'log', 'show', 'decline', 'reopen', 'clone', 'accept', 'approvenew', 'wipe', 'revoke', 'checkout', 'co' ]
1657 if subcmd != 'review' and args[0] not in cmds:
1658 raise oscerr.WrongArgs('Unknown request action %s. Choose one of %s.' \
1659 % (args[0],', '.join(cmds)))
1660 cmds = [ 'list', 'add', 'decline', 'accept', 'reopen' ]
1661 if subcmd == 'review' and args[0] not in cmds:
1662 raise oscerr.WrongArgs('Unknown review action %s. Choose one of %s.' \
1663 % (args[0],', '.join(cmds)))
1668 apiurl = self.get_api_url()
1671 min_args, max_args = 0, 2
1673 min_args, max_args = 1, 1
1674 if len(args) < min_args:
1675 raise oscerr.WrongArgs('Too few arguments.')
1676 if len(args) > max_args:
1677 raise oscerr.WrongArgs('Too many arguments.')
1678 if cmd in ['add'] and not opts.user and not opts.group:
1679 opts.user = conf.get_apiurl_usr(apiurl)
1681 if cmd == 'list' or cmd == 'approvenew':
1686 elif not opts.mine and not opts.user:
1688 project = store_read_project(os.curdir)
1689 package = store_read_package(os.curdir)
1690 except oscerr.NoWorkingCopy:
1695 elif cmd in ['log', 'add', 'show', 'decline', 'reopen', 'clone', 'accept', 'wipe', 'revoke', 'checkout', 'co']:
1698 # clone all packages from a given request
1699 if cmd in ['clone']:
1700 # should we force a message?
1701 print clone_request(apiurl, reqid, opts.message)
1703 # add new reviewer to existing request
1704 elif cmd in ['add'] and subcmd == 'review':
1705 query = { 'cmd': 'addreview' }
1707 query['by_user'] = opts.user
1709 query['by_group'] = opts.group
1710 url = makeurl(apiurl, ['request', reqid], query)
1711 if not opts.message:
1712 opts.message = edit_message()
1713 r = http_POST(url, data=opts.message)
1714 print ET.parse(r).getroot().attrib['code']
1716 # list and approvenew
1717 elif cmd == 'list' or cmd == 'approvenew':
1718 states = ('new', 'accepted', 'revoked', 'declined', 'review', 'superseded')
1720 if cmd == 'approvenew':
1722 results = get_request_list(apiurl, project, package, '', ['new'])
1724 state_list = opts.state.split(',')
1726 state_list = ['all']
1727 if subcmd == 'review':
1728 state_list = ['review']
1729 elif opts.state == 'all':
1730 state_list = ['all']
1732 for s in state_list:
1733 if not s in states and not s == 'all':
1734 raise oscerr.WrongArgs('Unknown state \'%s\', try one of %s' % (s, ','.join(states)))
1736 who = conf.get_apiurl_usr(apiurl)
1740 ## FIXME -B not implemented!
1742 if (self.options.debug):
1743 print 'list: option --bugowner ignored: not impl.'
1745 if subcmd == 'review':
1746 # FIXME: do the review list for the user and for all groups he belong to
1747 results = get_review_list(apiurl, project, package, who, opts.group, state_list)
1749 if opts.involved_projects:
1750 who = who or conf.get_apiurl_usr(apiurl)
1751 results = get_user_projpkgs_request_list(apiurl, who, req_state=state_list,
1752 req_type=opts.type, exclude_projects=opts.exclude_target_project or [])
1754 results = get_request_list(apiurl, project, package, who,
1755 state_list, opts.type, opts.exclude_target_project or [])
1757 results.sort(reverse=True)
1759 days = opts.days or conf.config['request_list_days']
1766 since = time.strftime('%Y-%m-%dT%H:%M:%S',time.localtime(time.time()-days*24*3600))
1769 ## bs has received 2009-09-20 a new xquery compare() function
1770 ## which allows us to limit the list inside of get_request_list
1771 ## That would be much faster for coolo. But counting the remainder
1772 ## would not be possible with current xquery implementation.
1773 ## Workaround: fetch all, and filter on client side.
1775 ## FIXME: date filtering should become implemented on server side
1776 for result in results:
1777 if days == 0 or result.state.when > since or result.state.name == 'new':
1778 if (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive:
1779 request_interactive_review(apiurl, result)
1781 print result.list_view()
1785 print "There are %d requests older than %s days.\n" % (skipped, days)
1787 if cmd == 'approvenew':
1788 print "\n *** Approve them all ? [y/n] ***"
1789 if sys.stdin.read(1) == "y":
1791 if not opts.message:
1792 opts.message = edit_message()
1793 for result in results:
1794 print result.reqid, ": ",
1795 r = change_request_state(apiurl,
1796 str(result.reqid), 'accepted', opts.message or '')
1797 print 'Result of change request state: %s' % r
1799 print >>sys.stderr, 'Aborted...'
1800 raise oscerr.UserAbort()
1803 for l in get_request_log(apiurl, reqid):
1808 r = get_request(apiurl, reqid)
1811 elif (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive:
1812 return request_interactive_review(apiurl, r)
1815 if opts.source_buildstatus:
1816 if r.actions[0].type != 'submit':
1817 raise oscerr.WrongOptions( '\'--source-buildstatus\' is not possible for ' \
1818 'request type: \'%s\'' % r.actions[0].type)
1819 print 'Buildstatus for \'%s/%s\':' % (r.actions[0].src_project, r.actions[0].src_package)
1820 print '\n'.join(get_results(apiurl, r.actions[0].src_project, r.actions[0].src_package))
1821 # FIXME: will inevitably fail if the given target doesn't exist
1822 # FIXME: diff should work if there are submit actions anyway
1823 if opts.diff and r.actions[0].type != 'submit':
1824 raise oscerr.WrongOptions('\'--diff\' is not possible for request type: \'%s\'' % r.actions[0].type)
1828 # works since OBS 2.1
1829 rdiff = request_diff(apiurl, reqid)
1830 except urllib2.HTTPError, e:
1831 # for OBS 2.0 and before
1833 rdiff = server_diff(apiurl,
1834 r.actions[0].dst_project, r.actions[0].dst_package, None,
1835 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, True)
1836 except urllib2.HTTPError, e:
1838 e.osc_msg = 'Diff not possible'
1840 # backward compatiblity: only a recent api/backend supports the missingok parameter
1842 rdiff = server_diff(apiurl,
1843 r.actions[0].dst_project, r.actions[0].dst_package, None,
1844 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, False)
1845 except urllib2.HTTPError, e:
1846 e.osc_msg = 'Diff not possible'
1851 elif cmd == 'checkout' or cmd == 'co':
1852 r = get_request(apiurl, reqid)
1853 submits = [ i for i in r.actions if i.type == 'submit' ]
1854 if not len(submits):
1855 raise oscerr.WrongArgs('\'checkout\' only works for \'submit\' requests')
1856 checkout_package(apiurl, submits[0].src_project, submits[0].src_package, \
1857 submits[0].src_rev, expand_link=True, prj_dir=submits[0].src_project)
1860 state_map = {'reopen' : 'new', 'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked'}
1861 # Change review state only
1862 if subcmd == 'review':
1863 if not opts.message:
1864 opts.message = edit_message()
1865 if cmd in ['accept', 'decline', 'reopen']:
1866 r = change_review_state(apiurl,
1867 reqid, state_map[cmd], conf.get_apiurl_usr(apiurl), opts.group, opts.message or '')
1869 # Change state of entire request
1870 elif cmd in ['reopen', 'accept', 'decline', 'wipe', 'revoke']:
1871 rq = get_request(apiurl, reqid)
1872 if rq.state.name == state_map[cmd]:
1873 repl = raw_input("\n *** The state of the request (#%s) is already '%s'. Change state anyway? [y/n] *** " % (reqid, rq.state.name))
1874 if repl.lower() != 'y':
1875 print >>sys.stderr, 'Aborted...'
1876 raise oscerr.UserAbort()
1878 if not opts.message:
1879 tmpl = change_request_state_template(rq, state_map[cmd])
1880 opts.message = edit_message(template=tmpl)
1881 r = change_request_state(apiurl,
1882 reqid, state_map[cmd], opts.message or '')
1883 print 'Result of change request state: %s' % r
1885 # editmeta and its aliases are all depracated
1886 @cmdln.alias("editprj")
1887 @cmdln.alias("createprj")
1888 @cmdln.alias("editpac")
1889 @cmdln.alias("createpac")
1890 @cmdln.alias("edituser")
1891 @cmdln.alias("usermeta")
1893 def do_editmeta(self, subcmd, opts, *args):
1896 Obsolete command to edit metadata. Use 'meta' now.
1898 See the help output of 'meta'.
1902 print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
1903 print >>sys.stderr, 'See \'osc help meta\'.'
1904 #self.do_help([None, 'meta'])
1908 @cmdln.option('-r', '--revision', metavar='rev',
1909 help='use the specified revision.')
1910 @cmdln.option('-R', '--use-plain-revision', action='store_true',
1911 help='Don\'t expand revsion based on baserev, the revision which was used when commit happened.')
1912 @cmdln.option('-b', '--use-baserev', action='store_true',
1913 help='Use the revisions which exists when the original commit happend and don\'t try to merge later commits.')
1914 @cmdln.option('-u', '--unset', action='store_true',
1915 help='remove revision in link, it will point always to latest revision')
1916 def do_setlinkrev(self, subcmd, opts, *args):
1917 """${cmd_name}: Updates a revision number in a source link.
1919 This command adds or updates a specified revision number in a source link.
1920 The current revision of the source is used, if no revision number is specified.
1924 osc setlinkrev PROJECT [PACKAGE]
1928 args = slash_split(args)
1929 apiurl = self.get_api_url()
1934 p = findpacs(os.curdir)[0]
1939 sys.exit('Local directory is no checked out source link package, aborting')
1940 elif len(args) == 2:
1943 elif len(args) == 1:
1946 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1947 + self.get_cmd_help('setlinkrev'))
1950 packages = [ package ]
1952 packages = meta_get_packagelist(apiurl, project)
1954 if opts.use_plain_revision:
1956 if opts.use_baserev:
1960 print "setting revision for package", p
1964 rev, dummy = parseRevisionOption(opts.revision)
1965 set_link_rev(apiurl, project, p, revision = rev, use_xsrcmd5 = use_xsrcmd5, use_baserev = use_baserev)
1968 def do_linktobranch(self, subcmd, opts, *args):
1969 """${cmd_name}: Convert a package containing a classic link with patch to a branch
1971 This command tells the server to convert a _link with or without a project.diff
1972 to a branch. This is a full copy with a _link file pointing to the branched place.
1975 osc linktobranch # can be used in checked out package
1976 osc linktobranch PROJECT PACKAGE
1979 args = slash_split(args)
1980 apiurl = self.get_api_url()
1984 project = store_read_project(wd)
1985 package = store_read_package(wd)
1986 update_local_dir = True
1988 raise oscerr.WrongArgs('Too few arguments (required none or two)')
1990 raise oscerr.WrongArgs('Too many arguments (required none or two)')
1994 update_local_dir = False
1997 link_to_branch(apiurl, project, package)
1998 if update_local_dir:
2000 pac.update(rev=pac.latest_rev())
2003 @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'],
2004 help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.')
2005 @cmdln.option('-c', '--current', action='store_true',
2006 help='link fixed against current revision.')
2007 @cmdln.option('-r', '--revision', metavar='rev',
2008 help='link the specified revision.')
2009 @cmdln.option('-f', '--force', action='store_true',
2010 help='overwrite an existing link file if it is there.')
2011 @cmdln.option('-d', '--disable-publish', action='store_true',
2012 help='disable publishing of the linked package')
2013 def do_linkpac(self, subcmd, opts, *args):
2014 """${cmd_name}: "Link" a package to another package
2016 A linked package is a clone of another package, but plus local
2017 modifications. It can be cross-project.
2019 The DESTPAC name is optional; the source packages' name will be used if
2022 Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
2024 To add a patch, add the patch as file and add it to the _link file.
2025 You can also specify text which will be inserted at the top of the spec file.
2027 See the examples in the _link file.
2029 NOTE: In case you are not aware about the difference of 'linkpac' and 'branch' command
2030 you should use the 'branch' command by default.
2033 osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
2037 args = slash_split(args)
2038 apiurl = self.get_api_url()
2040 if not args or len(args) < 3:
2041 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
2042 + self.get_cmd_help('linkpac'))
2044 rev, dummy = parseRevisionOption(opts.revision)
2046 src_project = args[0]
2047 src_package = args[1]
2048 dst_project = args[2]
2050 dst_package = args[3]
2052 dst_package = src_package
2054 if src_project == dst_project and src_package == dst_package:
2055 raise oscerr.WrongArgs('Error: source and destination are the same.')
2057 if src_project == dst_project and not opts.cicount:
2058 # in this case, the user usually wants to build different spec
2059 # files from the same source
2060 opts.cicount = "copy"
2063 rev = show_upstream_rev(apiurl, src_project, src_package)
2065 if rev and not checkRevision(src_project, src_package, rev):
2066 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2069 link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish)
2071 @cmdln.option('--nosources', action='store_true',
2072 help='ignore source packages when copying build results to destination project')
2073 @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]',
2074 help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]')
2075 @cmdln.option('-d', '--disable-publish', action='store_true',
2076 help='disable publishing of the aggregated package')
2077 def do_aggregatepac(self, subcmd, opts, *args):
2078 """${cmd_name}: "Aggregate" a package to another package
2080 Aggregation of a package means that the build results (binaries) of a
2081 package are basically copied into another project.
2082 This can be used to make packages available from building that are
2083 needed in a project but available only in a different project. Note
2084 that this is done at the expense of disk space. See
2085 http://en.opensuse.org/openSUSE:Build_Service_Tips_and_Tricks#link_and_aggregate
2086 for more information.
2088 The DESTPAC name is optional; the source packages' name will be used if
2092 osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
2096 args = slash_split(args)
2098 if not args or len(args) < 3:
2099 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
2100 + self.get_cmd_help('aggregatepac'))
2102 src_project = args[0]
2103 src_package = args[1]
2104 dst_project = args[2]
2106 dst_package = args[3]
2108 dst_package = src_package
2110 if src_project == dst_project and src_package == dst_package:
2111 raise oscerr.WrongArgs('Error: source and destination are the same.')
2115 for pair in opts.map_repo.split(','):
2116 src_tgt = pair.split('=')
2117 if len(src_tgt) != 2:
2118 raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo)
2119 repo_map[src_tgt[0]] = src_tgt[1]
2121 aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish, opts.nosources)
2124 @cmdln.option('-c', '--client-side-copy', action='store_true',
2125 help='do a (slower) client-side copy')
2126 @cmdln.option('-k', '--keep-maintainers', action='store_true',
2127 help='keep original maintainers. Default is remove all and replace with the one calling the script.')
2128 @cmdln.option('-d', '--keep-develproject', action='store_true',
2129 help='keep develproject tag in the package metadata')
2130 @cmdln.option('-r', '--revision', metavar='rev',
2131 help='link the specified revision.')
2132 @cmdln.option('-t', '--to-apiurl', metavar='URL',
2133 help='URL of destination api server. Default is the source api server.')
2134 @cmdln.option('-m', '--message', metavar='TEXT',
2135 help='specify message TEXT')
2136 @cmdln.option('-e', '--expand', action='store_true',
2137 help='if the source package is a link then copy the expanded version of the link')
2138 def do_copypac(self, subcmd, opts, *args):
2139 """${cmd_name}: Copy a package
2141 A way to copy package to somewhere else.
2143 It can be done across buildservice instances, if the -t option is used.
2144 In that case, a client-side copy and link expansion are implied.
2146 Using --client-side-copy always involves downloading all files, and
2147 uploading them to the target.
2149 The DESTPAC name is optional; the source packages' name will be used if
2153 osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
2157 args = slash_split(args)
2159 if not args or len(args) < 3:
2160 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
2161 + self.get_cmd_help('copypac'))
2163 src_project = args[0]
2164 src_package = args[1]
2165 dst_project = args[2]
2167 dst_package = args[3]
2169 dst_package = src_package
2171 src_apiurl = conf.config['apiurl']
2173 dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl)
2175 dst_apiurl = src_apiurl
2177 if src_apiurl != dst_apiurl:
2178 opts.client_side_copy = True
2181 rev, dummy = parseRevisionOption(opts.revision)
2184 comment = opts.message
2187 rev = show_upstream_rev(src_apiurl, src_project, src_package)
2188 comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev )
2190 if src_project == dst_project and \
2191 src_package == dst_package and \
2193 src_apiurl == dst_apiurl:
2194 raise oscerr.WrongArgs('Source and destination are the same.')
2196 r = copy_pac(src_apiurl, src_project, src_package,
2197 dst_apiurl, dst_project, dst_package,
2198 client_side_copy=opts.client_side_copy,
2199 keep_maintainers=opts.keep_maintainers,
2200 keep_develproject=opts.keep_develproject,
2207 @cmdln.option('-c', '--checkout', action='store_true',
2208 help='Checkout branched package afterwards ' \
2209 '(\'osc bco\' is a shorthand for this option)' )
2210 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
2211 help='Use this attribute to find affected packages (default is OBS:Maintained)')
2212 @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE',
2213 help='Use this attribute to find update projects (default is OBS:UpdateProject) ')
2214 def do_mbranch(self, subcmd, opts, *args):
2215 """${cmd_name}: Multiple branch of a package
2217 [See http://en.opensuse.org/openSUSE:Build_Service_Concept_Maintenance
2218 for information on this topic.]
2220 This command is used for creating multiple links of defined version of a package
2221 in one project. This is esp. used for maintenance updates.
2223 The branched package will live in
2224 home:USERNAME:branches:ATTRIBUTE:PACKAGE
2225 if nothing else specified.
2228 osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ]
2231 args = slash_split(args)
2232 apiurl = self.get_api_url()
2235 maintained_attribute = conf.config['maintained_attribute']
2236 maintained_update_project_attribute = conf.config['maintained_update_project_attribute']
2238 if not len(args) or len(args) > 2:
2239 raise oscerr.WrongArgs('Wrong number of arguments.')
2245 r = attribute_branch_pkg(apiurl, maintained_attribute, maintained_update_project_attribute, \
2249 print >>sys.stderr, 'ERROR: Attribute branch call came not back with a project.'
2252 print "Project " + r + " created."
2255 Project.init_project(apiurl, r, r, conf.config['do_package_tracking'])
2256 print statfrmt('A', r)
2259 for package in meta_get_packagelist(apiurl, r):
2261 checkout_package(apiurl, r, package, expand_link = True, prj_dir = r)
2263 print >>sys.stderr, 'Error while checkout package:\n', package
2265 if conf.config['verbose']:
2266 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
2269 @cmdln.alias('branchco')
2271 @cmdln.alias('getpac')
2272 @cmdln.option('--nodevelproject', action='store_true',
2273 help='do not follow a defined devel project ' \
2274 '(primary project where a package is developed)')
2275 @cmdln.option('-c', '--checkout', action='store_true',
2276 help='Checkout branched package afterwards ' \
2277 '(\'osc bco\' is a shorthand for this option)' )
2278 @cmdln.option('-f', '--force', default=False, action="store_true",
2279 help='force branch, overwrite target')
2280 @cmdln.option('-m', '--message', metavar='TEXT',
2281 help='specify message TEXT')
2282 @cmdln.option('-r', '--revision', metavar='rev',
2283 help='branch against a specific revision')
2284 def do_branch(self, subcmd, opts, *args):
2285 """${cmd_name}: Branch a package
2287 [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration
2288 for information on this topic.]
2290 Create a source link from a package of an existing project to a new
2291 subproject of the requesters home project (home:branches:)
2293 The branched package will live in
2294 home:USERNAME:branches:PROJECT/PACKAGE
2295 if nothing else specified.
2297 With getpac or bco, the branched package will come from
2298 %(getpac_default_project)s
2299 if nothing else specified.
2303 osc branch SOURCEPROJECT SOURCEPACKAGE
2304 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT
2305 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE
2306 osc getpac SOURCEPACKAGE
2311 if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': opts.checkout = True
2312 args = slash_split(args)
2313 tproject = tpackage = None
2315 if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1:
2316 print >>sys.stderr, 'defaulting to %s/%s' % (conf.config['getpac_default_project'], args[0])
2317 # python has no args.unshift ???
2318 args = [ conf.config['getpac_default_project'] , args[0] ]
2320 if len(args) == 0 and is_package_dir('.'):
2321 args = (store_read_project('.'), store_read_package('.'))
2323 if len(args) < 2 or len(args) > 4:
2324 raise oscerr.WrongArgs('Wrong number of arguments.')
2326 apiurl = self.get_api_url()
2328 expected = 'home:%s:branches:%s' % (conf.get_apiurl_usr(apiurl), args[0])
2330 expected = tproject = args[2]
2334 if not opts.message:
2335 footer='please specify the purpose of your branch'
2336 template='This package was branched from %s in order to ...\n' % args[0]
2337 opts.message = edit_message(footer, template)
2339 exists, targetprj, targetpkg, srcprj, srcpkg = \
2340 branch_pkg(apiurl, args[0], args[1],
2341 nodevelproject=opts.nodevelproject, rev=opts.revision,
2342 target_project=tproject, target_package=tpackage,
2343 return_existing=opts.checkout, msg=opts.message or '',
2346 print >>sys.stderr, 'Using existing branch project: %s' % targetprj
2349 if not exists and (srcprj is not None and srcprj != args[0] or \
2350 srcprj is None and targetprj != expected):
2351 devloc = srcprj or targetprj
2352 if not srcprj and 'branches:' in targetprj:
2353 devloc = targetprj.split('branches:')[1]
2354 print '\nNote: The branch has been created of a different project,\n' \
2356 ' which is the primary location of where development for\n' \
2357 ' that package takes place.\n' \
2358 ' That\'s also where you would normally make changes against.\n' \
2359 ' A direct branch of the specified package can be forced\n' \
2360 ' with the --nodevelproject option.\n' % devloc
2362 package = tpackage or args[1]
2364 checkout_package(apiurl, targetprj, package,
2365 expand_link=True, prj_dir=targetprj)
2366 if conf.config['verbose']:
2367 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
2370 if conf.get_configParser().get('general', 'apiurl') != apiurl:
2371 apiopt = '-A %s ' % apiurl
2372 print 'A working copy of the branched package can be checked out with:\n\n' \
2374 % (apiopt, targetprj, package)
2375 print_request_list(apiurl, args[0], args[1])
2377 print_request_list(apiurl, devloc, args[1])
2380 def do_undelete(self, subcmd, opts, *args):
2381 """${cmd_name}: Restores a deleted project or package on the server.
2383 The server restores a package including the sources and meta configuration.
2384 Binaries remain to be lost and will be rebuild.
2387 osc undelete PROJECT
2388 osc undelete PROJECT PACKAGE [PACKAGE ...]
2393 args = slash_split(args)
2395 raise oscerr.WrongArgs('Missing argument.')
2397 apiurl = self.get_api_url()
2403 undelete_package(apiurl, prj, pkg)
2405 undelete_project(apiurl, prj)
2408 @cmdln.option('-f', '--force', action='store_true',
2409 help='deletes a package or an empty project')
2410 def do_rdelete(self, subcmd, opts, *args):
2411 """${cmd_name}: Delete a project or packages on the server.
2413 As a safety measure, project must be empty (i.e., you need to delete all
2414 packages first). If you are sure that you want to remove this project and all
2415 its packages use \'--force\' switch.
2418 osc rdelete [-f] PROJECT [PACKAGE]
2423 args = slash_split(args)
2424 if len(args) < 1 or len(args) > 2:
2425 raise oscerr.WrongArgs('Wrong number of arguments')
2427 apiurl = self.get_api_url()
2430 # empty arguments result in recursive project delete ...
2432 raise oscerr.WrongArgs('Project argument is empty')
2438 raise oscerr.WrongArgs('Package argument is empty')
2440 delete_package(apiurl, prj, pkg)
2442 elif len(meta_get_packagelist(apiurl, prj)) >= 1 and not opts.force:
2443 print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
2444 'If you are sure that you want to remove this project and all its ' \
2445 'packages use the \'--force\' switch'
2448 delete_project(apiurl, prj)
2451 def do_deletepac(self, subcmd, opts, *args):
2452 print """${cmd_name} is obsolete !
2455 osc delete for checked out packages or projects
2457 osc rdelete for server side operations."""
2462 @cmdln.option('-f', '--force', action='store_true',
2463 help='deletes a project and its packages')
2464 def do_deleteprj(self, subcmd, opts, project):
2465 """${cmd_name} is obsolete !
2472 @cmdln.alias('metafromspec')
2473 @cmdln.option('', '--specfile', metavar='FILE',
2474 help='Path to specfile. (if you pass more than working copy this option is ignored)')
2475 def do_updatepacmetafromspec(self, subcmd, opts, *args):
2476 """${cmd_name}: Update package meta information from a specfile
2478 ARG, if specified, is a package working copy.
2484 args = parseargs(args)
2485 if opts.specfile and len(args) == 1:
2486 specfile = opts.specfile
2489 pacs = findpacs(args)
2491 p.read_meta_from_spec(specfile)
2492 p.update_package_meta()
2496 @cmdln.option('-c', '--change', metavar='rev',
2497 help='the change made by revision rev (like -r rev-1:rev).'
2498 'If rev is negative this is like -r rev:rev-1.')
2499 @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
2500 help='If rev1 is specified it will compare your working copy against '
2501 'the revision (rev1) on the server. '
2502 'If rev1 and rev2 are specified it will compare rev1 against rev2 '
2503 '(NOTE: changes in your working copy are ignored in this case)')
2504 @cmdln.option('-p', '--plain', action='store_true',
2505 help='output the diff in plain (not unified) diff format')
2506 @cmdln.option('--missingok', action='store_true',
2507 help='do not fail if the source or target project/package does not exist on the server')
2508 def do_diff(self, subcmd, opts, *args):
2509 """${cmd_name}: Generates a diff
2511 Generates a diff, comparing local changes against the repository
2514 ARG, specified, is a filename to include in the diff.
2520 args = parseargs(args)
2521 pacs = findpacs(args)
2525 rev = int(opts.change)
2535 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
2538 rev1, rev2 = parseRevisionOption(opts.revision)
2542 for i in pac.get_diff(rev1):
2543 sys.stdout.write(''.join(i))
2545 diff += server_diff(pac.apiurl, pac.prjname, pac.name, rev1,
2546 pac.prjname, pac.name, rev2, not opts.plain, opts.missingok)
2550 @cmdln.option('--oldprj', metavar='OLDPRJ',
2551 help='project to compare against'
2552 ' (deprecated, use 3 argument form)')
2553 @cmdln.option('--oldpkg', metavar='OLDPKG',
2554 help='package to compare against'
2555 ' (deprecated, use 3 argument form)')
2556 @cmdln.option('-r', '--revision', metavar='N[:M]',
2557 help='revision id, where N = old revision and M = new revision')
2558 @cmdln.option('-p', '--plain', action='store_true',
2559 help='output the diff in plain (not unified) diff format')
2560 @cmdln.option('-c', '--change', metavar='rev',
2561 help='the change made by revision rev (like -r rev-1:rev). '
2562 'If rev is negative this is like -r rev:rev-1.')
2563 @cmdln.option('--missingok', action='store_true',
2564 help='do not fail if the source or target project/package does not exist on the server')
2565 def do_rdiff(self, subcmd, opts, *args):
2566 """${cmd_name}: Server-side "pretty" diff of two packages
2568 Compares two packages (three or four arguments) or shows the
2569 changes of a specified revision of a package (two arguments)
2571 If no revision is specified the latest revision is used.
2573 Note that this command doesn't return a normal diff (which could be
2574 applied as patch), but a "pretty" diff, which also compares the content
2579 osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC]
2580 osc ${cmd_name} PROJECT PACKAGE
2584 args = slash_split(args)
2585 apiurl = self.get_api_url()
2596 new_project = args[0]
2597 new_package = args[1]
2599 old_project = opts.oldprj
2601 old_package = opts.oldpkg
2602 elif len(args) == 3 or len(args) == 4:
2603 if opts.oldprj or opts.oldpkg:
2604 raise oscerr.WrongArgs('--oldpkg and --oldprj are only valid with two arguments')
2605 old_project = args[0]
2606 new_package = old_package = args[1]
2607 new_project = args[2]
2609 new_package = args[3]
2611 raise oscerr.WrongArgs('Wrong number of arguments')
2616 rev = int(opts.change)
2626 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
2630 rev1, rev2 = parseRevisionOption(opts.revision)
2632 rdiff = server_diff(apiurl,
2633 old_project, old_package, rev1,
2634 new_project, new_package, rev2, not opts.plain, opts.missingok)
2639 def do_install(self, subcmd, opts, *args):
2640 """${cmd_name}: install a package after build via zypper in -r
2642 Not implemented yet. Use osc repourls,
2643 select the url you best like (standard),
2644 chop off after the last /, this should work with zypper.
2651 args = slash_split(args)
2652 args = expand_proj_pack(args)
2655 ## if there is only one argument, and it ends in .ymp
2656 ## then fetch it, Parse XML to get the first
2657 ## metapackage.group.repositories.repository.url
2658 ## and construct zypper cmd's for all
2659 ## metapackage.group.software.item.name
2661 ## if args[0] is already an url, the use it as is.
2663 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])
2664 print self.do_install.__doc__
2665 print "Example: \n" + cmd
2668 def do_repourls(self, subcmd, opts, *args):
2669 """${cmd_name}: Shows URLs of .repo files
2671 Shows URLs on which to access the project .repos files (yum-style
2672 metadata) on download.opensuse.org.
2675 osc repourls [PROJECT]
2680 apiurl = self.get_api_url()
2684 elif len(args) == 0:
2685 project = store_read_project('.')
2687 raise oscerr.WrongArgs('Wrong number of arguments')
2689 # XXX: API should somehow tell that
2690 url_tmpl = 'http://download.opensuse.org/repositories/%s/%s/%s.repo'
2691 repos = get_repositories_of_project(apiurl, project)
2693 print url_tmpl % (project.replace(':', ':/'), repo, project)
2696 @cmdln.option('-r', '--revision', metavar='rev',
2697 help='checkout the specified revision. '
2698 'NOTE: if you checkout the complete project '
2699 'this option is ignored!')
2700 @cmdln.option('-e', '--expand-link', action='store_true',
2701 help='if a package is a link, check out the expanded '
2702 'sources (no-op, since this became the default)')
2703 @cmdln.option('-u', '--unexpand-link', action='store_true',
2704 help='if a package is a link, check out the _link file ' \
2705 'instead of the expanded sources')
2706 @cmdln.option('-M', '--meta', action='store_true',
2707 help='checkout out meta data instead of sources' )
2708 @cmdln.option('-c', '--current-dir', action='store_true',
2709 help='place PACKAGE folder in the current directory' \
2710 'instead of a PROJECT/PACKAGE directory')
2711 @cmdln.option('-s', '--source-service-files', action='store_true',
2712 help='Use server side generated sources instead of local generation.' )
2713 @cmdln.option('-S', '--server-side-source-service-files', action='store_true',
2714 help='Use server side generated sources instead of local generation.' )
2715 @cmdln.option('-l', '--limit-size', metavar='limit_size',