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).
12 from util import safewriter
13 from optparse import SUPPRESS_HELP
15 MAN_HEADER = r""".TH %(ucname)s "1" "%(date)s" "%(name)s %(version)s" "User Commands"
17 %(name)s \- openSUSE build service command-line tool.
20 [\fIGLOBALOPTS\fR] \fISUBCOMMAND \fR[\fIOPTS\fR] [\fIARGS\fR...]
25 openSUSE build service command-line tool.
29 Type 'osc help <subcommand>' for more detailed help on a specific subcommand.
31 For additional information, see
32 * http://en.opensuse.org/openSUSE:Build_Service_Tutorial
33 * http://en.opensuse.org/openSUSE:OSC
35 You can modify osc commands, or roll you own, via the plugin API:
36 * http://en.opensuse.org/openSUSE:OSC_plugins
38 osc was written by several authors. This man page is automatically generated.
41 class Osc(cmdln.Cmdln):
42 """Usage: osc [GLOBALOPTS] SUBCOMMAND [OPTS] [ARGS...]
43 or: osc help SUBCOMMAND
45 openSUSE build service command-line tool.
46 Type 'osc help <subcommand>' for help on a specific subcommand.
51 For additional information, see
52 * http://en.opensuse.org/openSUSE:Build_Service_Tutorial
53 * http://en.opensuse.org/openSUSE:OSC
55 You can modify osc commands, or roll you own, via the plugin API:
56 * http://en.opensuse.org/openSUSE:OSC_plugins
61 man_header = MAN_HEADER
62 man_footer = MAN_FOOTER
64 def __init__(self, *args, **kwargs):
65 cmdln.Cmdln.__init__(self, *args, **kwargs)
66 cmdln.Cmdln.do_help.aliases.append('h')
67 sys.stderr = safewriter.SafeWriter(sys.stderr)
68 sys.stdout = safewriter.SafeWriter(sys.stdout)
70 def get_version(self):
71 return get_osc_version()
73 def get_optparser(self):
74 """this is the parser for "global" options (not specific to subcommand)"""
76 optparser = cmdln.CmdlnOptionParser(self, version=get_osc_version())
77 optparser.add_option('--debugger', action='store_true',
78 help='jump into the debugger before executing anything')
79 optparser.add_option('--post-mortem', action='store_true',
80 help='jump into the debugger in case of errors')
81 optparser.add_option('-t', '--traceback', action='store_true',
82 help='print call trace in case of errors')
83 optparser.add_option('-H', '--http-debug', action='store_true',
84 help='debug HTTP traffic (filters some headers)')
85 optparser.add_option('--http-full-debug', action='store_true',
86 help='debug HTTP traffic (filters no headers)'),
87 optparser.add_option('-d', '--debug', action='store_true',
88 help='print info useful for debugging')
89 optparser.add_option('-A', '--apiurl', dest='apiurl',
91 help='specify URL to access API server at or an alias')
92 optparser.add_option('-c', '--config', dest='conffile',
94 help='specify alternate configuration file')
95 optparser.add_option('--no-keyring', action='store_true',
96 help='disable usage of desktop keyring system')
97 optparser.add_option('--no-gnome-keyring', action='store_true',
98 help='disable usage of GNOME Keyring')
99 optparser.add_option('-v', '--verbose', dest='verbose', action='count', default=0,
100 help='increase verbosity')
101 optparser.add_option('-q', '--quiet', dest='verbose', action='store_const', const=-1,
102 help='be quiet, not verbose')
106 def postoptparse(self, try_again = True):
107 """merge commandline options into the config"""
109 conf.get_config(override_conffile = self.options.conffile,
110 override_apiurl = self.options.apiurl,
111 override_debug = self.options.debug,
112 override_http_debug = self.options.http_debug,
113 override_http_full_debug = self.options.http_full_debug,
114 override_traceback = self.options.traceback,
115 override_post_mortem = self.options.post_mortem,
116 override_no_keyring = self.options.no_keyring,
117 override_no_gnome_keyring = self.options.no_gnome_keyring,
118 override_verbose = self.options.verbose)
119 except oscerr.NoConfigfile, e:
120 print >>sys.stderr, e.msg
121 print >>sys.stderr, 'Creating osc configuration file %s ...' % e.file
124 config['user'] = raw_input('Username: ')
125 config['pass'] = getpass.getpass()
126 if self.options.no_keyring:
127 config['use_keyring'] = '0'
128 if self.options.no_gnome_keyring:
129 config['gnome_keyring'] = '0'
130 if self.options.apiurl:
131 config['apiurl'] = self.options.apiurl
133 conf.write_initial_config(e.file, config)
134 print >>sys.stderr, 'done'
135 if try_again: self.postoptparse(try_again = False)
136 except oscerr.ConfigMissingApiurl, e:
137 print >>sys.stderr, e.msg
139 user = raw_input('Username: ')
140 passwd = getpass.getpass()
141 conf.add_section(e.file, e.url, user, passwd)
142 if try_again: self.postoptparse(try_again = False)
144 self.options.verbose = conf.config['verbose']
145 self.download_progress = None
146 if conf.config.get('show_download_progress', False):
147 from meter import TextMeter
148 self.download_progress = TextMeter(hide_finished=True)
151 def get_cmd_help(self, cmdname):
152 doc = self._get_cmd_handler(cmdname).__doc__
153 doc = self._help_reindent(doc)
154 doc = self._help_preprocess(doc, cmdname)
155 doc = doc.rstrip() + '\n' # trim down trailing space
156 return self._str(doc)
158 def get_api_url(self):
159 localdir = os.getcwd()
160 if (is_package_dir(localdir) or is_project_dir(localdir)) and not self.options.apiurl:
161 return store_read_apiurl(os.curdir)
163 return conf.config['apiurl']
165 # overridden from class Cmdln() to use config variables in help texts
166 def _help_preprocess(self, help, cmdname):
167 help_msg = cmdln.Cmdln._help_preprocess(self, help, cmdname)
168 return help_msg % conf.config
171 def do_init(self, subcmd, opts, project, package=None):
172 """${cmd_name}: Initialize a directory as working copy
174 Initialize an existing directory to be a working copy of an
175 (already existing) buildservice project/package.
177 (This is the same as checking out a package and then copying sources
178 into the directory. It does NOT create a new package. To create a
179 package, use 'osc meta pkg ... ...')
181 You wouldn't normally use this command.
183 To get a working copy of a package (e.g. for building it or working on
184 it, you would normally use the checkout command. Use "osc help
185 checkout" to get help for it.
193 apiurl = self.get_api_url()
196 Project.init_project(apiurl, os.curdir, project, conf.config['do_package_tracking'])
197 print 'Initializing %s (Project: %s)' % (os.curdir, project)
199 Package.init_package(apiurl, project, package, os.curdir)
200 store_write_string(os.curdir, '_files', show_files_meta(apiurl, project, package) + '\n')
201 print 'Initializing %s (Project: %s, Package: %s)' % (os.curdir, project, package)
207 @cmdln.option('-a', '--arch', metavar='ARCH',
208 help='specify architecture (only for binaries)')
209 @cmdln.option('-r', '--repo', metavar='REPO',
210 help='specify repository (only for binaries)')
211 @cmdln.option('-b', '--binaries', action='store_true',
212 help='list built binaries instead of sources')
213 @cmdln.option('-R', '--revision', metavar='REVISION',
214 help='specify revision (only for sources)')
215 @cmdln.option('-e', '--expand', action='store_true',
216 help='expand linked package (only for sources)')
217 @cmdln.option('-u', '--unexpand', action='store_true',
218 help='always work with unexpanded (source) packages')
219 @cmdln.option('-v', '--verbose', action='store_true',
220 help='print extra information')
221 @cmdln.option('-l', '--long', action='store_true', dest='verbose',
222 help='print extra information')
223 @cmdln.option('-D', '--deleted', action='store_true',
224 help='show only the former deleted projects or packages')
225 def do_list(self, subcmd, opts, *args):
226 """${cmd_name}: List sources or binaries on the server
228 Examples for listing sources:
229 ls # list all projects (deprecated)
230 ls / # list all projects
231 ls PROJECT # list packages in a project
232 ls PROJECT PACKAGE # list source files of package of a project
233 ls PROJECT PACKAGE <file> # list <file> if this file exists
234 ls -v PROJECT PACKAGE # verbosely list source files of package
235 ls -l PROJECT PACKAGE # verbosely list source files of package
236 ll PROJECT PACKAGE # verbosely list source files of package
237 LL PROJECT PACKAGE # verbosely list source files of expanded link
239 With --verbose, the following fields will be shown for each item:
241 Revision number of the last commit
243 Date and time of the last commit
245 Examples for listing binaries:
246 ls -b PROJECT # list all binaries of a project
247 ls -b PROJECT -a ARCH # list ARCH binaries of a project
248 ls -b PROJECT -r REPO # list binaries in REPO
249 ls -b PROJECT PACKAGE REPO ARCH
252 ${cmd_name} [PROJECT [PACKAGE]]
253 ${cmd_name} -b [PROJECT [PACKAGE [REPO [ARCH]]]]
257 args = slash_split(args)
260 if subcmd == 'lL' or subcmd == 'LL':
268 # For consistency with *all* other commands
269 # this list what the server has in the current wd.
270 # CAUTION: 'osc ls -b' already works like this.
274 if project == '/': project = None
278 raise oscerr.WrongArgs("Too many arguments when listing deleted packages")
281 raise oscerr.WrongArgs("Too many arguments when listing deleted packages")
284 if opts.repo != args[2]:
285 raise oscerr.WrongArgs("conflicting repos specified ('%s' vs '%s')"%(opts.repo, args[2]))
292 if not opts.binaries:
293 raise oscerr.WrongArgs('Too many arguments')
295 if opts.arch != args[3]:
296 raise oscerr.WrongArgs("conflicting archs specified ('%s' vs '%s')"%(opts.arch, args[3]))
301 if opts.binaries and opts.expand:
302 raise oscerr.WrongOptions('Sorry, --binaries and --expand are mutual exclusive.')
304 apiurl = self.get_api_url()
308 # ls -b toplevel doesn't make sense, so use info from
309 # current dir if available
312 if is_project_dir(cwd):
313 project = store_read_project(cwd)
314 elif is_package_dir(cwd):
315 project = store_read_project(cwd)
316 package = store_read_package(cwd)
319 raise oscerr.WrongArgs('There are no binaries to list above project level.')
321 raise oscerr.WrongOptions('Sorry, the --revision option is not supported for binaries.')
325 if opts.repo and opts.arch:
326 repos.append(Repo(opts.repo, opts.arch))
327 elif opts.repo and not opts.arch:
328 repos = [repo for repo in get_repos_of_project(apiurl, project) if repo.name == opts.repo]
329 elif opts.arch and not opts.repo:
330 repos = [repo for repo in get_repos_of_project(apiurl, project) if repo.arch == opts.arch]
332 repos = get_repos_of_project(apiurl, project)
336 results.append((repo, get_binarylist(apiurl, project, repo.name, repo.arch, package=package, verbose=opts.verbose)))
338 for result in results:
341 print '%s/%s' % (result[0].name, result[0].arch)
346 print "%9d %s %-40s" % (f.size, shorttime(f.mtime), f.name)
352 elif not opts.binaries:
354 for prj in meta_get_project_list(apiurl, opts.deleted):
359 if self.options.verbose:
360 print >>sys.stderr, 'Sorry, the --verbose option is not implemented for projects.'
362 raise oscerr.WrongOptions('Sorry, the --expand option is not implemented for projects.')
363 for pkg in meta_get_packagelist(apiurl, project, opts.deleted):
366 elif len(args) == 2 or len(args) == 3:
368 print_not_found = True
371 l = meta_get_filelist(apiurl,
374 verbose=opts.verbose,
377 link_seen = '_link' in l
379 out = [ '%s %7s %9d %s %s' % (i.md5, i.rev, i.size, shorttime(i.mtime), i.name) \
380 for i in l if not fname or fname == i.name ]
382 print_not_found = False
387 print_not_found = False
390 if opts.expand or opts.unexpand or not link_seen: break
391 m = show_files_meta(apiurl, project, package)
393 li.read(ET.fromstring(''.join(m)).find('linkinfo'))
395 raise oscerr.LinkExpandError(project, package, li.error)
396 project, package, rev = li.project, li.package, li.rev
398 print '# -> %s %s (%s)' % (project, package, rev)
400 print '# -> %s %s (latest)' % (project, package)
402 if fname and print_not_found:
403 print 'file \'%s\' does not exist' % fname
406 @cmdln.option('-f', '--force', action='store_true',
407 help='force generation of new patchinfo file')
408 @cmdln.option('-n', '--new', action='store_true',
409 help='Use new, OBS 2.3 style patchinfo format. Will become default on release of OBS 2.3.')
410 @cmdln.option('--force-update', action='store_true',
411 help='drops away collected packages from an already built patch and let it collect again')
412 def do_patchinfo(self, subcmd, opts, *args):
413 """${cmd_name}: Generate and edit a patchinfo file.
415 A patchinfo file describes the packages for an update and the kind of
420 osc patchinfo PATCH_NAME
424 project_dir = localdir = os.getcwd()
425 if is_project_dir(localdir):
426 project = store_read_project(localdir)
427 apiurl = self.get_api_url()
429 sys.exit('This command must be called in a checked out project.')
431 for p in meta_get_packagelist(apiurl, project):
432 if p.startswith("_patchinfo:"):
435 if opts.force or not patchinfo:
436 print "Creating initial patchinfo..."
437 query='cmd=createpatchinfo'
439 query='&new_format=1'
441 query += "&name=" + args[0]
442 url = makeurl(apiurl, ['source', project], query=query)
444 for p in meta_get_packagelist(apiurl, project):
445 if p.startswith("_patchinfo:") or p.startswith("patchinfo"):
449 # Both conf.config['checkout_no_colon'] and conf.config['checkout_rooted']
451 if not os.path.exists(project_dir + "/" + patchinfo):
452 checkout_package(apiurl, project, patchinfo, prj_dir=project_dir)
454 filename = project_dir + "/" + patchinfo + "/_patchinfo"
457 @cmdln.alias('bsdevelproject')
458 @cmdln.option('-r', '--raw', default=False, action="store_true", help='print raw xml snippet')
459 def do_develproject(self, subcmd, opts, *args):
460 """${cmd_name}: print the bsdevelproject of a package
463 osc develproject PRJ PKG
467 args = slash_split(args)
468 apiurl = self.get_api_url()
472 project = store_read_project(os.curdir)
473 package = store_read_package(os.curdir)
475 raise oscerr.WrongArgs('need Project and Package')
478 m = show_package_meta(apiurl, project, package)
479 d = ET.fromstring(''.join(m)).find('devel')
483 print d.get('project')
486 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
487 help='affect only a given attribute')
488 @cmdln.option('--attribute-defaults', action='store_true',
489 help='include defined attribute defaults')
490 @cmdln.option('--attribute-project', action='store_true',
491 help='include project values, if missing in packages ')
492 @cmdln.option('-F', '--file', metavar='FILE',
493 help='read metadata from FILE, instead of opening an editor. '
494 '\'-\' denotes standard input. ')
495 @cmdln.option('-e', '--edit', action='store_true',
496 help='edit metadata')
497 @cmdln.option('-c', '--create', action='store_true',
498 help='create attribute without values')
499 @cmdln.option('-s', '--set', metavar='ATTRIBUTE_VALUES',
500 help='set attribute values')
501 @cmdln.option('--delete', action='store_true',
502 help='delete a pattern or attribute')
503 def do_meta(self, subcmd, opts, *args):
504 """${cmd_name}: Show meta information, or edit it
506 Show or edit build service metadata of type <prj|pkg|prjconf|user|pattern>.
508 This command displays metadata on buildservice objects like projects,
509 packages, or users. The type of metadata is specified by the word after
510 "meta", like e.g. "meta prj".
512 prj denotes metadata of a buildservice project.
513 prjconf denotes the (build) configuration of a project.
514 pkg denotes metadata of a buildservice package.
515 user denotes the metadata of a user.
516 pattern denotes installation patterns defined for a project.
518 To list patterns, use 'osc meta pattern PRJ'. An additional argument
519 will be the pattern file to view or edit.
521 With the --edit switch, the metadata can be edited. Per default, osc
522 opens the program specified by the environmental variable EDITOR with a
523 temporary file. Alternatively, content to be saved can be supplied via
524 the --file switch. If the argument is '-', input is taken from stdin:
525 osc meta prjconf home:user | sed ... | osc meta prjconf home:user -F -
527 When trying to edit a non-existing resource, it is created implicitly.
533 osc meta pkg PRJ PKG -e
534 osc meta attribute PRJ [PKG [SUBPACKAGE]] [--attribute ATTRIBUTE] [--create|--delete|--set [value_list]]
537 osc meta <prj|pkg|prjconf|user|pattern|attribute> ARGS...
538 osc meta <prj|pkg|prjconf|user|pattern|attribute> -e|--edit ARGS...
539 osc meta <prj|pkg|prjconf|user|pattern|attribute> -F|--file ARGS...
540 osc meta pattern --delete PRJ PATTERN
544 args = slash_split(args)
546 if not args or args[0] not in metatypes.keys():
547 raise oscerr.WrongArgs('Unknown meta type. Choose one of %s.' \
548 % ', '.join(metatypes))
553 apiurl = self.get_api_url()
556 min_args, max_args = 0, 2
557 elif cmd in ['pattern']:
558 min_args, max_args = 1, 2
559 elif cmd in ['attribute']:
560 min_args, max_args = 1, 3
561 elif cmd in ['prj', 'prjconf']:
562 min_args, max_args = 0, 1
564 min_args, max_args = 1, 1
566 if len(args) < min_args:
567 raise oscerr.WrongArgs('Too few arguments.')
568 if len(args) > max_args:
569 raise oscerr.WrongArgs('Too many arguments.')
573 if cmd in ['pkg', 'prj', 'prjconf' ]:
575 project = store_read_project(os.curdir)
581 package = store_read_package(os.curdir)
585 elif cmd == 'attribute':
591 if opts.attribute_project:
592 raise oscerr.WrongOptions('--attribute-project works only when also a package is given')
597 attributepath.append('source')
598 attributepath.append(project)
600 attributepath.append(package)
602 attributepath.append(subpackage)
603 attributepath.append('_attribute')
606 elif cmd == 'pattern':
612 # enforce pattern argument if needed
613 if opts.edit or opts.file:
614 raise oscerr.WrongArgs('A pattern file argument is required.')
617 if not opts.edit and not opts.file and not opts.delete and not opts.create and not opts.set:
619 sys.stdout.write(''.join(show_project_meta(apiurl, project)))
621 sys.stdout.write(''.join(show_package_meta(apiurl, project, package)))
622 elif cmd == 'attribute':
623 sys.stdout.write(''.join(show_attribute_meta(apiurl, project, package, subpackage, opts.attribute, opts.attribute_defaults, opts.attribute_project)))
624 elif cmd == 'prjconf':
625 sys.stdout.write(''.join(show_project_conf(apiurl, project)))
627 r = get_user_meta(apiurl, user)
629 sys.stdout.write(''.join(r))
630 elif cmd == 'pattern':
632 r = show_pattern_meta(apiurl, project, pattern)
634 sys.stdout.write(''.join(r))
636 r = show_pattern_metalist(apiurl, project)
638 sys.stdout.write('\n'.join(r) + '\n')
641 if opts.edit and not opts.file:
643 edit_meta(metatype='prj',
645 path_args=quote_plus(project),
649 'user': conf.get_apiurl_usr(apiurl)}))
651 edit_meta(metatype='pkg',
653 path_args=(quote_plus(project), quote_plus(package)),
657 'user': conf.get_apiurl_usr(apiurl)}))
658 elif cmd == 'prjconf':
659 edit_meta(metatype='prjconf',
661 path_args=quote_plus(project),
665 edit_meta(metatype='user',
667 path_args=(quote_plus(user)),
669 template_args=({'user': user}))
670 elif cmd == 'pattern':
671 edit_meta(metatype='pattern',
673 path_args=(project, pattern),
677 # create attribute entry
678 if (opts.create or opts.set) and cmd == 'attribute':
679 if not opts.attribute:
680 raise oscerr.WrongOptions('no attribute given to create')
683 opts.set = opts.set.replace('&', '&').replace('<', '<').replace('>', '>')
684 for i in opts.set.split(','):
685 values += '<value>%s</value>' % i
686 aname = opts.attribute.split(":")
687 d = '<attributes><attribute namespace=\'%s\' name=\'%s\' >%s</attribute></attributes>' % (aname[0], aname[1], values)
688 url = makeurl(apiurl, attributepath)
689 for data in streamfile(url, http_POST, data=d):
690 sys.stdout.write(data)
699 f = open(opts.file).read()
701 sys.exit('could not open file \'%s\'.' % opts.file)
704 edit_meta(metatype='prj',
708 path_args=quote_plus(project))
710 edit_meta(metatype='pkg',
714 path_args=(quote_plus(project), quote_plus(package)))
715 elif cmd == 'prjconf':
716 edit_meta(metatype='prjconf',
720 path_args=quote_plus(project))
722 edit_meta(metatype='user',
726 path_args=(quote_plus(user)))
727 elif cmd == 'pattern':
728 edit_meta(metatype='pattern',
732 path_args=(project, pattern))
737 path = metatypes[cmd]['path']
739 path = path % (project, pattern)
740 u = makeurl(apiurl, [path])
742 elif cmd == 'attribute':
743 if not opts.attribute:
744 raise oscerr.WrongOptions('no attribute given to create')
745 attributepath.append(opts.attribute)
746 u = makeurl(apiurl, attributepath)
747 for data in streamfile(u, http_DELETE):
748 sys.stdout.write(data)
750 raise oscerr.WrongOptions('The --delete switch is only for pattern metadata or attributes.')
753 # TODO: rewrite and consolidate the current submitrequest/createrequest "mess"
755 @cmdln.option('-m', '--message', metavar='TEXT',
756 help='specify message TEXT')
757 @cmdln.option('-r', '--revision', metavar='REV',
758 help='specify a certain source revision ID (the md5 sum) for the source package')
759 @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
760 help='Superseding another request by this one')
761 @cmdln.option('--nodevelproject', action='store_true',
762 help='do not follow a defined devel project ' \
763 '(primary project where a package is developed)')
764 @cmdln.option('--cleanup', action='store_true',
765 help='remove package if submission gets accepted (default for home:<id>:branch projects)')
766 @cmdln.option('--no-cleanup', action='store_true',
767 help='never remove source package on accept, but update its content')
768 @cmdln.option('--no-update', action='store_true',
769 help='never touch source package on accept (will break source links)')
770 @cmdln.option('-d', '--diff', action='store_true',
771 help='show diff only instead of creating the actual request')
772 @cmdln.option('--yes', action='store_true',
773 help='proceed without asking.')
775 @cmdln.alias("submitreq")
776 @cmdln.alias("submitpac")
777 def do_submitrequest(self, subcmd, opts, *args):
778 """${cmd_name}: Create request to submit source into another Project
780 [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration for information
783 See the "request" command for showing and modifing existing requests.
786 osc submitreq [OPTIONS]
787 osc submitreq [OPTIONS] DESTPRJ [DESTPKG]
788 osc submitreq [OPTIONS] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
790 osc submitpac ... is a shorthand for osc submitreq --cleanup ...
795 if opts.cleanup and opts.no_cleanup:
796 raise oscerr.WrongOptions('\'--cleanup\' and \'--no-cleanup\' are mutually exclusive')
798 src_update = conf.config['submitrequest_on_accept_action'] or None
799 # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
801 if subcmd == 'submitpac' and not opts.no_cleanup:
805 src_update = "cleanup"
806 elif opts.no_cleanup:
807 src_update = "update"
809 src_update = "noupdate"
811 args = slash_split(args)
813 # remove this block later again
814 oldcmds = ['create', 'list', 'log', 'show', 'decline', 'accept', 'delete', 'revoke']
815 if args and args[0] in oldcmds:
816 print >>sys.stderr, "************************************************************************"
817 print >>sys.stderr, "* WARNING: It looks that you are using this command with a *"
818 print >>sys.stderr, "* deprecated syntax. *"
819 print >>sys.stderr, "* Please run \"osc sr --help\" and \"osc rq --help\" *"
820 print >>sys.stderr, "* to see the new syntax. *"
821 print >>sys.stderr, "************************************************************************"
822 if args[0] == 'create':
828 raise oscerr.WrongArgs('Too many arguments.')
830 if len(args) > 0 and len(args) <= 2 and is_project_dir(os.getcwd()):
831 sys.exit('osc submitrequest from project directory is only working without target specs and for source linked files\n')
833 apiurl = self.get_api_url()
835 if len(args) == 0 and is_project_dir(os.getcwd()):
837 # submit requests for multiple packages are currently handled via multiple requests
838 # They could be also one request with multiple actions, but that avoids to accepts parts of it.
839 project = store_read_project(os.curdir)
845 # loop via all packages for checking their state
846 for p in meta_get_packagelist(apiurl, project):
847 if p.startswith("_patchinfo:") or p.startswith("patchinfo"):
850 # get _link info from server, that knows about the local state ...
851 u = makeurl(apiurl, ['source', project, p])
853 root = ET.parse(f).getroot()
854 linkinfo = root.find('linkinfo')
856 print "Package ", p, " is not a source link."
857 sys.exit("This is currently not supported.")
858 if linkinfo.get('error'):
859 print "Package ", p, " is a broken source link."
860 sys.exit("Please fix this first")
861 t = linkinfo.get('project')
863 if len(root.findall('entry')) > 1: # This is not really correct, but should work mostly
864 # Real fix is to ask the api if sources are modificated
865 # but there is no such call yet.
866 targetprojects.append(t)
868 print "Submitting package ", p
870 print " Skipping package ", p
872 print "Skipping package ", p, " since it is a source link pointing inside the project."
874 # was this project created by clone request ?
875 u = makeurl(apiurl, ['source', project, '_attribute', 'OBS:RequestCloned'])
877 root = ET.parse(f).getroot()
878 value = root.findtext('attribute/value')
885 print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
886 repl = raw_input('\nEverything fine? Can we create the requests ? (y/n) ')
887 if repl.lower() != 'y':
888 print >>sys.stderr, 'Aborted...'
889 raise oscerr.UserAbort()
892 # loop via all packages to do the action
894 result = create_submit_request(apiurl, project, p)
897 sys.exit("submit request creation failed")
898 sr_ids.append(result)
900 # create submit requests for all found patchinfos
904 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
907 for t in targetprojects:
908 s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \
909 (project, p, t, p, options_block)
913 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
914 (actionxml, cgi.escape(opts.message or ""))
915 u = makeurl(apiurl, ['request'], query='cmd=create')
916 f = http_POST(u, data=xml)
918 root = ET.parse(f).getroot()
919 sr_ids.append(root.get('id'))
921 print "Requests created: ",
927 print '\n\nThere are already following submit request: %s.' % \
928 ', '.join([str(i) for i in myreqs ])
929 repl = raw_input('\nSupersede the old requests? (y/n) ')
930 if repl.lower() == 'y':
932 change_request_state(apiurl, str(req), 'superseded',
933 'superseded by %s' % result, result)
935 sys.exit('Successfully finished')
938 # try using the working copy at hand
939 p = findpacs(os.curdir)[0]
940 src_project = p.prjname
943 if len(args) == 0 and p.islink():
944 dst_project = p.linkinfo.project
945 dst_package = p.linkinfo.package
947 dst_project = args[0]
949 dst_package = args[1]
951 dst_package = src_package
953 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
954 'Please provide it the target via commandline arguments.' % p.name)
956 modified = [i for i in p.filenamelist if not p.status(i) in (' ', '?', 'S')]
957 if len(modified) > 0:
958 print 'Your working copy has local modifications.'
959 repl = raw_input('Proceed without committing the local changes? (y|N) ')
961 raise oscerr.UserAbort()
963 # get the arguments from the commandline
964 src_project, src_package, dst_project = args[0:3]
966 dst_package = args[3]
968 dst_package = src_package
970 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
971 + self.get_cmd_help('request'))
973 if not opts.nodevelproject:
976 devloc = show_develproject(apiurl, dst_project, dst_package)
977 except urllib2.HTTPError:
978 print >>sys.stderr, """\
979 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
980 % (dst_project, dst_package)
984 dst_project != devloc and \
985 src_project != devloc:
987 A different project, %s, is defined as the place where development
988 of the package %s primarily takes place.
989 Please submit there instead, or use --nodevelproject to force direct submission.""" \
990 % (devloc, dst_package)
995 if opts.diff or not opts.message:
997 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
998 rdiff += server_diff(apiurl,
999 dst_project, dst_package, opts.revision,
1000 src_project, src_package, None, True)
1008 # Are there already requests to this package ?
1009 reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit', req_state=['new','review'])
1010 user = conf.get_apiurl_usr(apiurl)
1011 myreqs = [ i for i in reqs if i.state.who == user ]
1015 print 'There are already following submit request: %s.' % \
1016 ', '.join([i.reqid for i in myreqs ])
1017 repl = raw_input('Supersede the old requests? (y/n/c) ')
1018 if repl.lower() == 'c':
1019 print >>sys.stderr, 'Aborting'
1020 raise oscerr.UserAbort()
1022 if not opts.message:
1025 changes_re = re.compile(r'^--- .*\.changes ')
1026 for line in rdiff.split('\n'):
1027 if line.startswith('--- '):
1028 if changes_re.match(line):
1033 difflines.append(line)
1034 opts.message = edit_message(footer=rdiff, template='\n'.join(parse_diff_for_commit_message('\n'.join(difflines))))
1036 result = create_submit_request(apiurl,
1037 src_project, src_package,
1038 dst_project, dst_package,
1039 opts.message, orev=opts.revision, src_update=src_update)
1040 if repl.lower() == 'y':
1042 change_request_state(apiurl, req.reqid, 'superseded',
1043 'superseded by %s' % result, result)
1046 change_request_state(apiurl, opts.supersede, 'superseded',
1047 opts.message or '', result)
1049 print 'created request id', result
1051 def _actionparser(self, opt_str, value, parser):
1053 if not hasattr(parser.values, 'actiondata'):
1054 setattr(parser.values, 'actiondata', [])
1055 if parser.values.actions == None:
1056 parser.values.actions = []
1058 rargs = parser.rargs
1061 if ((arg[:2] == "--" and len(arg) > 2) or
1062 (arg[:1] == "-" and len(arg) > 1 and arg[1] != "-")):
1068 parser.values.actions.append(value[0])
1070 parser.values.actiondata.append(value)
1072 def _submit_request(self, args, opts, options_block):
1074 apiurl = self.get_api_url()
1075 if len(args) == 0 and is_project_dir(os.getcwd()):
1076 # submit requests for multiple packages are currently handled via multiple requests
1077 # They could be also one request with multiple actions, but that avoids to accepts parts of it.
1078 project = store_read_project(os.curdir)
1084 # loop via all packages for checking their state
1085 for p in meta_get_packagelist(apiurl, project):
1086 if p.startswith("_patchinfo:"):
1089 # get _link info from server, that knows about the local state ...
1090 u = makeurl(apiurl, ['source', project, p])
1092 root = ET.parse(f).getroot()
1093 linkinfo = root.find('linkinfo')
1094 if linkinfo == None:
1095 print "Package ", p, " is not a source link."
1096 sys.exit("This is currently not supported.")
1097 if linkinfo.get('error'):
1098 print "Package ", p, " is a broken source link."
1099 sys.exit("Please fix this first")
1100 t = linkinfo.get('project')
1104 rdiff = server_diff(apiurl, t, p, opts.revision, project, p, None, True)
1109 targetprojects.append(t)
1111 rdiffmsg.append("old: %s/%s\nnew: %s/%s\n%s" %(t, p, project, p,rdiff))
1113 print "Skipping package ", p, " since it has no difference with the target package."
1115 print "Skipping package ", p, " since it is a source link pointing inside the project."
1117 print ''.join(rdiffmsg)
1122 print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
1123 print "\nEverything fine? Can we create the requests ? [y/n]"
1124 if sys.stdin.read(1) != "y":
1125 sys.exit("Aborted...")
1127 # loop via all packages to do the action
1129 s = """<action type="submit"> <source project="%s" package="%s" rev="%s"/> <target project="%s" package="%s"/> %s </action>""" % \
1130 (project, p, opts.revision or show_upstream_rev(apiurl, project, p), t, p, options_block)
1133 # create submit requests for all found patchinfos
1135 for t in targetprojects:
1136 s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \
1137 (project, p, t, p, options_block)
1142 elif len(args) <= 2:
1143 # try using the working copy at hand
1144 p = findpacs(os.curdir)[0]
1145 src_project = p.prjname
1146 src_package = p.name
1147 if len(args) == 0 and p.islink():
1148 dst_project = p.linkinfo.project
1149 dst_package = p.linkinfo.package
1151 dst_project = args[0]
1153 dst_package = args[1]
1155 dst_package = src_package
1157 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
1158 'Please provide it the target via commandline arguments.' % p.name)
1160 modified = [i for i in p.filenamelist if p.status(i) != ' ' and p.status(i) != '?']
1161 if len(modified) > 0:
1162 print 'Your working copy has local modifications.'
1163 repl = raw_input('Proceed without committing the local changes? (y|N) ')
1166 elif len(args) >= 3:
1167 # get the arguments from the commandline
1168 src_project, src_package, dst_project = args[0:3]
1170 dst_package = args[3]
1172 dst_package = src_package
1174 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1175 + self.get_cmd_help('request'))
1177 if not opts.nodevelproject:
1180 devloc = show_develproject(apiurl, dst_project, dst_package)
1181 except urllib2.HTTPError:
1182 print >>sys.stderr, """\
1183 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
1184 % (dst_project, dst_package)
1188 dst_project != devloc and \
1189 src_project != devloc:
1191 A different project, %s, is defined as the place where development
1192 of the package %s primarily takes place.
1193 Please submit there instead, or use --nodevelproject to force direct submission.""" \
1194 % (devloc, dst_package)
1201 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
1202 rdiff += server_diff(apiurl,
1203 dst_project, dst_package, opts.revision,
1204 src_project, src_package, None, True)
1210 reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit', req_state=['new','review'])
1211 user = conf.get_apiurl_usr(apiurl)
1212 myreqs = [ i for i in reqs if i.state.who == user ]
1215 print 'You already created the following submit request: %s.' % \
1216 ', '.join([i.reqid for i in myreqs ])
1217 repl = raw_input('Supersede the old requests? (y/n/c) ')
1218 if repl.lower() == 'c':
1219 print >>sys.stderr, 'Aborting'
1222 actionxml = """<action type="submit"> <source project="%s" package="%s" rev="%s"/> <target project="%s" package="%s"/> %s </action>""" % \
1223 (src_project, src_package, opts.revision or show_upstream_rev(apiurl, src_project, src_package), dst_project, dst_package, options_block)
1224 if repl.lower() == 'y':
1226 change_request_state(apiurl, req.reqid, 'superseded',
1227 'superseded by %s' % result, result)
1230 change_request_state(apiurl, opts.supersede, 'superseded', '', result)
1232 #print 'created request id', result
1235 def _delete_request(self, args, opts):
1237 raise oscerr.WrongArgs('Please specify at least a project.')
1239 raise oscerr.WrongArgs('Too many arguments.')
1243 package = """package="%s" """ % (args[1])
1244 actionxml = """<action type="delete"> <target project="%s" %s/> </action> """ % (args[0], package)
1247 def _changedevel_request(self, args, opts):
1249 raise oscerr.WrongArgs('Too many arguments.')
1251 if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
1253 devel_project = store_read_project(wd)
1254 devel_package = package = store_read_package(wd)
1255 project = conf.config['getpac_default_project']
1258 raise oscerr.WrongArgs('Too few arguments.')
1260 devel_project = args[2]
1263 devel_package = package
1265 devel_package = args[3]
1267 actionxml = """ <action type="change_devel"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> </action> """ % \
1268 (devel_project, devel_package, project, package)
1272 def _add_me(self, args, opts):
1274 raise oscerr.WrongArgs('Too many arguments.')
1276 raise oscerr.WrongArgs('Too few arguments.')
1278 apiurl = self.get_api_url()
1280 user = conf.get_apiurl_usr(apiurl)
1283 actionxml = """ <action type="add_role"> <target project="%s" /> <person name="%s" role="%s" /> </action> """ % \
1284 (project, user, role)
1288 actionxml = """ <action type="add_role"> <target project="%s" package="%s" /> <person name="%s" role="%s" /> </action> """ % \
1289 (project, package, user, role)
1291 if get_user_meta(apiurl, user) == None:
1292 raise oscerr.WrongArgs('osc: an error occured.')
1296 def _add_user(self, args, opts):
1298 raise oscerr.WrongArgs('Too many arguments.')
1300 raise oscerr.WrongArgs('Too few arguments.')
1302 apiurl = self.get_api_url()
1307 actionxml = """ <action type="add_role"> <target project="%s" /> <person name="%s" role="%s" /> </action> """ % \
1308 (project, user, role)
1312 actionxml = """ <action type="add_role"> <target project="%s" package="%s" /> <person name="%s" role="%s" /> </action> """ % \
1313 (project, package, user, role)
1315 if get_user_meta(apiurl, user) == None:
1316 raise oscerr.WrongArgs('osc: an error occured.')
1320 def _add_group(self, args, opts):
1322 raise oscerr.WrongArgs('Too many arguments.')
1324 raise oscerr.WrongArgs('Too few arguments.')
1326 apiurl = self.get_api_url()
1331 actionxml = """ <action type="add_role"> <target project="%s" /> <group name="%s" role="%s" /> </action> """ % \
1332 (project, group, role)
1336 actionxml = """ <action type="add_role"> <target project="%s" package="%s" /> <group name="%s" role="%s" /> </action> """ % \
1337 (project, package, group, role)
1339 if get_group(apiurl, group) == None:
1340 raise oscerr.WrongArgs('osc: an error occured.')
1344 def _set_bugowner(self, args, opts):
1346 raise oscerr.WrongArgs('Too many arguments.')
1348 raise oscerr.WrongArgs('Too few arguments.')
1350 apiurl = self.get_api_url()
1357 if get_user_meta(apiurl, user) == None:
1358 raise oscerr.WrongArgs('osc: an error occured.')
1360 actionxml = """ <action type="set_bugowner"> <target project="%s" package="%s" /> <person name="%s" /> </action> """ % \
1361 (project, package, user)
1365 @cmdln.option('-a', '--action', action='callback', callback = _actionparser,dest = 'actions',
1366 help='specify action type of a request, can be : submit/delete/change_devel/add_role/set_bugowner')
1367 @cmdln.option('-m', '--message', metavar='TEXT',
1368 help='specify message TEXT')
1369 @cmdln.option('-r', '--revision', metavar='REV',
1370 help='for "create", specify a certain source revision ID (the md5 sum)')
1371 @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
1372 help='Superseding another request by this one')
1373 @cmdln.option('--nodevelproject', action='store_true',
1374 help='do not follow a defined devel project ' \
1375 '(primary project where a package is developed)')
1376 @cmdln.option('--cleanup', action='store_true',
1377 help='remove package if submission gets accepted (default for home:<id>:branch projects)')
1378 @cmdln.option('--no-cleanup', action='store_true',
1379 help='never remove source package on accept, but update its content')
1380 @cmdln.option('--no-update', action='store_true',
1381 help='never touch source package on accept (will break source links)')
1382 @cmdln.option('-d', '--diff', action='store_true',
1383 help='show diff only instead of creating the actual request')
1384 @cmdln.option('--yes', action='store_true',
1385 help='proceed without asking.')
1386 @cmdln.alias("creq")
1387 def do_createrequest(self, subcmd, opts, *args):
1388 """${cmd_name}: create multiple requests with a single command
1391 osc creq [OPTIONS] [
1392 -a submit SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
1393 -a delete PROJECT [PACKAGE]
1394 -a change_devel PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
1395 -a add_me ROLE PROJECT [PACKAGE]
1396 -a add_group GROUP ROLE PROJECT [PACKAGE]
1397 -a add_role USER ROLE PROJECT [PACKAGE]
1398 -a set_bugowner USER PROJECT [PACKAGE]
1401 Option -m works for all types of request, the rest work only for submit.
1403 osc creq -a submit -a delete home:someone:branches:openSUSE:Tools -a change_devel openSUSE:Tools osc home:someone:branches:openSUSE:Tools -m ok
1405 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.
1408 src_update = conf.config['submitrequest_on_accept_action'] or None
1409 # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
1411 src_update = "cleanup"
1412 elif opts.no_cleanup:
1413 src_update = "update"
1414 elif opts.no_update:
1415 src_update = "noupdate"
1419 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
1421 args = slash_split(args)
1423 apiurl = self.get_api_url()
1427 for ai in opts.actions:
1429 args = opts.actiondata[i]
1431 actionsxml += self._submit_request(args,opts, options_block)
1432 elif ai == 'delete':
1433 args = opts.actiondata[i]
1434 actionsxml += self._delete_request(args,opts)
1436 elif ai == 'change_devel':
1437 args = opts.actiondata[i]
1438 actionsxml += self._changedevel_request(args,opts)
1440 elif ai == 'add_me':
1441 args = opts.actiondata[i]
1442 actionsxml += self._add_me(args,opts)
1444 elif ai == 'add_group':
1445 args = opts.actiondata[i]
1446 actionsxml += self._add_group(args,opts)
1448 elif ai == 'add_role':
1449 args = opts.actiondata[i]
1450 actionsxml += self._add_user(args,opts)
1452 elif ai == 'set_bugowner':
1453 args = opts.actiondata[i]
1454 actionsxml += self._set_bugowner(args,opts)
1457 raise oscerr.WrongArgs('Unsupported action %s' % ai)
1458 if actionsxml == "":
1459 sys.exit('No actions need to be taken.')
1461 if not opts.message:
1462 opts.message = edit_message()
1465 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
1466 (actionsxml, cgi.escape(opts.message or ""))
1467 u = makeurl(apiurl, ['request'], query='cmd=create')
1468 f = http_POST(u, data=xml)
1470 root = ET.parse(f).getroot()
1471 return root.get('id')
1474 @cmdln.option('-m', '--message', metavar='TEXT',
1475 help='specify message TEXT')
1476 @cmdln.option('-r', '--role', metavar='role', default='maintainer',
1477 help='specify user role (default: maintainer)')
1478 @cmdln.alias("reqmaintainership")
1479 @cmdln.alias("reqms")
1480 def do_requestmaintainership(self, subcmd, opts, *args):
1481 """${cmd_name}: requests to add user as maintainer
1484 osc requestmaintainership # for current user in checked out package
1485 osc requestmaintainership USER # for specified user in checked out package
1486 osc requestmaintainership PROJECT PACKAGE # for current user
1487 osc requestmaintainership PROJECT PACKAGE USER # request for specified user
1492 args = slash_split(args)
1493 apiurl = self.get_api_url()
1498 user = conf.get_apiurl_usr(apiurl)
1499 elif len(args) == 3:
1503 elif len(args) < 2 and is_package_dir(os.curdir):
1504 project = store_read_project(os.curdir)
1505 package = store_read_package(os.curdir)
1507 user = conf.get_apiurl_usr(apiurl)
1511 raise oscerr.WrongArgs('Wrong number of arguments.')
1513 if not opts.role in ('maintainer', 'bugowner'):
1514 raise oscerr.WrongOptions('invalid \'--role\': either specify \'maintainer\' or \'bugowner\'')
1515 if not opts.message:
1516 opts.message = edit_message()
1519 r.add_action('add_role', tgt_project=project, tgt_package=package,
1520 person_name=user, person_role=opts.role)
1521 r.description = cgi.escape(opts.message or '')
1525 @cmdln.option('-m', '--message', metavar='TEXT',
1526 help='specify message TEXT')
1528 @cmdln.alias("dropreq")
1529 @cmdln.alias("droprequest")
1530 @cmdln.alias("deletereq")
1531 def do_deleterequest(self, subcmd, opts, *args):
1532 """${cmd_name}: Request to delete (or 'drop') a package or project
1535 osc deletereq [-m TEXT] # works in checked out project/package
1536 osc deletereq [-m TEXT] PROJECT [PACKAGE]
1541 args = slash_split(args)
1547 raise oscerr.WrongArgs('Too many arguments.')
1548 elif len(args) == 1:
1550 elif len(args) == 2:
1553 elif is_project_dir(os.getcwd()):
1554 project = store_read_project(os.curdir)
1555 elif is_package_dir(os.getcwd()):
1556 project = store_read_project(os.curdir)
1557 package = store_read_package(os.curdir)
1559 raise oscerr.WrongArgs('Please specify at least a project.')
1561 if not opts.message:
1563 if package is not None:
1564 footer=textwrap.TextWrapper(width = 66).fill(
1565 'please explain why you like to delete package %s of project %s'
1566 % (package,project))
1568 footer=textwrap.TextWrapper(width = 66).fill(
1569 'please explain why you like to delete project %s' % project)
1570 opts.message = edit_message(footer)
1573 r.add_action('delete', tgt_project=project, tgt_package=package)
1574 r.description = cgi.escape(opts.message)
1575 r.create(self.get_api_url())
1579 @cmdln.option('-m', '--message', metavar='TEXT',
1580 help='specify message TEXT')
1582 @cmdln.alias("changedevelreq")
1583 def do_changedevelrequest(self, subcmd, opts, *args):
1584 """${cmd_name}: Create request to change the devel package definition.
1586 [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration
1587 for information on this topic.]
1589 See the "request" command for showing and modifing existing requests.
1591 osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
1595 if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
1597 devel_project = store_read_project(wd)
1598 devel_package = package = store_read_package(wd)
1599 project = conf.config['getpac_default_project']
1601 raise oscerr.WrongArgs('Too few arguments.')
1603 raise oscerr.WrongArgs('Too many arguments.')
1605 devel_project = args[2]
1608 devel_package = package
1610 devel_package = args[3]
1612 if not opts.message:
1614 footer=textwrap.TextWrapper(width = 66).fill(
1615 'please explain why you like to change the devel project of %s/%s to %s/%s'
1616 % (project,package,devel_project,devel_package))
1617 opts.message = edit_message(footer)
1620 r.add_action('change_devel', src_project=devel_project, src_package=devel_package,
1621 tgt_project=project, tgt_package=package)
1622 r.description = cgi.escape(opts.message)
1623 r.create(self.get_api_url())
1627 @cmdln.option('-d', '--diff', action='store_true',
1628 help='generate a diff')
1629 @cmdln.option('-u', '--unified', action='store_true',
1630 help='output the diff in the unified diff format')
1631 @cmdln.option('-m', '--message', metavar='TEXT',
1632 help='specify message TEXT')
1633 @cmdln.option('-t', '--type', metavar='TYPE',
1634 help='limit to requests which contain a given action type (submit/delete/change_devel)')
1635 @cmdln.option('-a', '--all', action='store_true',
1636 help='all states. Same as\'-s all\'')
1637 @cmdln.option('-f', '--force', action='store_true',
1638 help='enforce state change, can be used to ignore open reviews')
1639 @cmdln.option('-s', '--state', default='', # default is 'all' if no args given, 'new,review' otherwise
1640 help='only list requests in one of the comma separated given states (new/review/accepted/revoked/declined) or "all" [default="new,review", or "all", if no args given]')
1641 @cmdln.option('-D', '--days', metavar='DAYS',
1642 help='only list requests in state "new" or changed in the last DAYS. [default=%(request_list_days)s]')
1643 @cmdln.option('-U', '--user', metavar='USER',
1644 help='requests or reviews limited for the specified USER')
1645 @cmdln.option('-G', '--group', metavar='GROUP',
1646 help='requests or reviews limited for the specified GROUP')
1647 @cmdln.option('-P', '--project', metavar='PROJECT',
1648 help='requests or reviews limited for the specified PROJECT')
1649 @cmdln.option('-p', '--package', metavar='PACKAGE',
1650 help='requests or reviews limited for the specified PACKAGE, requires also a PROJECT')
1651 @cmdln.option('-b', '--brief', action='store_true', default=False,
1652 help='print output in list view as list subcommand')
1653 @cmdln.option('-M', '--mine', action='store_true',
1654 help='only show requests created by yourself')
1655 @cmdln.option('-B', '--bugowner', action='store_true',
1656 help='also show requests about packages where I am bugowner')
1657 @cmdln.option('-e', '--edit', action='store_true',
1658 help='edit a submit action')
1659 @cmdln.option('-i', '--interactive', action='store_true',
1660 help='interactive review of request')
1661 @cmdln.option('--non-interactive', action='store_true',
1662 help='non-interactive review of request')
1663 @cmdln.option('--exclude-target-project', action='append',
1664 help='exclude target project from request list')
1665 @cmdln.option('--involved-projects', action='store_true',
1666 help='show all requests for project/packages where USER is involved')
1667 @cmdln.option('--source-buildstatus', action='store_true',
1668 help='print the buildstatus of the source package (only works with "show")')
1670 @cmdln.alias("review")
1671 # FIXME: rewrite this mess and split request and review
1672 def do_request(self, subcmd, opts, *args):
1673 """${cmd_name}: Show or modify requests and reviews
1675 [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration
1676 for information on this topic.]
1678 The 'request' command has the following sub commands:
1680 "list" lists open requests attached to a project or package or person.
1681 Uses the project/package of the current directory if none of
1682 -M, -U USER, project/package are given.
1684 "log" will show the history of the given ID
1686 "show" will show the request itself, and generate a diff for review, if
1687 used with the --diff option. The keyword show can be omitted if the ID is numeric.
1689 "decline" will change the request state to "declined"
1691 "reopen" will set the request back to new or review.
1693 "supersede" will supersede one request with another existing one.
1695 "revoke" will set the request state to "revoked"
1697 "accept" will change the request state to "accepted" and will trigger
1698 the actual submit process. That would normally be a server-side copy of
1699 the source package to the target package.
1701 "checkout" will checkout the request's source package ("submit" requests only).
1703 The 'review' command has the following sub commands:
1705 "list" lists open requests that need to be reviewed by the
1706 specified user or group
1708 "add" adds a person or group as reviewer to a request
1710 "accept" mark the review positive
1712 "decline" mark the review negative. A negative review will
1713 decline the request.
1716 osc request list [-M] [-U USER] [-s state] [-D DAYS] [-t type] [-B] [PRJ [PKG]]
1718 osc request [show] [-d] [-b] ID
1720 osc request accept [-m TEXT] ID
1721 osc request decline [-m TEXT] ID
1722 osc request revoke [-m TEXT] ID
1723 osc request reopen [-m TEXT] ID
1724 osc request supersede [-m TEXT] ID SUPERSEDE_ID
1725 osc request approvenew [-m TEXT] PROJECT
1727 osc request checkout/co ID
1728 osc request clone [-m TEXT] ID
1730 osc review list [-U USER] [-G GROUP] [-s state]
1731 osc review add [-m TEXT] [-U USER] [-G GROUP] ID
1732 osc review accept [-m TEXT] ID
1733 osc review decline [-m TEXT] ID
1734 osc review reopen [-m TEXT] ID
1735 osc review supersede [-m TEXT] ID SUPERSEDE_ID
1740 args = slash_split(args)
1742 if opts.all and opts.state:
1743 raise oscerr.WrongOptions('Sorry, the options \'--all\' and \'--state\' ' \
1744 'are mutually exclusive.')
1745 if opts.mine and opts.user:
1746 raise oscerr.WrongOptions('Sorry, the options \'--user\' and \'--mine\' ' \
1747 'are mutually exclusive.')
1748 if opts.interactive and opts.non_interactive:
1749 raise oscerr.WrongOptions('Sorry, the options \'--interactive\' and ' \
1750 '\'--non-interactive\' are mutually exclusive')
1755 if opts.state == '':
1758 if opts.state == '':
1759 opts.state = 'new,review'
1761 if args[0] == 'help':
1762 return self.do_help(['help', 'request'])
1764 cmds = [ 'list', 'log', 'show', 'decline', 'reopen', 'clone', 'accept', 'approvenew', 'wipe', 'supersede', 'revoke', 'checkout', 'co' ]
1765 if subcmd != 'review' and args[0] not in cmds:
1766 raise oscerr.WrongArgs('Unknown request action %s. Choose one of %s.' \
1767 % (args[0],', '.join(cmds)))
1768 cmds = [ 'list', 'add', 'decline', 'accept', 'reopen', 'supersede' ]
1769 if subcmd == 'review' and args[0] not in cmds:
1770 raise oscerr.WrongArgs('Unknown review action %s. Choose one of %s.' \
1771 % (args[0],', '.join(cmds)))
1776 apiurl = self.get_api_url()
1779 min_args, max_args = 0, 2
1780 elif cmd in ['supersede']:
1781 min_args, max_args = 2, 2
1783 min_args, max_args = 1, 1
1784 if len(args) < min_args:
1785 raise oscerr.WrongArgs('Too few arguments.')
1786 if len(args) > max_args:
1787 raise oscerr.WrongArgs('Too many arguments.')
1788 if cmd in ['add'] and not opts.user and not opts.group:
1789 opts.user = conf.get_apiurl_usr(apiurl)
1793 if cmd == 'list' or cmd == 'approvenew':
1798 elif not opts.mine and not opts.user:
1800 project = store_read_project(os.curdir)
1801 package = store_read_package(os.curdir)
1802 except oscerr.NoWorkingCopy:
1807 elif cmd == 'supersede':
1809 supersedid = args[1]
1810 elif cmd in ['log', 'add', 'show', 'decline', 'reopen', 'clone', 'accept', 'wipe', 'revoke', 'checkout', 'co']:
1813 # clone all packages from a given request
1814 if cmd in ['clone']:
1815 # should we force a message?
1816 print 'Cloned packages are available in project: %s' % clone_request(apiurl, reqid, opts.message)
1818 # add new reviewer to existing request
1819 elif cmd in ['add'] and subcmd == 'review':
1820 query = { 'cmd': 'addreview' }
1822 query['by_user'] = opts.user
1824 query['by_group'] = opts.group
1826 query['by_project'] = opts.project
1828 query['by_package'] = opts.package
1829 url = makeurl(apiurl, ['request', reqid], query)
1830 if not opts.message:
1831 opts.message = edit_message()
1832 r = http_POST(url, data=opts.message)
1833 print ET.parse(r).getroot().get('code')
1835 # list and approvenew
1836 elif cmd == 'list' or cmd == 'approvenew':
1837 states = ('new', 'accepted', 'revoked', 'declined', 'review', 'superseded')
1839 if cmd == 'approvenew':
1841 results = get_request_list(apiurl, project, package, '', ['new'])
1843 state_list = opts.state.split(',')
1845 state_list = ['all']
1846 if subcmd == 'review':
1847 state_list = ['review']
1848 elif opts.state == 'all':
1849 state_list = ['all']
1851 for s in state_list:
1852 if not s in states and not s == 'all':
1853 raise oscerr.WrongArgs('Unknown state \'%s\', try one of %s' % (s, ','.join(states)))
1855 who = conf.get_apiurl_usr(apiurl)
1859 ## FIXME -B not implemented!
1861 if (self.options.debug):
1862 print 'list: option --bugowner ignored: not impl.'
1864 if subcmd == 'review':
1865 # FIXME: do the review list for the user and for all groups he belong to
1866 results = get_review_list(apiurl, project, package, who, opts.group, opts.project, opts.package, state_list)
1868 if opts.involved_projects:
1869 who = who or conf.get_apiurl_usr(apiurl)
1870 results = get_user_projpkgs_request_list(apiurl, who, req_state=state_list,
1871 req_type=opts.type, exclude_projects=opts.exclude_target_project or [])
1873 results = get_request_list(apiurl, project, package, who,
1874 state_list, opts.type, opts.exclude_target_project or [])
1876 results.sort(reverse=True)
1878 days = opts.days or conf.config['request_list_days']
1885 since = time.strftime('%Y-%m-%dT%H:%M:%S',time.localtime(time.time()-days*24*3600))
1888 ## bs has received 2009-09-20 a new xquery compare() function
1889 ## which allows us to limit the list inside of get_request_list
1890 ## That would be much faster for coolo. But counting the remainder
1891 ## would not be possible with current xquery implementation.
1892 ## Workaround: fetch all, and filter on client side.
1894 ## FIXME: date filtering should become implemented on server side
1895 for result in results:
1896 if days == 0 or result.state.when > since or result.state.name == 'new':
1897 if (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive:
1898 request_interactive_review(apiurl, result)
1900 print result.list_view(), '\n'
1904 print "There are %d requests older than %s days.\n" % (skipped, days)
1906 if cmd == 'approvenew':
1907 print "\n *** Approve them all ? [y/n] ***"
1908 if sys.stdin.read(1) == "y":
1910 if not opts.message:
1911 opts.message = edit_message()
1912 for result in results:
1913 print result.reqid, ": ",
1914 r = change_request_state(apiurl,
1915 result.reqid, 'accepted', opts.message or '', force=opts.force)
1916 print 'Result of change request state: %s' % r
1918 print >>sys.stderr, 'Aborted...'
1919 raise oscerr.UserAbort()
1922 for l in get_request_log(apiurl, reqid):
1927 r = get_request(apiurl, reqid)
1931 if not r.get_actions('submit'):
1932 raise oscerr.WrongOptions('\'--edit\' not possible ' \
1933 '(request has no \'submit\' action)')
1934 return request_interactive_review(apiurl, r, 'e')
1935 elif (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive:
1936 return request_interactive_review(apiurl, r)
1939 if opts.source_buildstatus:
1940 sr_actions = r.get_actions('submit')
1942 raise oscerr.WrongOptions( '\'--source-buildstatus\' not possible ' \
1943 '(request has no \'submit\' actions)')
1944 for action in sr_actions:
1945 print 'Buildstatus for \'%s/%s\':' % (action.src_project, action.src_package)
1946 print '\n'.join(get_results(apiurl, action.src_project, action.src_package))
1950 # works since OBS 2.1
1951 diff = request_diff(apiurl, reqid)
1952 except urllib2.HTTPError, e:
1953 # for OBS 2.0 and before
1954 sr_actions = r.get_actions('submit')
1956 raise oscerr.WrongOptions('\'--diff\' not possible (request has no \'submit\' actions)')
1957 for action in sr_actions:
1958 diff += 'old: %s/%s\nnew: %s/%s\n' % (action.src_project, action.src_package,
1959 action.tgt_project, action.tgt_package)
1960 diff += submit_action_diff(apiurl, action)
1965 elif cmd == 'checkout' or cmd == 'co':
1966 r = get_request(apiurl, reqid)
1967 sr_actions = r.get_actions('submit')
1969 raise oscerr.WrongArgs('\'checkout\' not possible (request has no \'submit\' actions)')
1970 for action in sr_actions:
1971 checkout_package(apiurl, action.src_project, action.src_package, \
1972 action.src_rev, expand_link=True, prj_dir=action.src_project)
1975 state_map = {'reopen' : 'new', 'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked', 'supersede' : 'superseded'}
1976 # Change review state only
1977 if subcmd == 'review':
1978 if not opts.message:
1979 opts.message = edit_message()
1980 if cmd in ['accept', 'decline', 'reopen', 'supersede']:
1981 r = change_review_state(apiurl,
1982 reqid, state_map[cmd], conf.get_apiurl_usr(apiurl), opts.group, opts.project, opts.package, opts.message or '', supersed=supersedid)
1984 # Change state of entire request
1985 elif cmd in ['reopen', 'accept', 'decline', 'wipe', 'revoke', 'supersede']:
1986 rq = get_request(apiurl, reqid)
1987 if rq.state.name == state_map[cmd]:
1988 repl = raw_input("\n *** The state of the request (#%s) is already '%s'. Change state anyway? [y/n] *** " % (reqid, rq.state.name))
1989 if repl.lower() != 'y':
1990 print >>sys.stderr, 'Aborted...'
1991 raise oscerr.UserAbort()
1993 if not opts.message:
1994 tmpl = change_request_state_template(rq, state_map[cmd])
1995 opts.message = edit_message(template=tmpl)
1996 r = change_request_state(apiurl,
1997 reqid, state_map[cmd], opts.message or '', supersed=supersedid, force=opts.force)
1998 print 'Result of change request state: %s' % r
2000 # editmeta and its aliases are all depracated
2001 @cmdln.alias("editprj")
2002 @cmdln.alias("createprj")
2003 @cmdln.alias("editpac")
2004 @cmdln.alias("createpac")
2005 @cmdln.alias("edituser")
2006 @cmdln.alias("usermeta")
2008 def do_editmeta(self, subcmd, opts, *args):
2011 Obsolete command to edit metadata. Use 'meta' now.
2013 See the help output of 'meta'.
2017 print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
2018 print >>sys.stderr, 'See \'osc help meta\'.'
2019 #self.do_help([None, 'meta'])
2023 @cmdln.option('-r', '--revision', metavar='rev',
2024 help='use the specified revision.')
2025 @cmdln.option('-R', '--use-plain-revision', action='store_true',
2026 help='Don\'t expand revsion based on baserev, the revision which was used when commit happened.')
2027 @cmdln.option('-b', '--use-baserev', action='store_true',
2028 help='Use the revisions which exists when the original commit happend and don\'t try to merge later commits.')
2029 @cmdln.option('-u', '--unset', action='store_true',
2030 help='remove revision in link, it will point always to latest revision')
2031 def do_setlinkrev(self, subcmd, opts, *args):
2032 """${cmd_name}: Updates a revision number in a source link.
2034 This command adds or updates a specified revision number in a source link.
2035 The current revision of the source is used, if no revision number is specified.
2039 osc setlinkrev PROJECT [PACKAGE]
2043 args = slash_split(args)
2044 apiurl = self.get_api_url()
2048 if opts.use_plain_revision:
2050 if opts.use_baserev:
2053 rev = parseRevisionOption(opts.revision)[0] or ''
2058 p = findpacs(os.curdir)[0]
2063 sys.exit('Local directory is no checked out source link package, aborting')
2064 elif len(args) == 2:
2067 elif len(args) == 1:
2070 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
2071 + self.get_cmd_help('setlinkrev'))
2074 packages = [package]
2076 packages = meta_get_packagelist(apiurl, project)
2079 print 'setting revision for package %s' % p
2080 set_link_rev(apiurl, project, p, revision=rev, xsrcmd5=xsrcmd5, baserev=baserev)
2083 def do_linktobranch(self, subcmd, opts, *args):
2084 """${cmd_name}: Convert a package containing a classic link with patch to a branch
2086 This command tells the server to convert a _link with or without a project.diff
2087 to a branch. This is a full copy with a _link file pointing to the branched place.
2090 osc linktobranch # can be used in checked out package
2091 osc linktobranch PROJECT PACKAGE
2094 args = slash_split(args)
2095 apiurl = self.get_api_url()
2099 project = store_read_project(wd)
2100 package = store_read_package(wd)
2101 update_local_dir = True
2103 raise oscerr.WrongArgs('Too few arguments (required none or two)')
2105 raise oscerr.WrongArgs('Too many arguments (required none or two)')
2109 update_local_dir = False
2112 link_to_branch(apiurl, project, package)
2113 if update_local_dir:
2115 pac.update(rev=pac.latest_rev())
2118 @cmdln.option('-m', '--message', metavar='TEXT',
2119 help='specify message TEXT')
2120 def do_detachbranch(self, subcmd, opts, *args):
2121 """${cmd_name}: replace a link with its expanded sources
2123 If a package is a link it is replaced with its expanded sources. The link
2124 does not exist anymore.
2127 osc detachbranch # can be used in package working copy
2128 osc detachbranch PROJECT PACKAGE
2131 args = slash_split(args)
2132 apiurl = self.get_api_url()
2134 project = store_read_project(os.curdir)
2135 package = store_read_package(os.curdir)
2136 elif len(args) == 2:
2137 project, package = args
2139 raise oscerr.WrongArgs('Too many arguments (required none or two)')
2141 raise oscerr.WrongArgs('Too few arguments (required none or two)')
2144 copy_pac(apiurl, project, package, apiurl, project, package, expand=True, comment=opts.message)
2145 except urllib2.HTTPError, e:
2146 root = ET.fromstring(show_files_meta(apiurl, project, package, 'latest', expand=False))
2148 li.read(root.find('linkinfo'))
2149 if li.islink() and li.haserror():
2150 raise oscerr.LinkExpandError(project, package, li.error)
2151 elif not li.islink():
2152 print >>sys.stderr, 'package \'%s/%s\' is no link' % (project, package)
2157 @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'],
2158 help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.')
2159 @cmdln.option('-c', '--current', action='store_true',
2160 help='link fixed against current revision.')
2161 @cmdln.option('-r', '--revision', metavar='rev',
2162 help='link the specified revision.')
2163 @cmdln.option('-f', '--force', action='store_true',
2164 help='overwrite an existing link file if it is there.')
2165 @cmdln.option('-d', '--disable-publish', action='store_true',
2166 help='disable publishing of the linked package')
2167 def do_linkpac(self, subcmd, opts, *args):
2168 """${cmd_name}: "Link" a package to another package
2170 A linked package is a clone of another package, but plus local
2171 modifications. It can be cross-project.
2173 The DESTPAC name is optional; the source packages' name will be used if
2176 Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
2178 To add a patch, add the patch as file and add it to the _link file.
2179 You can also specify text which will be inserted at the top of the spec file.
2181 See the examples in the _link file.
2183 NOTE: In case you are not aware about the difference of 'linkpac' and 'branch' command
2184 you should use the 'branch' command by default.
2187 osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
2191 args = slash_split(args)
2192 apiurl = self.get_api_url()
2194 if not args or len(args) < 3:
2195 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
2196 + self.get_cmd_help('linkpac'))
2198 rev, dummy = parseRevisionOption(opts.revision)
2200 src_project = args[0]
2201 src_package = args[1]
2202 dst_project = args[2]
2204 dst_package = args[3]
2206 dst_package = src_package
2208 if src_project == dst_project and src_package == dst_package:
2209 raise oscerr.WrongArgs('Error: source and destination are the same.')
2211 if src_project == dst_project and not opts.cicount:
2212 # in this case, the user usually wants to build different spec
2213 # files from the same source
2214 opts.cicount = "copy"
2217 rev = show_upstream_rev(apiurl, src_project, src_package)
2219 if rev and not checkRevision(src_project, src_package, rev):
2220 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2223 link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish)
2225 @cmdln.option('--nosources', action='store_true',
2226 help='ignore source packages when copying build results to destination project')
2227 @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]',
2228 help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]')
2229 @cmdln.option('-d', '--disable-publish', action='store_true',
2230 help='disable publishing of the aggregated package')
2231 def do_aggregatepac(self, subcmd, opts, *args):
2232 """${cmd_name}: "Aggregate" a package to another package
2234 Aggregation of a package means that the build results (binaries) of a
2235 package are basically copied into another project.
2236 This can be used to make packages available from building that are
2237 needed in a project but available only in a different project. Note
2238 that this is done at the expense of disk space. See
2239 http://en.opensuse.org/openSUSE:Build_Service_Tips_and_Tricks#link_and_aggregate
2240 for more information.
2242 The DESTPAC name is optional; the source packages' name will be used if
2246 osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
2250 args = slash_split(args)
2252 if not args or len(args) < 3:
2253 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
2254 + self.get_cmd_help('aggregatepac'))
2256 src_project = args[0]
2257 src_package = args[1]
2258 dst_project = args[2]
2260 dst_package = args[3]
2262 dst_package = src_package
2264 if src_project == dst_project and src_package == dst_package:
2265 raise oscerr.WrongArgs('Error: source and destination are the same.')
2269 for pair in opts.map_repo.split(','):
2270 src_tgt = pair.split('=')
2271 if len(src_tgt) != 2:
2272 raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo)
2273 repo_map[src_tgt[0]] = src_tgt[1]
2275 aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish, opts.nosources)
2278 @cmdln.option('-c', '--client-side-copy', action='store_true',
2279 help='do a (slower) client-side copy')
2280 @cmdln.option('-k', '--keep-maintainers', action='store_true',
2281 help='keep original maintainers. Default is remove all and replace with the one calling the script.')
2282 @cmdln.option('-d', '--keep-develproject', action='store_true',
2283 help='keep develproject tag in the package metadata')
2284 @cmdln.option('-r', '--revision', metavar='rev',
2285 help='link the specified revision.')
2286 @cmdln.option('-t', '--to-apiurl', metavar='URL',
2287 help='URL of destination api server. Default is the source api server.')
2288 @cmdln.option('-m', '--message', metavar='TEXT',
2289 help='specify message TEXT')
2290 @cmdln.option('-e', '--expand', action='store_true',
2291 help='if the source package is a link then copy the expanded version of the link')
2292 def do_copypac(self, subcmd, opts, *args):
2293 """${cmd_name}: Copy a package
2295 A way to copy package to somewhere else.
2297 It can be done across buildservice instances, if the -t option is used.
2298 In that case, a client-side copy and link expansion are implied.
2300 Using --client-side-copy always involves downloading all files, and
2301 uploading them to the target.
2303 The DESTPAC name is optional; the source packages' name will be used if
2307 osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
2311 args = slash_split(args)
2313 if not args or len(args) < 3:
2314 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
2315 + self.get_cmd_help('copypac'))
2317 src_project = args[0]
2318 src_package = args[1]
2319 dst_project = args[2]
2321 dst_package = args[3]
2323 dst_package = src_package
2325 src_apiurl = conf.config['apiurl']
2327 dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl)
2329 dst_apiurl = src_apiurl
2331 if src_apiurl != dst_apiurl:
2332 opts.client_side_copy = True
2335 rev, dummy = parseRevisionOption(opts.revision)
2338 comment = opts.message
2341 rev = show_upstream_rev(src_apiurl, src_project, src_package)
2342 comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev )
2344 if src_project == dst_project and \
2345 src_package == dst_package and \
2347 src_apiurl == dst_apiurl:
2348 raise oscerr.WrongArgs('Source and destination are the same.')
2350 r = copy_pac(src_apiurl, src_project, src_package,
2351 dst_apiurl, dst_project, dst_package,
2352 client_side_copy=opts.client_side_copy,
2353 keep_maintainers=opts.keep_maintainers,
2354 keep_develproject=opts.keep_develproject,
2361 @cmdln.option('-m', '--message', metavar='TEXT',
2362 help='specify message TEXT')
2363 def do_releaserequest(self, subcmd, opts, *args):
2364 """${cmd_name}: Create a request for releasing a maintenance update.
2366 [See http://doc.opensuse.org/products/draft/OBS/obs-reference-guide/cha.obs.maintenance_setup.html
2367 for information on this topic.]
2369 This command is used by the maintence team to start the release process of a maintenance update.
2370 This includes usually testing based on the defined reviewers of the update project.
2373 osc releaserequest [ SOURCEPROJECT ]
2377 # FIXME: additional parameters can be a certain repo list to create a partitial release
2379 args = slash_split(args)
2380 apiurl = self.get_api_url()
2382 source_project = None
2385 raise oscerr.WrongArgs('Too many arguments.')
2387 if len(args) == 0 and is_project_dir(os.curdir):
2388 source_project = store_read_project(os.curdir)
2389 elif len(args) == 0:
2390 raise oscerr.WrongArgs('Too few arguments.')
2392 source_project = args[0]
2394 if not opts.message:
2395 opts.message = edit_message()
2397 r = create_release_request(apiurl, source_project, opts.message)
2402 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
2403 help='Use this attribute to find default maintenance project (default is OBS:Maintenance)')
2404 @cmdln.option('-m', '--message', metavar='TEXT',
2405 help='specify message TEXT')
2406 def do_maintenancerequest(self, subcmd, opts, *args):
2407 """${cmd_name}: Create a request for starting a maintenance incident.
2409 [See http://doc.opensuse.org/products/draft/OBS/obs-reference-guide/cha.obs.maintenance_setup.html
2410 for information on this topic.]
2412 This command is asking the maintence team to start a maintence incident based on a
2413 created maintenance update. Please see the "mbranch" command on how to create such a project and
2414 the "patchinfo" command how add the required maintenance update informations.
2417 osc maintenancerequest [ SOURCEPROJECT [ TARGETPROJECT ] ]
2421 args = slash_split(args)
2422 apiurl = self.get_api_url()
2423 attribute = "OBS:Maintenance" # default attribute as defined in api code.
2425 attribute = opts.attribute
2427 source_project = target_project = None
2430 raise oscerr.WrongArgs('Too many arguments.')
2432 if len(args) == 0 and is_project_dir(os.curdir):
2433 source_project = store_read_project(os.curdir)
2434 elif len(args) == 0:
2435 raise oscerr.WrongArgs('Too few arguments.')
2437 source_project = args[0]
2440 target_project = args[1]
2442 xpath = 'attribute/@name = \'%s\'' % attribute
2443 res = search(apiurl, project_id=xpath)
2444 root = res['project_id']
2445 project = root.find('project')
2447 sys.exit('Unable to find defined OBS:Maintenance project on server.')
2448 target_project = project.get('name')
2449 print 'Using target project \'%s\'' % target_project
2451 if not opts.message:
2452 opts.message = edit_message()
2454 r = create_maintenance_request(apiurl, source_project, target_project, opts.message)
2458 @cmdln.option('-c', '--checkout', action='store_true',
2459 help='Checkout branched package afterwards ' \
2460 '(\'osc bco\' is a shorthand for this option)' )
2461 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
2462 help='Use this attribute to find affected packages (default is OBS:Maintained)')
2463 @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE',
2464 help='Use this attribute to find update projects (default is OBS:UpdateProject) ')
2465 def do_mbranch(self, subcmd, opts, *args):
2466 """${cmd_name}: Multiple branch of a package
2468 [See http://en.opensuse.org/openSUSE:Build_Service_Concept_Maintenance
2469 for information on this topic.]
2471 This command is used for creating multiple links of defined version of a package
2472 in one project. This is esp. used for maintenance updates.
2474 The branched package will live in
2475 home:USERNAME:branches:ATTRIBUTE:PACKAGE
2476 if nothing else specified.
2479 osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ]
2482 args = slash_split(args)
2483 apiurl = self.get_api_url()
2486 maintained_attribute = conf.config['maintained_attribute']
2487 maintained_update_project_attribute = conf.config['maintained_update_project_attribute']
2489 if not len(args) or len(args) > 2:
2490 raise oscerr.WrongArgs('Wrong number of arguments.')
2496 r = attribute_branch_pkg(apiurl, maintained_attribute, maintained_update_project_attribute, \
2500 print >>sys.stderr, 'ERROR: Attribute branch call came not back with a project.'
2503 print "Project " + r + " created."
2506 Project.init_project(apiurl, r, r, conf.config['do_package_tracking'])
2507 print statfrmt('A', r)
2510 for package in meta_get_packagelist(apiurl, r):
2512 checkout_package(apiurl, r, package, expand_link = True, prj_dir = r)
2514 print >>sys.stderr, 'Error while checkout package:\n', package
2516 if conf.config['verbose']:
2517 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
2520 @cmdln.alias('branchco')
2522 @cmdln.alias('getpac')
2523 @cmdln.option('--nodevelproject', action='store_true',
2524 help='do not follow a defined devel project ' \
2525 '(primary project where a package is developed)')
2526 @cmdln.option('-c', '--checkout', action='store_true',
2527 help='Checkout branched package afterwards using "co -e -S"' \
2528 '(\'osc bco\' is a shorthand for this option)' )
2529 @cmdln.option('-f', '--force', default=False, action="store_true",
2530 help='force branch, overwrite target')
2531 @cmdln.option('-m', '--message', metavar='TEXT',
2532 help='specify message TEXT')
2533 @cmdln.option('-r', '--revision', metavar='rev',
2534 help='branch against a specific revision')
2535 def do_branch(self, subcmd, opts, *args):
2536 """${cmd_name}: Branch a package
2538 [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration
2539 for information on this topic.]
2541 Create a source link from a package of an existing project to a new
2542 subproject of the requesters home project (home:branches:)
2544 The branched package will live in
2545 home:USERNAME:branches:PROJECT/PACKAGE
2546 if nothing else specified.
2548 With getpac or bco, the branched package will come from
2549 %(getpac_default_project)s
2550 if nothing else specified.
2554 osc branch SOURCEPROJECT SOURCEPACKAGE
2555 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT
2556 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE
2557 osc getpac SOURCEPACKAGE
2562 if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': opts.checkout = True
2563 args = slash_split(args)
2564 tproject = tpackage = None
2566 if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1:
2567 print >>sys.stderr, 'defaulting to %s/%s' % (conf.config['getpac_default_project'], args[0])
2568 # python has no args.unshift ???
2569 args = [ conf.config['getpac_default_project'] , args[0] ]
2571 if len(args) == 0 and is_package_dir('.'):
2572 args = (store_read_project('.'), store_read_package('.'))
2574 if len(args) < 2 or len(args) > 4:
2575 raise oscerr.WrongArgs('Wrong number of arguments.')
2577 apiurl = self.get_api_url()
2579 expected = 'home:%s:branches:%s' % (conf.get_apiurl_usr(apiurl), args[0])
2581 expected = tproject = args[2]
2585 exists, targetprj, targetpkg, srcprj, srcpkg = \
2586 branch_pkg(apiurl, args[0], args[1],
2587 nodevelproject=opts.nodevelproject, rev=opts.revision,
2588 target_project=tproject, target_package=tpackage,
2589 return_existing=opts.checkout, msg=opts.message or '',
2592 print >>sys.stderr, 'Using existing branch project: %s' % targetprj
2595 if not exists and (srcprj != args[0] or srcpkg != args[1]):
2597 root = ET.fromstring(''.join(show_attribute_meta(apiurl, args[0], None, None,
2598 conf.config['maintained_update_project_attribute'], False, False)))
2599 # this might raise an AttributeError
2600 uproject = root.find('attribute').find('value').text
2601 print '\nNote: The branch has been created from the configured update project: %s' \
2603 except (AttributeError, urllib2.HTTPError), e:
2605 print '\nNote: The branch has been created of a different project,\n' \
2607 ' which is the primary location of where development for\n' \
2608 ' that package takes place.\n' \
2609 ' That\'s also where you would normally make changes against.\n' \
2610 ' A direct branch of the specified package can be forced\n' \
2611 ' with the --nodevelproject option.\n' % devloc
2613 package = tpackage or args[1]
2615 checkout_package(apiurl, targetprj, package, server_service_files=True,
2616 expand_link=True, prj_dir=targetprj)
2617 if conf.config['verbose']:
2618 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
2621 if conf.get_configParser().get('general', 'apiurl') != apiurl:
2622 apiopt = '-A %s ' % apiurl
2623 print 'A working copy of the branched package can be checked out with:\n\n' \
2625 % (apiopt, targetprj, package)
2626 print_request_list(apiurl, args[0], args[1])
2628 print_request_list(apiurl, devloc, srcpkg)
2631 def do_undelete(self, subcmd, opts, *args):
2632 """${cmd_name}: Restores a deleted project or package on the server.
2634 The server restores a package including the sources and meta configuration.
2635 Binaries remain to be lost and will be rebuild.
2638 osc undelete PROJECT
2639 osc undelete PROJECT PACKAGE [PACKAGE ...]
2644 args = slash_split(args)
2646 raise oscerr.WrongArgs('Missing argument.')
2648 apiurl = self.get_api_url()
2654 undelete_package(apiurl, prj, pkg)
2656 undelete_project(apiurl, prj)
2659 @cmdln.option('-f', '--force', action='store_true',
2660 help='deletes a package or an empty project')
2661 def do_rdelete(self, subcmd, opts, *args):
2662 """${cmd_name}: Delete a project or packages on the server.
2664 As a safety measure, project must be empty (i.e., you need to delete all
2665 packages first). Also, packages must have no requests pending (i.e., you need
2666 to accept/revoke such requests first).
2667 If you are sure that you want to remove this project and all
2668 its packages use \'--force\' switch.
2671 osc rdelete [-f] PROJECT [PACKAGE]
2676 args = slash_split(args)
2677 if len(args) < 1 or len(args) > 2:
2678 raise oscerr.WrongArgs('Wrong number of arguments')
2680 apiurl = self.get_api_url()
2683 # empty arguments result in recursive project delete ...
2685 raise oscerr.WrongArgs('Project argument is empty')
2691 raise oscerr.WrongArgs('Package argument is empty')
2693 ## FIXME: core.py:commitDelPackage() should have something similar
2694 rlist = get_request_list(apiurl, prj, pkg)
2695 for rq in rlist: print rq
2696 if len(rlist) >= 1 and not opts.force:
2697 print >>sys.stderr, 'Package has pending requests. Deleting the package will break them. '\
2698 'They should be accepted/declined/revoked before deleting the package. '\
2699 'Or just use \'--force\'.'
2702 delete_package(apiurl, prj, pkg)
2704 elif len(meta_get_packagelist(apiurl, prj)) >= 1 and not opts.force:
2705 print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
2706 'If you are sure that you want to remove this project and all its ' \
2707 'packages use the \'--force\' switch.'
2710 delete_project(apiurl, prj)
2713 def do_deletepac(self, subcmd, opts, *args):
2714 print """${cmd_name} is obsolete !
2717 osc delete for checked out packages or projects
2719 osc rdelete for server side operations."""
2724 @cmdln.option('-f', '--force', action='store_true',
2725 help='deletes a project and its packages')
2726 def do_deleteprj(self, subcmd, opts, project):
2727 """${cmd_name} is obsolete !
2734 @cmdln.alias('metafromspec')
2735 @cmdln.option('', '--specfile', metavar='FILE',
2736 help='Path to specfile. (if you pass more than working copy this option is ignored)')
2737 def do_updatepacmetafromspec(self, subcmd, opts, *args):
2738 """${cmd_name}: Update package meta information from a specfile
2740 ARG, if specified, is a package working copy.
2746 args = parseargs(args)
2747 if opts.specfile and len(args) == 1:
2748 specfile = opts.specfile
2751 pacs = findpacs(args)
2753 p.read_meta_from_spec(specfile)
2754 p.update_package_meta()
2757 @cmdln.alias('linkdiff')
2758 @cmdln.alias('ldiff')
2760 @cmdln.option('-c', '--change', metavar='rev',
2761 help='the change made by revision rev (like -r rev-1:rev).'
2762 'If rev is negative this is like -r rev:rev-1.')
2763 @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
2764 help='If rev1 is specified it will compare your working copy against '
2765 'the revision (rev1) on the server. '
2766 'If rev1 and rev2 are specified it will compare rev1 against rev2 '
2767 '(NOTE: changes in your working copy are ignored in this case)')
2768 @cmdln.option('-p', '--plain', action='store_true',
2769 help='output the diff in plain (not unified) diff format')
2770 @cmdln.option('-l', '--link', action='store_true',
2771 help='(osc linkdiff): compare against the base revision of the link')
2772 @cmdln.option('--missingok', action='store_true',
2773 help='do not fail if the source or target project/package does not exist on the server')
2774 def do_diff(self, subcmd, opts, *args):
2775 """${cmd_name}: Generates a diff
2777 Generates a diff, comparing local changes against the repository
2781 ARG, if specified, is a filename to include in the diff.
2786 Compare current checkout directory against the link base.
2788 osc diff --link PROJ PACK
2789 osc linkdiff PROJ PACK
2790 Compare a package against the link base (ignoring working copy changes).
2795 if (subcmd == 'ldiff' or subcmd == 'linkdiff'):
2797 args = parseargs(args)
2800 if not opts.link or not len(args) == 2:
2801 pacs = findpacs(args)
2805 query = { 'rev': 'latest' }
2807 u = makeurl(pacs[0].apiurl, ['source', pacs[0].prjname, pacs[0].name], query=query)
2809 u = makeurl(self.get_api_url(), ['source', args[0], args[1]], query=query)
2811 root = ET.parse(f).getroot()
2812 linkinfo = root.find('linkinfo')
2813 if linkinfo == None:
2814 raise oscerr.APIError('package is not a source link')
2815 baserev = linkinfo.get('baserev')
2816 opts.revision = baserev
2818 print "diff working copy against linked revision %s\n" % baserev
2820 print "diff commited package against linked revision %s\n" % baserev
2821 run_pager(server_diff(self.get_api_url(), args[0], args[1], baserev,
2822 args[0], args[1], linkinfo.get('lsrcmd5'), not opts.plain, opts.missingok))
2827 rev = int(opts.change)
2837 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
2840 rev1, rev2 = parseRevisionOption(opts.revision)
2844 for i in pac.get_diff(rev1):
2845 sys.stdout.write(''.join(i))
2847 diff += server_diff_noex(pac.apiurl, pac.prjname, pac.name, rev1,
2848 pac.prjname, pac.name, rev2, not opts.plain, opts.missingok)
2852 @cmdln.option('--oldprj', metavar='OLDPRJ',
2853 help='project to compare against'
2854 ' (deprecated, use 3 argument form)')
2855 @cmdln.option('--oldpkg', metavar='OLDPKG',
2856 help='package to compare against'
2857 ' (deprecated, use 3 argument form)')
2858 @cmdln.option('-M', '--meta', action='store_true',
2859 help='diff meta data')
2860 @cmdln.option('-r', '--revision', metavar='N[:M]',
2861 help='revision id, where N = old revision and M = new revision')
2862 @cmdln.option('-p', '--plain', action='store_true',
2863 help='output the diff in plain (not unified) diff format')
2864 @cmdln.option('-c', '--change', metavar='rev',
2865 help='the change made by revision rev (like -r rev-1:rev). '
2866 'If rev is negative this is like -r rev:rev-1.')
2867 @cmdln.option('--missingok', action='store_true',
2868 help='do not fail if the source or target project/package does not exist on the server')
2869 @cmdln.option('-u', '--unexpand', action='store_true',
2870 help='diff unexpanded version if sources are linked')
2871 def do_rdiff(self, subcmd, opts, *args):
2872 """${cmd_name}: Server-side "pretty" diff of two packages
2874 Compares two packages (three or four arguments) or shows the
2875 changes of a specified revision of a package (two arguments)
2877 If no revision is specified the latest revision is used.
2879 Note that this command doesn't return a normal diff (which could be
2880 applied as patch), but a "pretty" diff, which also compares the content
2885 osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC]
2886 osc ${cmd_name} PROJECT PACKAGE
2890 args = slash_split(args)
2891 apiurl = self.get_api_url()
2902 new_project = args[0]
2903 new_package = args[1]
2905 old_project = opts.oldprj
2907 old_package = opts.oldpkg
2908 elif len(args) == 3 or len(args) == 4:
2909 if opts.oldprj or opts.oldpkg:
2910 raise oscerr.WrongArgs('--oldpkg and --oldprj are only valid with two arguments')
2911 old_project = args[0]
2912 new_package = old_package = args[1]
2913 new_project = args[2]
2915 new_package = args[3]
2916 elif len(args) == 1 and opts.meta:
2917 new_project = args[0]
2918 new_package = '_project'
2920 raise oscerr.WrongArgs('Wrong number of arguments')
2923 opts.unexpand = True
2927 rev = int(opts.change)
2937 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
2941 rev1, rev2 = parseRevisionOption(opts.revision)
2943 rdiff = server_diff_noex(apiurl,
2944 old_project, old_package, rev1,
2945 new_project, new_package, rev2, not opts.plain, opts.missingok,
2947 expand=not opts.unexpand)
2953 def do_install(self, subcmd, opts, *args):
2954 """${cmd_name}: install a package after build via zypper in -r
2956 Not implemented yet. Use osc repourls,
2957 select the url you best like (standard),
2958 chop off after the last /, this should work with zypper.
2965 args = slash_split(args)
2966 args = expand_proj_pack(args)
2969 ## if there is only one argument, and it ends in .ymp
2970 ## then fetch it, Parse XML to get the first
2971 ## metapackage.group.repositories.repository.url
2972 ## and construct zypper cmd's for all
2973 ## metapackage.group.software.item.name
2975 ## if args[0] is already an url, the use it as is.
2977 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])
2978 print self.do_install.__doc__
2979 print "Example: \n" + cmd
2982 def do_repourls(self, subcmd, opts, *args):
2983 """${cmd_name}: Shows URLs of .repo files
2985 Shows URLs on which to access the project .repos files (yum-style
2986 metadata) on download.opensuse.org.
2989 osc repourls [PROJECT]
2994 apiurl = self.get_api_url()
2998 elif len(args) == 0:
2999 project = store_read_project('.')
3001 raise oscerr.WrongArgs('Wrong number of arguments')
3003 # XXX: API should somehow tell that
3004 url_tmpl = 'http://download.opensuse.org/repositories/%s/%s/%s.repo'
3005 repos = get_repositories_of_project(apiurl, project)
3007 print url_tmpl % (project.replace(':', ':/'), repo, project)
3010 @cmdln.option('-r', '--revision', metavar='rev',
3011 help='checkout the specified revision. '
3012 'NOTE: if you checkout the complete project '
3013 'this option is ignored!')
3014 @cmdln.option('-e', '--expand-link', action='store_true',
3015 help='if a package is a link, check out the expanded '
3016 'sources (no-op, since this became the default)')
3017 @cmdln.option('-u', '--unexpand-link', action='store_true',
3018 help='if a package is a link, check out the _link file ' \
3019 'instead of the expanded sources')
3020 @cmdln.option('-M', '--meta', action='store_true',
3021 help='checkout out meta data instead of sources' )
3022 @cmdln.option('-c', '--current-dir', action='store_true',
3023 help='place PACKAGE folder in the current directory' \
3024 'instead of a PROJECT/PACKAGE directory')
3025 @cmdln.option('-s', '--source-service-files', action='store_true',
3026 help='Run source services.' )
3027 @cmdln.option('-S', '--server-side-source-service-files', action='store_true',
3028 help='Use server side generated sources instead of local generation.' )
3029 @cmdln.option('-l', '--limit-size', metavar='limit_size',
3030 help='Skip all files with a given size')
3032 def do_checkout(self, subcmd, opts, *args):
3033 """${cmd_name}: Check out content from the repository
3035 Check out content from the repository server, creating a local working
3038 When checking out a single package, the option --revision can be used
3039 to specify a revision of the package to be checked out.
3041 When a package is a source link, then it will be checked out in
3042 expanded form. If --unexpand-link option is used, the checkout will
3043 instead produce the raw _link file plus patches.
3046 osc co PROJECT [PACKAGE] [FILE]
3047 osc co PROJECT # entire project
3048 osc co PROJECT PACKAGE # a package
3049 osc co PROJECT PACKAGE FILE # single file -> to current dir
3051 while inside a project directory:
3052 osc co PACKAGE # check out PACKAGE from project
3054 with the result of rpm -q --qf '%%{disturl}n' PACKAGE
3055 osc co obs://API/PROJECT/PLATFORM/REVISION-PACKAGE
3060 if opts.unexpand_link:
3065 # XXX: this too openSUSE-setup specific...
3066 # FIXME: this should go into ~jw/patches/osc/osc.proj_pack_20101201.diff
3067 # to be available to all subcommands via @cmdline.prep(proj_pack)
3068 # obs://build.opensuse.org/openSUSE:11.3/standard/fc6c25e795a89503e99d59da5dc94a79-screen
3069 m = re.match(r"obs://([^/]+)/(\S+)/([^/]+)/([A-Fa-f\d]+)\-(\S+)", args[0])
3070 if m and len(args) == 1:
3071 apiurl = "https://" + m.group(1)
3072 project = project_dir = m.group(2)
3073 # platform = m.group(3)
3074 opts.revision = m.group(4)
3075 package = m.group(5)
3076 apiurl = apiurl.replace('/build.', '/api.')
3079 args = slash_split(args)
3080 project = package = filename = None
3081 apiurl = self.get_api_url()
3083 project = project_dir = args[0]
3089 if len(args) == 1 and is_project_dir(os.curdir):
3090 project = store_read_project(os.curdir)
3091 project_dir = os.curdir
3094 rev, dummy = parseRevisionOption(opts.revision)
3098 if rev and rev != "latest" and not checkRevision(project, package, rev):
3099 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
3103 # Note: same logic as with 'osc cat' (not 'osc ls', which never merges!)
3105 rev = show_upstream_srcmd5(apiurl, project, package, expand=True, revision=rev)
3106 get_source_file(apiurl, project, package, filename, revision=rev, progress_obj=self.download_progress)
3109 if opts.current_dir:
3111 checkout_package(apiurl, project, package, rev, expand_link=expand_link, \
3112 prj_dir=project_dir, service_files = opts.source_service_files, server_service_files=opts.server_side_source_service_files, progress_obj=self.download_progress, size_limit=opts.limit_size, meta=opts.meta)
3113 print_request_list(apiurl, project, package)
3117 if sys.platform[:3] == 'win':