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).
13 from optparse import SUPPRESS_HELP
16 from util import safewriter
18 MAN_HEADER = r""".TH %(ucname)s "1" "%(date)s" "%(name)s %(version)s" "User Commands"
20 %(name)s \- openSUSE build service command-line tool.
23 [\fIGLOBALOPTS\fR] \fISUBCOMMAND \fR[\fIOPTS\fR] [\fIARGS\fR...]
28 openSUSE build service command-line tool.
32 Type 'osc help <subcommand>' for more detailed help on a specific subcommand.
34 For additional information, see
35 * http://en.opensuse.org/openSUSE:Build_Service_Tutorial
36 * http://en.opensuse.org/openSUSE:OSC
38 You can modify osc commands, or roll you own, via the plugin API:
39 * http://en.opensuse.org/openSUSE:OSC_plugins
41 osc was written by several authors. This man page is automatically generated.
44 class Osc(cmdln.Cmdln):
45 """Usage: osc [GLOBALOPTS] SUBCOMMAND [OPTS] [ARGS...]
46 or: osc help SUBCOMMAND
48 openSUSE build service command-line tool.
49 Type 'osc help <subcommand>' for help on a specific subcommand.
54 For additional information, see
55 * http://en.opensuse.org/openSUSE:Build_Service_Tutorial
56 * http://en.opensuse.org/openSUSE:OSC
58 You can modify osc commands, or roll you own, via the plugin API:
59 * http://en.opensuse.org/openSUSE:OSC_plugins
64 man_header = MAN_HEADER
65 man_footer = MAN_FOOTER
67 def __init__(self, *args, **kwargs):
68 cmdln.Cmdln.__init__(self, *args, **kwargs)
69 cmdln.Cmdln.do_help.aliases.append('h')
70 sys.stderr = safewriter.SafeWriter(sys.stderr)
71 sys.stdout = safewriter.SafeWriter(sys.stdout)
73 def get_version(self):
74 return get_osc_version()
76 def get_optparser(self):
77 """this is the parser for "global" options (not specific to subcommand)"""
79 optparser = cmdln.CmdlnOptionParser(self, version=get_osc_version())
80 optparser.add_option('--debugger', action='store_true',
81 help='jump into the debugger before executing anything')
82 optparser.add_option('--post-mortem', action='store_true',
83 help='jump into the debugger in case of errors')
84 optparser.add_option('-t', '--traceback', action='store_true',
85 help='print call trace in case of errors')
86 optparser.add_option('-H', '--http-debug', action='store_true',
87 help='debug HTTP traffic (filters some headers)')
88 optparser.add_option('--http-full-debug', action='store_true',
89 help='debug HTTP traffic (filters no headers)'),
90 optparser.add_option('-d', '--debug', action='store_true',
91 help='print info useful for debugging')
92 optparser.add_option('-A', '--apiurl', dest='apiurl',
94 help='specify URL to access API server at or an alias')
95 optparser.add_option('-c', '--config', dest='conffile',
97 help='specify alternate configuration file')
98 optparser.add_option('--no-keyring', action='store_true',
99 help='disable usage of desktop keyring system')
100 optparser.add_option('--no-gnome-keyring', action='store_true',
101 help='disable usage of GNOME Keyring')
102 optparser.add_option('-v', '--verbose', dest='verbose', action='count', default=0,
103 help='increase verbosity')
104 optparser.add_option('-q', '--quiet', dest='verbose', action='store_const', const=-1,
105 help='be quiet, not verbose')
109 def postoptparse(self, try_again = True):
110 """merge commandline options into the config"""
112 conf.get_config(override_conffile = self.options.conffile,
113 override_apiurl = self.options.apiurl,
114 override_debug = self.options.debug,
115 override_http_debug = self.options.http_debug,
116 override_http_full_debug = self.options.http_full_debug,
117 override_traceback = self.options.traceback,
118 override_post_mortem = self.options.post_mortem,
119 override_no_keyring = self.options.no_keyring,
120 override_no_gnome_keyring = self.options.no_gnome_keyring,
121 override_verbose = self.options.verbose)
122 except oscerr.NoConfigfile, e:
123 print >>sys.stderr, e.msg
124 print >>sys.stderr, 'Creating osc configuration file %s ...' % e.file
127 config['user'] = raw_input('Username: ')
128 config['pass'] = getpass.getpass()
129 if self.options.no_keyring:
130 config['use_keyring'] = '0'
131 if self.options.no_gnome_keyring:
132 config['gnome_keyring'] = '0'
133 if self.options.apiurl:
134 config['apiurl'] = self.options.apiurl
136 conf.write_initial_config(e.file, config)
137 print >>sys.stderr, 'done'
138 if try_again: self.postoptparse(try_again = False)
139 except oscerr.ConfigMissingApiurl, e:
140 print >>sys.stderr, e.msg
142 user = raw_input('Username: ')
143 passwd = getpass.getpass()
144 conf.add_section(e.file, e.url, user, passwd)
145 if try_again: self.postoptparse(try_again = False)
147 self.options.verbose = conf.config['verbose']
148 self.download_progress = None
149 if conf.config.get('show_download_progress', False):
150 from meter import TextMeter
151 self.download_progress = TextMeter(hide_finished=True)
154 def get_cmd_help(self, cmdname):
155 doc = self._get_cmd_handler(cmdname).__doc__
156 doc = self._help_reindent(doc)
157 doc = self._help_preprocess(doc, cmdname)
158 doc = doc.rstrip() + '\n' # trim down trailing space
159 return self._str(doc)
161 def get_api_url(self):
163 localdir = os.getcwd()
165 ## check for Stale NFS file handle: '.'
167 except Exception, ee: e = ee
168 print >>sys.stderr, "os.getcwd() failed: ", e
171 if (is_package_dir(localdir) or is_project_dir(localdir)) and not self.options.apiurl:
172 return store_read_apiurl(os.curdir)
174 return conf.config['apiurl']
176 # overridden from class Cmdln() to use config variables in help texts
177 def _help_preprocess(self, help, cmdname):
178 help_msg = cmdln.Cmdln._help_preprocess(self, help, cmdname)
179 return help_msg % conf.config
182 def do_init(self, subcmd, opts, project, package=None):
183 """${cmd_name}: Initialize a directory as working copy
185 Initialize an existing directory to be a working copy of an
186 (already existing) buildservice project/package.
188 (This is the same as checking out a package and then copying sources
189 into the directory. It does NOT create a new package. To create a
190 package, use 'osc meta pkg ... ...')
192 You wouldn't normally use this command.
194 To get a working copy of a package (e.g. for building it or working on
195 it, you would normally use the checkout command. Use "osc help
196 checkout" to get help for it.
204 apiurl = self.get_api_url()
207 Project.init_project(apiurl, os.curdir, project, conf.config['do_package_tracking'])
208 print 'Initializing %s (Project: %s)' % (os.curdir, project)
210 Package.init_package(apiurl, project, package, os.curdir)
211 store_write_string(os.curdir, '_files', show_files_meta(apiurl, project, package) + '\n')
212 print 'Initializing %s (Project: %s, Package: %s)' % (os.curdir, project, package)
218 @cmdln.option('-a', '--arch', metavar='ARCH',
219 help='specify architecture (only for binaries)')
220 @cmdln.option('-r', '--repo', metavar='REPO',
221 help='specify repository (only for binaries)')
222 @cmdln.option('-b', '--binaries', action='store_true',
223 help='list built binaries instead of sources')
224 @cmdln.option('-e', '--expand', action='store_true',
225 help='expand linked package (only for sources)')
226 @cmdln.option('-u', '--unexpand', action='store_true',
227 help='always work with unexpanded (source) packages')
228 @cmdln.option('-v', '--verbose', action='store_true',
229 help='print extra information')
230 @cmdln.option('-l', '--long', action='store_true', dest='verbose',
231 help='print extra information')
232 @cmdln.option('-D', '--deleted', action='store_true',
233 help='show only the former deleted projects or packages')
234 @cmdln.option('-M', '--meta', action='store_true',
235 help='list meta data files')
236 @cmdln.option('-R', '--revision', metavar='REVISION',
237 help='specify revision (only for sources)')
238 def do_list(self, subcmd, opts, *args):
239 """${cmd_name}: List sources or binaries on the server
241 Examples for listing sources:
242 ls # list all projects (deprecated)
243 ls / # list all projects
244 ls . # take PROJECT/PACKAGE from current dir.
245 ls PROJECT # list packages in a project
246 ls PROJECT PACKAGE # list source files of package of a project
247 ls PROJECT PACKAGE <file> # list <file> if this file exists
248 ls -v PROJECT PACKAGE # verbosely list source files of package
249 ls -l PROJECT PACKAGE # verbosely list source files of package
250 ll PROJECT PACKAGE # verbosely list source files of package
251 LL PROJECT PACKAGE # verbosely list source files of expanded link
253 With --verbose, the following fields will be shown for each item:
255 Revision number of the last commit
257 Date and time of the last commit
259 Examples for listing binaries:
260 ls -b PROJECT # list all binaries of a project
261 ls -b PROJECT -a ARCH # list ARCH binaries of a project
262 ls -b PROJECT -r REPO # list binaries in REPO
263 ls -b PROJECT PACKAGE REPO ARCH
266 ${cmd_name} [PROJECT [PACKAGE]]
267 ${cmd_name} -b [PROJECT [PACKAGE [REPO [ARCH]]]]
271 args = slash_split(args)
274 if subcmd == 'lL' or subcmd == 'LL':
282 # For consistency with *all* other commands
283 # this lists what the server has in the current wd.
284 # CAUTION: 'osc ls -b' already works like this.
288 if project == '/': project = None
291 if is_project_dir(cwd):
292 project = store_read_project(cwd)
293 elif is_package_dir(cwd):
294 project = store_read_project(cwd)
295 package = store_read_package(cwd)
299 raise oscerr.WrongArgs("Too many arguments when listing deleted packages")
302 raise oscerr.WrongArgs("Too many arguments when listing deleted packages")
305 if opts.repo != args[2]:
306 raise oscerr.WrongArgs("conflicting repos specified ('%s' vs '%s')"%(opts.repo, args[2]))
313 if not opts.binaries:
314 raise oscerr.WrongArgs('Too many arguments')
316 if opts.arch != args[3]:
317 raise oscerr.WrongArgs("conflicting archs specified ('%s' vs '%s')"%(opts.arch, args[3]))
322 if opts.binaries and opts.expand:
323 raise oscerr.WrongOptions('Sorry, --binaries and --expand are mutual exclusive.')
325 apiurl = self.get_api_url()
329 # ls -b toplevel doesn't make sense, so use info from
330 # current dir if available
333 if is_project_dir(cwd):
334 project = store_read_project(cwd)
335 elif is_package_dir(cwd):
336 project = store_read_project(cwd)
337 package = store_read_package(cwd)
340 raise oscerr.WrongArgs('There are no binaries to list above project level.')
342 raise oscerr.WrongOptions('Sorry, the --revision option is not supported for binaries.')
346 if opts.repo and opts.arch:
347 repos.append(Repo(opts.repo, opts.arch))
348 elif opts.repo and not opts.arch:
349 repos = [repo for repo in get_repos_of_project(apiurl, project) if repo.name == opts.repo]
350 elif opts.arch and not opts.repo:
351 repos = [repo for repo in get_repos_of_project(apiurl, project) if repo.arch == opts.arch]
353 repos = get_repos_of_project(apiurl, project)
357 results.append((repo, get_binarylist(apiurl, project, repo.name, repo.arch, package=package, verbose=opts.verbose)))
359 for result in results:
362 print '%s/%s' % (result[0].name, result[0].arch)
367 print "%9d %s %-40s" % (f.size, shorttime(f.mtime), f.name)
373 elif not opts.binaries:
375 for prj in meta_get_project_list(apiurl, opts.deleted):
380 if self.options.verbose:
381 print >>sys.stderr, 'Sorry, the --verbose option is not implemented for projects.'
383 raise oscerr.WrongOptions('Sorry, the --expand option is not implemented for projects.')
384 for pkg in meta_get_packagelist(apiurl, project, opts.deleted):
387 elif len(args) == 2 or len(args) == 3:
389 print_not_found = True
392 l = meta_get_filelist(apiurl,
395 verbose=opts.verbose,
399 link_seen = '_link' in l
401 out = [ '%s %7s %9d %s %s' % (i.md5, i.rev, i.size, shorttime(i.mtime), i.name) \
402 for i in l if not fname or fname == i.name ]
404 print_not_found = False
409 print_not_found = False
412 if opts.expand or opts.unexpand or not link_seen: break
413 m = show_files_meta(apiurl, project, package)
415 li.read(ET.fromstring(''.join(m)).find('linkinfo'))
417 raise oscerr.LinkExpandError(project, package, li.error)
418 project, package, rev = li.project, li.package, li.rev
420 print '# -> %s %s (%s)' % (project, package, rev)
422 print '# -> %s %s (latest)' % (project, package)
424 if fname and print_not_found:
425 print 'file \'%s\' does not exist' % fname
428 @cmdln.option('-f', '--force', action='store_true',
429 help='force generation of new patchinfo file')
430 @cmdln.option('-n', '--new', action='store_true',
431 help='Use new, OBS 2.3 style patchinfo format. Will become default on release of OBS 2.3.')
432 @cmdln.option('--force-update', action='store_true',
433 help='drops away collected packages from an already built patch and let it collect again')
434 def do_patchinfo(self, subcmd, opts, *args):
435 """${cmd_name}: Generate and edit a patchinfo file.
437 A patchinfo file describes the packages for an update and the kind of
442 osc patchinfo PATCH_NAME
446 project_dir = localdir = os.getcwd()
447 if is_project_dir(localdir):
448 project = store_read_project(localdir)
449 apiurl = self.get_api_url()
451 sys.exit('This command must be called in a checked out project.')
453 for p in meta_get_packagelist(apiurl, project):
454 if p.startswith("_patchinfo") or p.startswith("patchinfo"):
457 if opts.force or not patchinfo:
458 print "Creating initial patchinfo..."
459 query='cmd=createpatchinfo'
461 query+='&new_format=1'
463 query += "&name=" + args[0]
464 url = makeurl(apiurl, ['source', project], query=query)
466 for p in meta_get_packagelist(apiurl, project):
467 if p.startswith("_patchinfo") or p.startswith("patchinfo"):
471 # Both conf.config['checkout_no_colon'] and conf.config['checkout_rooted']
473 if not os.path.exists(project_dir + "/" + patchinfo):
474 checkout_package(apiurl, project, patchinfo, prj_dir=project_dir)
476 filename = project_dir + "/" + patchinfo + "/_patchinfo"
479 @cmdln.alias('bsdevelproject')
480 @cmdln.option('-r', '--raw', action='store_true',
481 help='print raw xml snippet')
482 def do_develproject(self, subcmd, opts, *args):
483 """${cmd_name}: print the bsdevelproject of a package
486 osc develproject PRJ PKG
490 args = slash_split(args)
491 apiurl = self.get_api_url()
494 project = store_read_project(os.curdir)
495 package = store_read_package(os.curdir)
500 raise oscerr.WrongArgs('need Project and Package')
502 devel = show_develproject(apiurl, project, package, opts.raw)
504 print '\'%s/%s\' has no devel project' % (project, package)
511 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
512 help='affect only a given attribute')
513 @cmdln.option('--attribute-defaults', action='store_true',
514 help='include defined attribute defaults')
515 @cmdln.option('--attribute-project', action='store_true',
516 help='include project values, if missing in packages ')
517 @cmdln.option('-f', '--force', action='store_true',
518 help='force the save operation, allows one to ignores some errors like depending repositories. For prj meta only.')
519 @cmdln.option('-F', '--file', metavar='FILE',
520 help='read metadata from FILE, instead of opening an editor. '
521 '\'-\' denotes standard input. ')
522 @cmdln.option('-e', '--edit', action='store_true',
523 help='edit metadata')
524 @cmdln.option('-c', '--create', action='store_true',
525 help='create attribute without values')
526 @cmdln.option('-s', '--set', metavar='ATTRIBUTE_VALUES',
527 help='set attribute values')
528 @cmdln.option('--delete', action='store_true',
529 help='delete a pattern or attribute')
530 def do_meta(self, subcmd, opts, *args):
531 """${cmd_name}: Show meta information, or edit it
533 Show or edit build service metadata of type <prj|pkg|prjconf|user|pattern>.
535 This command displays metadata on buildservice objects like projects,
536 packages, or users. The type of metadata is specified by the word after
537 "meta", like e.g. "meta prj".
539 prj denotes metadata of a buildservice project.
540 prjconf denotes the (build) configuration of a project.
541 pkg denotes metadata of a buildservice package.
542 user denotes the metadata of a user.
543 pattern denotes installation patterns defined for a project.
545 To list patterns, use 'osc meta pattern PRJ'. An additional argument
546 will be the pattern file to view or edit.
548 With the --edit switch, the metadata can be edited. Per default, osc
549 opens the program specified by the environmental variable EDITOR with a
550 temporary file. Alternatively, content to be saved can be supplied via
551 the --file switch. If the argument is '-', input is taken from stdin:
552 osc meta prjconf home:user | sed ... | osc meta prjconf home:user -F -
554 When trying to edit a non-existing resource, it is created implicitly.
560 osc meta pkg PRJ PKG -e
561 osc meta attribute PRJ [PKG [SUBPACKAGE]] [--attribute ATTRIBUTE] [--create|--delete|--set [value_list]]
564 osc meta <prj|pkg|prjconf|user|pattern|attribute> ARGS...
565 osc meta <prj|pkg|prjconf|user|pattern|attribute> -e|--edit ARGS...
566 osc meta <prj|pkg|prjconf|user|pattern|attribute> -F|--file ARGS...
567 osc meta pattern --delete PRJ PATTERN
571 args = slash_split(args)
573 if not args or args[0] not in metatypes.keys():
574 raise oscerr.WrongArgs('Unknown meta type. Choose one of %s.' \
575 % ', '.join(metatypes))
580 apiurl = self.get_api_url()
583 min_args, max_args = 0, 2
584 elif cmd in ['pattern']:
585 min_args, max_args = 1, 2
586 elif cmd in ['attribute']:
587 min_args, max_args = 1, 3
588 elif cmd in ['prj', 'prjconf']:
589 min_args, max_args = 0, 1
591 min_args, max_args = 1, 1
593 if len(args) < min_args:
594 raise oscerr.WrongArgs('Too few arguments.')
595 if len(args) > max_args:
596 raise oscerr.WrongArgs('Too many arguments.')
600 if cmd in ['pkg', 'prj', 'prjconf' ]:
602 project = store_read_project(os.curdir)
608 package = store_read_package(os.curdir)
612 elif cmd == 'attribute':
618 if opts.attribute_project:
619 raise oscerr.WrongOptions('--attribute-project works only when also a package is given')
624 attributepath.append('source')
625 attributepath.append(project)
627 attributepath.append(package)
629 attributepath.append(subpackage)
630 attributepath.append('_attribute')
633 elif cmd == 'pattern':
639 # enforce pattern argument if needed
640 if opts.edit or opts.file:
641 raise oscerr.WrongArgs('A pattern file argument is required.')
644 if not opts.edit and not opts.file and not opts.delete and not opts.create and not opts.set:
646 sys.stdout.write(''.join(show_project_meta(apiurl, project)))
648 sys.stdout.write(''.join(show_package_meta(apiurl, project, package)))
649 elif cmd == 'attribute':
650 sys.stdout.write(''.join(show_attribute_meta(apiurl, project, package, subpackage, opts.attribute, opts.attribute_defaults, opts.attribute_project)))
651 elif cmd == 'prjconf':
652 sys.stdout.write(''.join(show_project_conf(apiurl, project)))
654 r = get_user_meta(apiurl, user)
656 sys.stdout.write(''.join(r))
657 elif cmd == 'pattern':
659 r = show_pattern_meta(apiurl, project, pattern)
661 sys.stdout.write(''.join(r))
663 r = show_pattern_metalist(apiurl, project)
665 sys.stdout.write('\n'.join(r) + '\n')
668 if opts.edit and not opts.file:
670 edit_meta(metatype='prj',
673 path_args=quote_plus(project),
677 'user': conf.get_apiurl_usr(apiurl)}))
679 edit_meta(metatype='pkg',
681 path_args=(quote_plus(project), quote_plus(package)),
685 'user': conf.get_apiurl_usr(apiurl)}))
686 elif cmd == 'prjconf':
687 edit_meta(metatype='prjconf',
689 path_args=quote_plus(project),
693 edit_meta(metatype='user',
695 path_args=(quote_plus(user)),
697 template_args=({'user': user}))
698 elif cmd == 'pattern':
699 edit_meta(metatype='pattern',
701 path_args=(project, pattern),
705 # create attribute entry
706 if (opts.create or opts.set) and cmd == 'attribute':
707 if not opts.attribute:
708 raise oscerr.WrongOptions('no attribute given to create')
711 opts.set = opts.set.replace('&', '&').replace('<', '<').replace('>', '>')
712 for i in opts.set.split(','):
713 values += '<value>%s</value>' % i
714 aname = opts.attribute.split(":")
715 d = '<attributes><attribute namespace=\'%s\' name=\'%s\' >%s</attribute></attributes>' % (aname[0], aname[1], values)
716 url = makeurl(apiurl, attributepath)
717 for data in streamfile(url, http_POST, data=d):
718 sys.stdout.write(data)
727 f = open(opts.file).read()
729 sys.exit('could not open file \'%s\'.' % opts.file)
732 edit_meta(metatype='prj',
737 path_args=quote_plus(project))
739 edit_meta(metatype='pkg',
743 path_args=(quote_plus(project), quote_plus(package)))
744 elif cmd == 'prjconf':
745 edit_meta(metatype='prjconf',
749 path_args=quote_plus(project))
751 edit_meta(metatype='user',
755 path_args=(quote_plus(user)))
756 elif cmd == 'pattern':
757 edit_meta(metatype='pattern',
761 path_args=(project, pattern))
766 path = metatypes[cmd]['path']
768 path = path % (project, pattern)
769 u = makeurl(apiurl, [path])
771 elif cmd == 'attribute':
772 if not opts.attribute:
773 raise oscerr.WrongOptions('no attribute given to create')
774 attributepath.append(opts.attribute)
775 u = makeurl(apiurl, attributepath)
776 for data in streamfile(u, http_DELETE):
777 sys.stdout.write(data)
779 raise oscerr.WrongOptions('The --delete switch is only for pattern metadata or attributes.')
782 # TODO: rewrite and consolidate the current submitrequest/createrequest "mess"
784 @cmdln.option('-m', '--message', metavar='TEXT',
785 help='specify message TEXT')
786 @cmdln.option('-r', '--revision', metavar='REV',
787 help='specify a certain source revision ID (the md5 sum) for the source package')
788 @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
789 help='Superseding another request by this one')
790 @cmdln.option('--nodevelproject', action='store_true',
791 help='do not follow a defined devel project ' \
792 '(primary project where a package is developed)')
793 @cmdln.option('--cleanup', action='store_true',
794 help='remove package if submission gets accepted (default for home:<id>:branch projects)')
795 @cmdln.option('--no-cleanup', action='store_true',
796 help='never remove source package on accept, but update its content')
797 @cmdln.option('--no-update', action='store_true',
798 help='never touch source package on accept (will break source links)')
799 @cmdln.option('-d', '--diff', action='store_true',
800 help='show diff only instead of creating the actual request')
801 @cmdln.option('--yes', action='store_true',
802 help='proceed without asking.')
804 @cmdln.alias("submitreq")
805 @cmdln.alias("submitpac")
806 def do_submitrequest(self, subcmd, opts, *args):
807 """${cmd_name}: Create request to submit source into another Project
809 [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration for information
812 See the "request" command for showing and modifing existing requests.
815 osc submitreq [OPTIONS]
816 osc submitreq [OPTIONS] DESTPRJ [DESTPKG]
817 osc submitreq [OPTIONS] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
819 osc submitpac ... is a shorthand for osc submitreq --cleanup ...
824 if opts.cleanup and opts.no_cleanup:
825 raise oscerr.WrongOptions('\'--cleanup\' and \'--no-cleanup\' are mutually exclusive')
827 src_update = conf.config['submitrequest_on_accept_action'] or None
828 # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
830 if subcmd == 'submitpac' and not opts.no_cleanup:
834 src_update = "cleanup"
835 elif opts.no_cleanup:
836 src_update = "update"
838 src_update = "noupdate"
840 args = slash_split(args)
842 # remove this block later again
843 oldcmds = ['create', 'list', 'log', 'show', 'decline', 'accept', 'delete', 'revoke']
844 if args and args[0] in oldcmds:
845 print >>sys.stderr, "************************************************************************"
846 print >>sys.stderr, "* WARNING: It looks that you are using this command with a *"
847 print >>sys.stderr, "* deprecated syntax. *"
848 print >>sys.stderr, "* Please run \"osc sr --help\" and \"osc rq --help\" *"
849 print >>sys.stderr, "* to see the new syntax. *"
850 print >>sys.stderr, "************************************************************************"
851 if args[0] == 'create':
857 raise oscerr.WrongArgs('Too many arguments.')
859 if len(args) > 0 and len(args) <= 2 and is_project_dir(os.getcwd()):
860 sys.exit('osc submitrequest from project directory is only working without target specs and for source linked files\n')
862 apiurl = self.get_api_url()
864 if len(args) == 0 and is_project_dir(os.getcwd()):
866 # submit requests for multiple packages are currently handled via multiple requests
867 # They could be also one request with multiple actions, but that avoids to accepts parts of it.
868 project = store_read_project(os.curdir)
874 # loop via all packages for checking their state
875 for p in meta_get_packagelist(apiurl, project):
876 if p.startswith("_patchinfo:") or p.startswith("patchinfo"):
879 # get _link info from server, that knows about the local state ...
880 u = makeurl(apiurl, ['source', project, p])
882 root = ET.parse(f).getroot()
883 linkinfo = root.find('linkinfo')
885 print "Package ", p, " is not a source link."
886 sys.exit("This is currently not supported.")
887 if linkinfo.get('error'):
888 print "Package ", p, " is a broken source link."
889 sys.exit("Please fix this first")
890 t = linkinfo.get('project')
892 if len(root.findall('entry')) > 1: # This is not really correct, but should work mostly
893 # Real fix is to ask the api if sources are modificated
894 # but there is no such call yet.
895 targetprojects.append(t)
897 print "Submitting package ", p
899 print " Skipping package ", p
901 print "Skipping package ", p, " since it is a source link pointing inside the project."
902 serviceinfo = root.find('serviceinfo')
903 if serviceinfo != None:
904 if serviceinfo.get('code') != "succeeded":
905 print "Package ", p, " has a ", serviceinfo.get('code'), " source service"
906 sys.exit("Please fix this first")
907 if serviceinfo.get('error'):
908 print "Package ", p, " contains a failed source service."
909 sys.exit("Please fix this first")
911 # was this project created by clone request ?
912 u = makeurl(apiurl, ['source', project, '_attribute', 'OBS:RequestCloned'])
914 root = ET.parse(f).getroot()
915 value = root.findtext('attribute/value')
922 print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
923 repl = raw_input('\nEverything fine? Can we create the requests ? (y/n) ')
924 if repl.lower() != 'y':
925 print >>sys.stderr, 'Aborted...'
926 raise oscerr.UserAbort()
929 # loop via all packages to do the action
931 result = create_submit_request(apiurl, project, p)
934 sys.exit("submit request creation failed")
935 sr_ids.append(result)
937 # create submit requests for all found patchinfos
941 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
944 for t in targetprojects:
945 s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \
946 (project, p, t, p, options_block)
950 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
951 (actionxml, cgi.escape(opts.message or ""))
952 u = makeurl(apiurl, ['request'], query='cmd=create')
953 f = http_POST(u, data=xml)
955 root = ET.parse(f).getroot()
956 sr_ids.append(root.get('id'))
958 print "Requests created: ",
963 if len(myreqs) > 0 and not opts.supersede:
964 print '\n\nThere are already following submit request: %s.' % \
965 ', '.join([str(i) for i in myreqs ])
966 repl = raw_input('\nSupersede the old requests? (y/n) ')
967 if repl.lower() == 'y':
969 change_request_state(apiurl, str(req), 'superseded',
970 'superseded by %s' % result, result)
972 sys.exit('Successfully finished')
975 # try using the working copy at hand
976 p = findpacs(os.curdir)[0]
977 src_project = p.prjname
980 if len(args) == 0 and p.islink():
981 dst_project = p.linkinfo.project
982 dst_package = p.linkinfo.package
984 dst_project = args[0]
986 dst_package = args[1]
988 dst_package = src_package
990 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
991 'Please provide it the target via commandline arguments.' % p.name)
993 modified = [i for i in p.filenamelist if not p.status(i) in (' ', '?', 'S')]
994 if len(modified) > 0:
995 print 'Your working copy has local modifications.'
996 repl = raw_input('Proceed without committing the local changes? (y|N) ')
998 raise oscerr.UserAbort()
1000 # get the arguments from the commandline
1001 src_project, src_package, dst_project = args[0:3]
1003 dst_package = args[3]
1005 dst_package = src_package
1007 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1008 + self.get_cmd_help('request'))
1010 # check for running source service
1011 u = makeurl(apiurl, ['source', src_project, src_package])
1013 root = ET.parse(f).getroot()
1014 serviceinfo = root.find('serviceinfo')
1015 if serviceinfo != None:
1016 if serviceinfo.get('code') != "succeeded":
1017 print "Package ", src_package, " has a ", serviceinfo.get('code'), " source service"
1018 sys.exit("Please fix this first")
1019 if serviceinfo.get('error'):
1020 print "Package ", src_package, " contains a failed source service."
1021 sys.exit("Please fix this first")
1023 if not opts.nodevelproject:
1026 devloc = show_develproject(apiurl, dst_project, dst_package)
1027 except urllib2.HTTPError:
1028 print >>sys.stderr, """\
1029 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
1030 % (dst_project, dst_package)
1034 dst_project != devloc and \
1035 src_project != devloc:
1037 A different project, %s, is defined as the place where development
1038 of the package %s primarily takes place.
1039 Please submit there instead, or use --nodevelproject to force direct submission.""" \
1040 % (devloc, dst_package)
1046 # get _link info from server, that knows about the local state ...
1047 u = makeurl(apiurl, ['source', src_project, src_package])
1049 root = ET.parse(f).getroot()
1050 linkinfo = root.find('linkinfo')
1051 if linkinfo != None:
1052 if linkinfo.get('error'):
1053 print "Package source is a broken source link."
1054 sys.exit("Please fix this first")
1055 if linkinfo.get('project') != dst_project or linkinfo.get('package') != dst_package:
1056 # the submit target is not link target. use merged md5sum references to avoid not mergable
1057 # sources when multiple request from same source get created.
1058 rev=linkinfo.get('xsrcmd5')
1061 if opts.diff or not opts.message:
1063 rdiff = 'old: %s/%s\nnew: %s/%s rev %s' %(dst_project, dst_package, src_project, src_package, rev)
1064 rdiff += server_diff(apiurl,
1065 dst_project, dst_package, None,
1066 src_project, src_package, rev, True)
1074 # Are there already requests to this package ?
1075 reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit', req_state=['new','review'])
1076 user = conf.get_apiurl_usr(apiurl)
1077 myreqs = [ i for i in reqs if i.state.who == user ]
1080 if len(myreqs) > 0 and not opts.supersede:
1081 print 'There are already following submit request: %s.' % \
1082 ', '.join([i.reqid for i in myreqs ])
1083 repl = raw_input('Supersede the old requests? (y/n/c) ')
1084 if repl.lower() == 'c':
1085 print >>sys.stderr, 'Aborting'
1086 raise oscerr.UserAbort()
1088 if not opts.message:
1091 changes_re = re.compile(r'^--- .*\.changes ')
1092 for line in rdiff.split('\n'):
1093 if line.startswith('--- '):
1094 if changes_re.match(line):
1099 difflines.append(line)
1100 opts.message = edit_message(footer=rdiff, template='\n'.join(parse_diff_for_commit_message('\n'.join(difflines))))
1102 result = create_submit_request(apiurl,
1103 src_project, src_package,
1104 dst_project, dst_package,
1105 opts.message, orev=rev, src_update=src_update)
1106 if repl.lower() == 'y':
1108 change_request_state(apiurl, req.reqid, 'superseded',
1109 'superseded by %s' % result, result)
1112 change_request_state(apiurl, opts.supersede, 'superseded',
1113 opts.message or '', result)
1115 print 'created request id', result
1117 def _actionparser(self, opt_str, value, parser):
1119 if not hasattr(parser.values, 'actiondata'):
1120 setattr(parser.values, 'actiondata', [])
1121 if parser.values.actions == None:
1122 parser.values.actions = []
1124 rargs = parser.rargs
1127 if ((arg[:2] == "--" and len(arg) > 2) or
1128 (arg[:1] == "-" and len(arg) > 1 and arg[1] != "-")):
1134 parser.values.actions.append(value[0])
1136 parser.values.actiondata.append(value)
1138 def _submit_request(self, args, opts, options_block):
1140 apiurl = self.get_api_url()
1141 if len(args) == 0 and is_project_dir(os.getcwd()):
1142 # submit requests for multiple packages are currently handled via multiple requests
1143 # They could be also one request with multiple actions, but that avoids to accepts parts of it.
1144 project = store_read_project(os.curdir)
1150 # loop via all packages for checking their state
1151 for p in meta_get_packagelist(apiurl, project):
1152 if p.startswith("_patchinfo:"):
1155 # get _link info from server, that knows about the local state ...
1156 u = makeurl(apiurl, ['source', project, p])
1158 root = ET.parse(f).getroot()
1159 linkinfo = root.find('linkinfo')
1160 if linkinfo == None:
1161 print "Package ", p, " is not a source link."
1162 sys.exit("This is currently not supported.")
1163 if linkinfo.get('error'):
1164 print "Package ", p, " is a broken source link."
1165 sys.exit("Please fix this first")
1166 t = linkinfo.get('project')
1170 rdiff = server_diff(apiurl, t, p, opts.revision, project, p, None, True)
1175 targetprojects.append(t)
1177 rdiffmsg.append("old: %s/%s\nnew: %s/%s\n%s" %(t, p, project, p,rdiff))
1179 print "Skipping package ", p, " since it has no difference with the target package."
1181 print "Skipping package ", p, " since it is a source link pointing inside the project."
1183 print ''.join(rdiffmsg)
1188 print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
1189 print "\nEverything fine? Can we create the requests ? [y/n]"
1190 if sys.stdin.read(1) != "y":
1191 sys.exit("Aborted...")
1193 # loop via all packages to do the action
1195 s = """<action type="submit"> <source project="%s" package="%s" rev="%s"/> <target project="%s" package="%s"/> %s </action>""" % \
1196 (project, p, opts.revision or show_upstream_rev(apiurl, project, p), t, p, options_block)
1199 # create submit requests for all found patchinfos
1201 for t in targetprojects:
1202 s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \
1203 (project, p, t, p, options_block)
1208 elif len(args) <= 2:
1209 # try using the working copy at hand
1210 p = findpacs(os.curdir)[0]
1211 src_project = p.prjname
1212 src_package = p.name
1213 if len(args) == 0 and p.islink():
1214 dst_project = p.linkinfo.project
1215 dst_package = p.linkinfo.package
1217 dst_project = args[0]
1219 dst_package = args[1]
1221 dst_package = src_package
1223 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
1224 'Please provide it the target via commandline arguments.' % p.name)
1226 modified = [i for i in p.filenamelist if p.status(i) != ' ' and p.status(i) != '?']
1227 if len(modified) > 0:
1228 print 'Your working copy has local modifications.'
1229 repl = raw_input('Proceed without committing the local changes? (y|N) ')
1232 elif len(args) >= 3:
1233 # get the arguments from the commandline
1234 src_project, src_package, dst_project = args[0:3]
1236 dst_package = args[3]
1238 dst_package = src_package
1240 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1241 + self.get_cmd_help('request'))
1243 if not opts.nodevelproject:
1246 devloc = show_develproject(apiurl, dst_project, dst_package)
1247 except urllib2.HTTPError:
1248 print >>sys.stderr, """\
1249 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
1250 % (dst_project, dst_package)
1254 dst_project != devloc and \
1255 src_project != devloc:
1257 A different project, %s, is defined as the place where development
1258 of the package %s primarily takes place.
1259 Please submit there instead, or use --nodevelproject to force direct submission.""" \
1260 % (devloc, dst_package)
1267 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
1268 rdiff += server_diff(apiurl,
1269 dst_project, dst_package, opts.revision,
1270 src_project, src_package, None, True)
1276 reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit', req_state=['new','review'])
1277 user = conf.get_apiurl_usr(apiurl)
1278 myreqs = [ i for i in reqs if i.state.who == user ]
1281 print 'You already created the following submit request: %s.' % \
1282 ', '.join([i.reqid for i in myreqs ])
1283 repl = raw_input('Supersede the old requests? (y/n/c) ')
1284 if repl.lower() == 'c':
1285 print >>sys.stderr, 'Aborting'
1288 actionxml = """<action type="submit"> <source project="%s" package="%s" rev="%s"/> <target project="%s" package="%s"/> %s </action>""" % \
1289 (src_project, src_package, opts.revision or show_upstream_rev(apiurl, src_project, src_package), dst_project, dst_package, options_block)
1290 if repl.lower() == 'y':
1292 change_request_state(apiurl, req.reqid, 'superseded',
1293 'superseded by %s' % result, result)
1296 change_request_state(apiurl, opts.supersede, 'superseded', '', result)
1298 #print 'created request id', result
1301 def _delete_request(self, args, opts):
1303 raise oscerr.WrongArgs('Please specify at least a project.')
1305 raise oscerr.WrongArgs('Too many arguments.')
1309 package = """package="%s" """ % (args[1])
1310 actionxml = """<action type="delete"> <target project="%s" %s/> </action> """ % (args[0], package)
1313 def _changedevel_request(self, args, opts):
1315 raise oscerr.WrongArgs('Too many arguments.')
1317 if len(args) == 0 and is_package_dir('.') and find_default_project():
1319 devel_project = store_read_project(wd)
1320 devel_package = package = store_read_package(wd)
1321 project = find_default_project(self.get_api_url(), package)
1324 raise oscerr.WrongArgs('Too few arguments.')
1326 devel_project = args[2]
1329 devel_package = package
1331 devel_package = args[3]
1333 actionxml = """ <action type="change_devel"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> </action> """ % \
1334 (devel_project, devel_package, project, package)
1338 def _add_me(self, args, opts):
1340 raise oscerr.WrongArgs('Too many arguments.')
1342 raise oscerr.WrongArgs('Too few arguments.')
1344 apiurl = self.get_api_url()
1346 user = conf.get_apiurl_usr(apiurl)
1349 actionxml = """ <action type="add_role"> <target project="%s" /> <person name="%s" role="%s" /> </action> """ % \
1350 (project, user, role)
1354 actionxml = """ <action type="add_role"> <target project="%s" package="%s" /> <person name="%s" role="%s" /> </action> """ % \
1355 (project, package, user, role)
1357 if get_user_meta(apiurl, user) == None:
1358 raise oscerr.WrongArgs('osc: an error occured.')
1362 def _add_user(self, args, opts):
1364 raise oscerr.WrongArgs('Too many arguments.')
1366 raise oscerr.WrongArgs('Too few arguments.')
1368 apiurl = self.get_api_url()
1373 actionxml = """ <action type="add_role"> <target project="%s" /> <person name="%s" role="%s" /> </action> """ % \
1374 (project, user, role)
1378 actionxml = """ <action type="add_role"> <target project="%s" package="%s" /> <person name="%s" role="%s" /> </action> """ % \
1379 (project, package, user, role)
1381 if get_user_meta(apiurl, user) == None:
1382 raise oscerr.WrongArgs('osc: an error occured.')
1386 def _add_group(self, args, opts):
1388 raise oscerr.WrongArgs('Too many arguments.')
1390 raise oscerr.WrongArgs('Too few arguments.')
1392 apiurl = self.get_api_url()
1397 actionxml = """ <action type="add_role"> <target project="%s" /> <group name="%s" role="%s" /> </action> """ % \
1398 (project, group, role)
1402 actionxml = """ <action type="add_role"> <target project="%s" package="%s" /> <group name="%s" role="%s" /> </action> """ % \
1403 (project, package, group, role)
1405 if get_group(apiurl, group) == None:
1406 raise oscerr.WrongArgs('osc: an error occured.')
1410 def _set_bugowner(self, args, opts):
1412 raise oscerr.WrongArgs('Too many arguments.')
1414 raise oscerr.WrongArgs('Too few arguments.')
1416 apiurl = self.get_api_url()
1423 if get_user_meta(apiurl, user) == None:
1424 raise oscerr.WrongArgs('osc: an error occured.')
1426 actionxml = """ <action type="set_bugowner"> <target project="%s" package="%s" /> <person name="%s" /> </action> """ % \
1427 (project, package, user)
1431 @cmdln.option('-a', '--action', action='callback', callback = _actionparser,dest = 'actions',
1432 help='specify action type of a request, can be : submit/delete/change_devel/add_role/set_bugowner')
1433 @cmdln.option('-m', '--message', metavar='TEXT',
1434 help='specify message TEXT')
1435 @cmdln.option('-r', '--revision', metavar='REV',
1436 help='for "create", specify a certain source revision ID (the md5 sum)')
1437 @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
1438 help='Superseding another request by this one')
1439 @cmdln.option('--nodevelproject', action='store_true',
1440 help='do not follow a defined devel project ' \
1441 '(primary project where a package is developed)')
1442 @cmdln.option('--cleanup', action='store_true',
1443 help='remove package if submission gets accepted (default for home:<id>:branch projects)')
1444 @cmdln.option('--no-cleanup', action='store_true',
1445 help='never remove source package on accept, but update its content')
1446 @cmdln.option('--no-update', action='store_true',
1447 help='never touch source package on accept (will break source links)')
1448 @cmdln.option('-d', '--diff', action='store_true',
1449 help='show diff only instead of creating the actual request')
1450 @cmdln.option('--yes', action='store_true',
1451 help='proceed without asking.')
1452 @cmdln.alias("creq")
1453 def do_createrequest(self, subcmd, opts, *args):
1454 """${cmd_name}: create multiple requests with a single command
1457 osc creq [OPTIONS] [
1458 -a submit SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
1459 -a delete PROJECT [PACKAGE]
1460 -a change_devel PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
1461 -a add_me ROLE PROJECT [PACKAGE]
1462 -a add_group GROUP ROLE PROJECT [PACKAGE]
1463 -a add_role USER ROLE PROJECT [PACKAGE]
1464 -a set_bugowner USER PROJECT [PACKAGE]
1467 Option -m works for all types of request, the rest work only for submit.
1469 osc creq -a submit -a delete home:someone:branches:openSUSE:Tools -a change_devel openSUSE:Tools osc home:someone:branches:openSUSE:Tools -m ok
1471 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.
1474 src_update = conf.config['submitrequest_on_accept_action'] or None
1475 # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
1477 src_update = "cleanup"
1478 elif opts.no_cleanup:
1479 src_update = "update"
1480 elif opts.no_update:
1481 src_update = "noupdate"
1485 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
1487 args = slash_split(args)
1489 apiurl = self.get_api_url()
1493 for ai in opts.actions:
1495 args = opts.actiondata[i]
1497 actionsxml += self._submit_request(args,opts, options_block)
1498 elif ai == 'delete':
1499 args = opts.actiondata[i]
1500 actionsxml += self._delete_request(args,opts)
1502 elif ai == 'change_devel':
1503 args = opts.actiondata[i]
1504 actionsxml += self._changedevel_request(args,opts)
1506 elif ai == 'add_me':
1507 args = opts.actiondata[i]
1508 actionsxml += self._add_me(args,opts)
1510 elif ai == 'add_group':
1511 args = opts.actiondata[i]
1512 actionsxml += self._add_group(args,opts)
1514 elif ai == 'add_role':
1515 args = opts.actiondata[i]
1516 actionsxml += self._add_user(args,opts)
1518 elif ai == 'set_bugowner':
1519 args = opts.actiondata[i]
1520 actionsxml += self._set_bugowner(args,opts)
1523 raise oscerr.WrongArgs('Unsupported action %s' % ai)
1524 if actionsxml == "":
1525 sys.exit('No actions need to be taken.')
1527 if not opts.message:
1528 opts.message = edit_message()
1531 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
1532 (actionsxml, cgi.escape(opts.message or ""))
1533 u = makeurl(apiurl, ['request'], query='cmd=create')
1534 f = http_POST(u, data=xml)
1536 root = ET.parse(f).getroot()
1537 return root.get('id')
1540 @cmdln.option('-m', '--message', metavar='TEXT',
1541 help='specify message TEXT')
1542 @cmdln.option('-r', '--role', metavar='role',
1543 help='specify user role (default: maintainer)')
1544 @cmdln.alias("reqbugownership")
1545 @cmdln.alias("requestbugownership")
1546 @cmdln.alias("reqmaintainership")
1547 @cmdln.alias("reqms")
1548 @cmdln.alias("reqbs")
1549 def do_requestmaintainership(self, subcmd, opts, *args):
1550 """${cmd_name}: requests to add user as maintainer or bugowner
1553 osc requestmaintainership # for current user in checked out package
1554 osc requestmaintainership USER # for specified user in checked out package
1555 osc requestmaintainership PROJECT PACKAGE # for current user
1556 osc requestmaintainership PROJECT PACKAGE USER # request for specified user
1558 osc requestbugownership ... # accepts same parameters but uses bugowner role
1563 args = slash_split(args)
1564 apiurl = self.get_api_url()
1569 user = conf.get_apiurl_usr(apiurl)
1570 elif len(args) == 3:
1574 elif len(args) < 2 and is_package_dir(os.curdir):
1575 project = store_read_project(os.curdir)
1576 package = store_read_package(os.curdir)
1578 user = conf.get_apiurl_usr(apiurl)
1582 raise oscerr.WrongArgs('Wrong number of arguments.')
1585 if subcmd in ( 'reqbugownership', 'requestbugownership', 'reqbs' ):
1589 if not role in ('maintainer', 'bugowner'):
1590 raise oscerr.WrongOptions('invalid \'--role\': either specify \'maintainer\' or \'bugowner\'')
1591 if not opts.message:
1592 opts.message = edit_message()
1595 if role == 'bugowner':
1596 r.add_action('set_bugowner', tgt_project=project, tgt_package=package,
1599 r.add_action('add_role', tgt_project=project, tgt_package=package,
1600 person_name=user, person_role=role)
1601 r.description = cgi.escape(opts.message or '')
1605 @cmdln.option('-m', '--message', metavar='TEXT',
1606 help='specify message TEXT')
1608 @cmdln.alias("dropreq")
1609 @cmdln.alias("droprequest")
1610 @cmdln.alias("deletereq")
1611 def do_deleterequest(self, subcmd, opts, *args):
1612 """${cmd_name}: Request to delete (or 'drop') a package or project
1615 osc deletereq [-m TEXT] # works in checked out project/package
1616 osc deletereq [-m TEXT] PROJECT [PACKAGE]
1621 args = slash_split(args)
1627 raise oscerr.WrongArgs('Too many arguments.')
1628 elif len(args) == 1:
1630 elif len(args) == 2:
1633 elif is_project_dir(os.getcwd()):
1634 project = store_read_project(os.curdir)
1635 elif is_package_dir(os.getcwd()):
1636 project = store_read_project(os.curdir)
1637 package = store_read_package(os.curdir)
1639 raise oscerr.WrongArgs('Please specify at least a project.')
1641 if not opts.message:
1643 if package is not None:
1644 footer=textwrap.TextWrapper(width = 66).fill(
1645 'please explain why you like to delete package %s of project %s'
1646 % (package,project))
1648 footer=textwrap.TextWrapper(width = 66).fill(
1649 'please explain why you like to delete project %s' % project)
1650 opts.message = edit_message(footer)
1653 r.add_action('delete', tgt_project=project, tgt_package=package)
1654 r.description = cgi.escape(opts.message)
1655 r.create(self.get_api_url())
1659 @cmdln.option('-m', '--message', metavar='TEXT',
1660 help='specify message TEXT')
1662 @cmdln.alias("changedevelreq")
1663 def do_changedevelrequest(self, subcmd, opts, *args):
1664 """${cmd_name}: Create request to change the devel package definition.
1666 [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration
1667 for information on this topic.]
1669 See the "request" command for showing and modifing existing requests.
1671 osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
1675 if len(args) == 0 and is_package_dir('.') and find_default_project():
1677 devel_project = store_read_project(wd)
1678 devel_package = package = store_read_package(wd)
1679 project = find_default_project(self.get_api_url(), package)
1681 raise oscerr.WrongArgs('Too few arguments.')
1683 raise oscerr.WrongArgs('Too many arguments.')
1685 devel_project = args[2]
1688 devel_package = package
1690 devel_package = args[3]
1692 if not opts.message:
1694 footer=textwrap.TextWrapper(width = 66).fill(
1695 'please explain why you like to change the devel project of %s/%s to %s/%s'
1696 % (project,package,devel_project,devel_package))
1697 opts.message = edit_message(footer)
1700 r.add_action('change_devel', src_project=devel_project, src_package=devel_package,
1701 tgt_project=project, tgt_package=package)
1702 r.description = cgi.escape(opts.message)
1703 r.create(self.get_api_url())
1707 @cmdln.option('-d', '--diff', action='store_true',
1708 help='generate a diff')
1709 @cmdln.option('-u', '--unified', action='store_true',
1710 help='output the diff in the unified diff format')
1711 @cmdln.option('-m', '--message', metavar='TEXT',
1712 help='specify message TEXT')
1713 @cmdln.option('-t', '--type', metavar='TYPE',
1714 help='limit to requests which contain a given action type (submit/delete/change_devel)')
1715 @cmdln.option('-a', '--all', action='store_true',
1716 help='all states. Same as\'-s all\'')
1717 @cmdln.option('-f', '--force', action='store_true',
1718 help='enforce state change, can be used to ignore open reviews')
1719 @cmdln.option('-s', '--state', default='', # default is 'all' if no args given, 'new,review' otherwise
1720 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]')
1721 @cmdln.option('-D', '--days', metavar='DAYS',
1722 help='only list requests in state "new" or changed in the last DAYS. [default=%(request_list_days)s]')
1723 @cmdln.option('-U', '--user', metavar='USER',
1724 help='requests or reviews limited for the specified USER')
1725 @cmdln.option('-G', '--group', metavar='GROUP',
1726 help='requests or reviews limited for the specified GROUP')
1727 @cmdln.option('-P', '--project', metavar='PROJECT',
1728 help='requests or reviews limited for the specified PROJECT')
1729 @cmdln.option('-p', '--package', metavar='PACKAGE',
1730 help='requests or reviews limited for the specified PACKAGE, requires also a PROJECT')
1731 @cmdln.option('-b', '--brief', action='store_true', default=False,
1732 help='print output in list view as list subcommand')
1733 @cmdln.option('-M', '--mine', action='store_true',
1734 help='only show requests created by yourself')
1735 @cmdln.option('-B', '--bugowner', action='store_true',
1736 help='also show requests about packages where I am bugowner')
1737 @cmdln.option('-e', '--edit', action='store_true',
1738 help='edit a submit action')
1739 @cmdln.option('-i', '--interactive', action='store_true',
1740 help='interactive review of request')
1741 @cmdln.option('--non-interactive', action='store_true',
1742 help='non-interactive review of request')
1743 @cmdln.option('--exclude-target-project', action='append',
1744 help='exclude target project from request list')
1745 @cmdln.option('--involved-projects', action='store_true',
1746 help='show all requests for project/packages where USER is involved')
1747 @cmdln.option('--source-buildstatus', action='store_true',
1748 help='print the buildstatus of the source package (only works with "show")')
1750 @cmdln.alias("review")
1751 # FIXME: rewrite this mess and split request and review
1752 def do_request(self, subcmd, opts, *args):
1753 """${cmd_name}: Show or modify requests and reviews
1755 [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration
1756 for information on this topic.]
1758 The 'request' command has the following sub commands:
1760 "list" lists open requests attached to a project or package or person.
1761 Uses the project/package of the current directory if none of
1762 -M, -U USER, project/package are given.
1764 "log" will show the history of the given ID
1766 "show" will show the request itself, and generate a diff for review, if
1767 used with the --diff option. The keyword show can be omitted if the ID is numeric.
1769 "decline" will change the request state to "declined"
1771 "reopen" will set the request back to new or review.
1773 "supersede" will supersede one request with another existing one.
1775 "revoke" will set the request state to "revoked"
1777 "accept" will change the request state to "accepted" and will trigger
1778 the actual submit process. That would normally be a server-side copy of
1779 the source package to the target package.
1781 "checkout" will checkout the request's source package ("submit" requests only).
1783 The 'review' command has the following sub commands:
1785 "list" lists open requests that need to be reviewed by the
1786 specified user or group
1788 "add" adds a person or group as reviewer to a request
1790 "accept" mark the review positive
1792 "decline" mark the review negative. A negative review will
1793 decline the request.
1796 osc request list [-M] [-U USER] [-s state] [-D DAYS] [-t type] [-B] [PRJ [PKG]]
1798 osc request [show] [-d] [-b] ID
1800 osc request accept [-m TEXT] ID
1801 osc request decline [-m TEXT] ID
1802 osc request revoke [-m TEXT] ID
1803 osc request reopen [-m TEXT] ID
1804 osc request supersede [-m TEXT] ID SUPERSEDING_ID
1805 osc request approvenew [-m TEXT] PROJECT
1807 osc request checkout/co ID
1808 osc request clone [-m TEXT] ID
1810 osc review list [-U USER] [-G GROUP] [-P PROJECT [-p PACKAGE]] [-s state]
1811 osc review add [-m TEXT] [-U USER] [-G GROUP] [-P PROJECT [-p PACKAGE]] ID
1812 osc review accept [-m TEXT] [-U USER] [-G GROUP] [-P PROJECT [-p PACKAGE]] ID
1813 osc review decline [-m TEXT] [-U USER] [-G GROUP] [-P PROJECT [-p PACKAGE]] ID
1814 osc review reopen [-m TEXT] [-U USER] [-G GROUP] [-P PROJECT [-p PACKAGE]] ID
1815 osc review supersede [-m TEXT] [-U USER] [-G GROUP] [-P PROJECT [-p PACKAGE]] ID SUPERSEDING_ID
1820 args = slash_split(args)
1822 if opts.all and opts.state:
1823 raise oscerr.WrongOptions('Sorry, the options \'--all\' and \'--state\' ' \
1824 'are mutually exclusive.')
1825 if opts.mine and opts.user:
1826 raise oscerr.WrongOptions('Sorry, the options \'--user\' and \'--mine\' ' \
1827 'are mutually exclusive.')
1828 if opts.interactive and opts.non_interactive:
1829 raise oscerr.WrongOptions('Sorry, the options \'--interactive\' and ' \
1830 '\'--non-interactive\' are mutually exclusive')
1835 if opts.state == '':
1838 if opts.state == '':
1839 opts.state = 'new,review'
1841 if args[0] == 'help':
1842 return self.do_help(['help', 'request'])
1844 cmds = [ 'list', 'log', 'show', 'decline', 'reopen', 'clone', 'accept', 'approvenew', 'wipe', 'supersede', 'revoke', 'checkout', 'co' ]
1845 if subcmd != 'review' and args[0] not in cmds:
1846 raise oscerr.WrongArgs('Unknown request action %s. Choose one of %s.' \
1847 % (args[0],', '.join(cmds)))
1848 cmds = [ 'list', 'add', 'decline', 'accept', 'reopen', 'supersede' ]
1849 if subcmd == 'review' and args[0] not in cmds:
1850 raise oscerr.WrongArgs('Unknown review action %s. Choose one of %s.' \
1851 % (args[0],', '.join(cmds)))
1856 apiurl = self.get_api_url()
1859 min_args, max_args = 0, 2
1860 elif cmd in ['supersede']:
1861 min_args, max_args = 2, 2
1863 min_args, max_args = 1, 1
1864 if len(args) < min_args:
1865 raise oscerr.WrongArgs('Too few arguments.')
1866 if len(args) > max_args:
1867 raise oscerr.WrongArgs('Too many arguments.')
1868 if cmd in ['add'] and not opts.user and not opts.group and not opts.project:
1869 raise oscerr.WrongArgs('No reviewer specified.')
1873 if cmd == 'list' or cmd == 'approvenew':
1878 elif not opts.mine and not opts.user:
1880 project = store_read_project(os.curdir)
1881 package = store_read_package(os.curdir)
1882 except oscerr.NoWorkingCopy:
1885 project = opts.project
1887 package = opts.package
1891 elif cmd == 'supersede':
1893 supersedid = args[1]
1894 elif cmd in ['log', 'add', 'show', 'decline', 'reopen', 'clone', 'accept', 'wipe', 'revoke', 'checkout', 'co']:
1897 # clone all packages from a given request
1898 if cmd in ['clone']:
1899 # should we force a message?
1900 print 'Cloned packages are available in project: %s' % clone_request(apiurl, reqid, opts.message)
1902 # add new reviewer to existing request
1903 elif cmd in ['add'] and subcmd == 'review':
1904 query = { 'cmd': 'addreview' }
1906 query['by_user'] = opts.user
1908 query['by_group'] = opts.group
1910 query['by_project'] = opts.project
1912 query['by_package'] = opts.package
1913 url = makeurl(apiurl, ['request', reqid], query)
1914 if not opts.message:
1915 opts.message = edit_message()
1916 r = http_POST(url, data=opts.message)
1917 print ET.parse(r).getroot().get('code')
1919 # list and approvenew
1920 elif cmd == 'list' or cmd == 'approvenew':
1921 states = ('new', 'accepted', 'revoked', 'declined', 'review', 'superseded')
1923 if cmd == 'approvenew':
1925 results = get_request_list(apiurl, project, package, '', ['new'])
1927 state_list = opts.state.split(',')
1929 state_list = ['all']
1930 if subcmd == 'review':
1931 state_list = ['review']
1932 elif opts.state == 'all':
1933 state_list = ['all']
1935 for s in state_list:
1936 if not s in states and not s == 'all':
1937 raise oscerr.WrongArgs('Unknown state \'%s\', try one of %s' % (s, ','.join(states)))
1939 who = conf.get_apiurl_usr(apiurl)
1943 ## FIXME -B not implemented!
1945 if (self.options.debug):
1946 print 'list: option --bugowner ignored: not impl.'
1948 if subcmd == 'review':
1949 # FIXME: do the review list for the user and for all groups he belong to
1950 results = get_review_list(apiurl, project, package, who, opts.group, opts.project, opts.package, state_list)
1952 if opts.involved_projects:
1953 who = who or conf.get_apiurl_usr(apiurl)
1954 results = get_user_projpkgs_request_list(apiurl, who, req_state=state_list,
1955 req_type=opts.type, exclude_projects=opts.exclude_target_project or [])
1957 results = get_request_list(apiurl, project, package, who,
1958 state_list, opts.type, opts.exclude_target_project or [])
1960 # Check if project actually exists if result list is empty
1963 show_project_meta(apiurl, project)
1964 print 'No results for {0}'.format(project)
1966 print 'Project {0} does not exist'.format(project)
1969 results.sort(reverse=True)
1970 days = opts.days or conf.config['request_list_days']
1977 since = time.strftime('%Y-%m-%dT%H:%M:%S',time.localtime(time.time()-days*24*3600))
1980 ## bs has received 2009-09-20 a new xquery compare() function
1981 ## which allows us to limit the list inside of get_request_list
1982 ## That would be much faster for coolo. But counting the remainder
1983 ## would not be possible with current xquery implementation.
1984 ## Workaround: fetch all, and filter on client side.
1986 ## FIXME: date filtering should become implemented on server side
1987 for result in results:
1988 if days == 0 or result.state.when > since or result.state.name == 'new':
1989 if (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive:
1990 request_interactive_review(apiurl, result)
1992 print result.list_view(), '\n'
1996 print "There are %d requests older than %s days.\n" % (skipped, days)
1998 if cmd == 'approvenew':
1999 print "\n *** Approve them all ? [y/n] ***"
2000 if sys.stdin.read(1) == "y":
2002 if not opts.message:
2003 opts.message = edit_message()
2004 for result in results:
2005 print result.reqid, ": ",
2006 r = change_request_state(apiurl,
2007 result.reqid, 'accepted', opts.message or '', force=opts.force)
2008 print 'Result of change request state: %s' % r
2010 print >>sys.stderr, 'Aborted...'
2011 raise oscerr.UserAbort()
2014 for l in get_request_log(apiurl, reqid):
2019 r = get_request(apiurl, reqid)
2023 if not r.get_actions('submit'):
2024 raise oscerr.WrongOptions('\'--edit\' not possible ' \
2025 '(request has no \'submit\' action)')
2026 return request_interactive_review(apiurl, r, 'e')
2027 elif (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive:
2028 return request_interactive_review(apiurl, r)
2031 if opts.source_buildstatus:
2032 sr_actions = r.get_actions('submit')
2034 raise oscerr.WrongOptions( '\'--source-buildstatus\' not possible ' \
2035 '(request has no \'submit\' actions)')
2036 for action in sr_actions:
2037 print 'Buildstatus for \'%s/%s\':' % (action.src_project, action.src_package)
2038 print '\n'.join(get_results(apiurl, action.src_project, action.src_package))
2042 # works since OBS 2.1
2043 diff = request_diff(apiurl, reqid)
2044 except urllib2.HTTPError, e:
2045 # for OBS 2.0 and before
2046 sr_actions = r.get_actions('submit')
2048 raise oscerr.WrongOptions('\'--diff\' not possible (request has no \'submit\' actions)')
2049 for action in sr_actions:
2050 diff += 'old: %s/%s\nnew: %s/%s\n' % (action.src_project, action.src_package,
2051 action.tgt_project, action.tgt_package)
2052 diff += submit_action_diff(apiurl, action)
2054 run_pager(diff, tmp_suffix='')
2057 elif cmd == 'checkout' or cmd == 'co':
2058 r = get_request(apiurl, reqid)
2059 sr_actions = r.get_actions('submit')
2061 raise oscerr.WrongArgs('\'checkout\' not possible (request has no \'submit\' actions)')
2062 for action in sr_actions:
2063 checkout_package(apiurl, action.src_project, action.src_package, \
2064 action.src_rev, expand_link=True, prj_dir=action.src_project)
2067 state_map = {'reopen' : 'new', 'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked', 'supersede' : 'superseded'}
2068 # Change review state only
2069 if subcmd == 'review':
2070 if not opts.message:
2071 opts.message = edit_message()
2072 if cmd in ['accept', 'decline', 'reopen', 'supersede']:
2073 if opts.user or opts.group or opts.project or opts.package:
2074 r = change_review_state(apiurl, reqid, state_map[cmd], opts.user, opts.group, opts.project,
2075 opts.package, opts.message or '', supersed=supersedid)
2078 rq = get_request(apiurl, reqid)
2079 if rq.state.name in ['new', 'review']:
2080 for review in rq.reviews: # try all, but do not fail on error
2082 r = change_review_state(apiurl, reqid, state_map[cmd], review.by_user, review.by_group,
2083 review.by_project, review.by_package, opts.message or '', supersed=supersedid)
2085 except urllib2.HTTPError, e:
2087 print 'No permission on review by user %s' % review.by_user
2089 print 'No permission on review by group %s' % review.by_group
2090 if review.by_package:
2091 print 'No permission on review by package %s / %s' % (review.by_project, review.by_package)
2092 elif review.by_project:
2093 print 'No permission on review by project %s' % review.by_project
2095 print 'Request is closed, please reopen the request first before changing any reviews.'
2096 # Change state of entire request
2097 elif cmd in ['reopen', 'accept', 'decline', 'wipe', 'revoke', 'supersede']:
2098 rq = get_request(apiurl, reqid)
2099 if rq.state.name == state_map[cmd]:
2100 repl = raw_input("\n *** The state of the request (#%s) is already '%s'. Change state anyway? [y/n] *** " % (reqid, rq.state.name))
2101 if repl.lower() != 'y':
2102 print >>sys.stderr, 'Aborted...'
2103 raise oscerr.UserAbort()
2105 if not opts.message:
2106 tmpl = change_request_state_template(rq, state_map[cmd])
2107 opts.message = edit_message(template=tmpl)
2108 r = change_request_state(apiurl,
2109 reqid, state_map[cmd], opts.message or '', supersed=supersedid, force=opts.force)
2110 print 'Result of change request state: %s' % r
2112 # editmeta and its aliases are all depracated
2113 @cmdln.alias("editprj")
2114 @cmdln.alias("createprj")
2115 @cmdln.alias("editpac")
2116 @cmdln.alias("createpac")
2117 @cmdln.alias("edituser")
2118 @cmdln.alias("usermeta")
2120 def do_editmeta(self, subcmd, opts, *args):
2123 Obsolete command to edit metadata. Use 'meta' now.
2125 See the help output of 'meta'.
2129 print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
2130 print >>sys.stderr, 'See \'osc help meta\'.'
2131 #self.do_help([None, 'meta'])
2135 @cmdln.option('-r', '--revision', metavar='rev',
2136 help='use the specified revision.')
2137 @cmdln.option('-R', '--use-plain-revision', action='store_true',
2138 help='Don\'t expand revsion based on baserev, the revision which was used when commit happened.')
2139 @cmdln.option('-b', '--use-baserev', action='store_true',
2140 help='Use the revisions which exists when the original commit happend and don\'t try to merge later commits.')
2141 @cmdln.option('-u', '--unset', action='store_true',
2142 help='remove revision in link, it will point always to latest revision')
2143 def do_setlinkrev(self, subcmd, opts, *args):
2144 """${cmd_name}: Updates a revision number in a source link.
2146 This command adds or updates a specified revision number in a source link.
2147 The current revision of the source is used, if no revision number is specified.
2151 osc setlinkrev PROJECT [PACKAGE]
2155 args = slash_split(args)
2156 apiurl = self.get_api_url()
2160 if opts.use_plain_revision:
2162 if opts.use_baserev:
2165 rev = parseRevisionOption(opts.revision)[0] or ''
2170 p = findpacs(os.curdir)[0]
2175 sys.exit('Local directory is no checked out source link package, aborting')
2176 elif len(args) == 2:
2179 elif len(args) == 1:
2182 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
2183 + self.get_cmd_help('setlinkrev'))
2186 packages = [package]
2188 packages = meta_get_packagelist(apiurl, project)
2191 print 'setting revision to %s for package %s' % (rev, p)
2192 set_link_rev(apiurl, project, p, revision=rev, expand=expand, baserev=baserev)
2195 def do_linktobranch(self, subcmd, opts, *args):
2196 """${cmd_name}: Convert a package containing a classic link with patch to a branch
2198 This command tells the server to convert a _link with or without a project.diff
2199 to a branch. This is a full copy with a _link file pointing to the branched place.
2202 osc linktobranch # can be used in checked out package
2203 osc linktobranch PROJECT PACKAGE
2206 args = slash_split(args)
2207 apiurl = self.get_api_url()
2211 project = store_read_project(wd)
2212 package = store_read_package(wd)
2213 update_local_dir = True
2215 raise oscerr.WrongArgs('Too few arguments (required none or two)')
2217 raise oscerr.WrongArgs('Too many arguments (required none or two)')
2221 update_local_dir = False
2224 link_to_branch(apiurl, project, package)
2225 if update_local_dir:
2227 pac.update(rev=pac.latest_rev())
2230 @cmdln.option('-m', '--message', metavar='TEXT',
2231 help='specify message TEXT')
2232 def do_detachbranch(self, subcmd, opts, *args):
2233 """${cmd_name}: replace a link with its expanded sources
2235 If a package is a link it is replaced with its expanded sources. The link
2236 does not exist anymore.
2239 osc detachbranch # can be used in package working copy
2240 osc detachbranch PROJECT PACKAGE
2243 args = slash_split(args)
2244 apiurl = self.get_api_url()
2246 project = store_read_project(os.curdir)
2247 package = store_read_package(os.curdir)
2248 elif len(args) == 2:
2249 project, package = args
2251 raise oscerr.WrongArgs('Too many arguments (required none or two)')
2253 raise oscerr.WrongArgs('Too few arguments (required none or two)')
2256 copy_pac(apiurl, project, package, apiurl, project, package, expand=True, comment=opts.message)
2257 except urllib2.HTTPError, e:
2258 root = ET.fromstring(show_files_meta(apiurl, project, package, 'latest', expand=False))
2260 li.read(root.find('linkinfo'))
2261 if li.islink() and li.haserror():
2262 raise oscerr.LinkExpandError(project, package, li.error)
2263 elif not li.islink():
2264 print >>sys.stderr, 'package \'%s/%s\' is no link' % (project, package)
2269 @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'],
2270 help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.')
2271 @cmdln.option('-c', '--current', action='store_true',
2272 help='link fixed against current revision.')
2273 @cmdln.option('-r', '--revision', metavar='rev',
2274 help='link the specified revision.')
2275 @cmdln.option('-f', '--force', action='store_true',
2276 help='overwrite an existing link file if it is there.')
2277 @cmdln.option('-d', '--disable-publish', action='store_true',
2278 help='disable publishing of the linked package')
2279 def do_linkpac(self, subcmd, opts, *args):
2280 """${cmd_name}: "Link" a package to another package
2282 A linked package is a clone of another package, but plus local
2283 modifications. It can be cross-project.
2285 The DESTPAC name is optional; the source packages' name will be used if
2288 Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
2290 To add a patch, add the patch as file and add it to the _link file.
2291 You can also specify text which will be inserted at the top of the spec file.
2293 See the examples in the _link file.
2295 NOTE: In case you are not aware about the difference of 'linkpac' and 'branch' command
2296 you should use the 'branch' command by default.
2299 osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
2303 args = slash_split(args)
2304 apiurl = self.get_api_url()
2306 if not args or len(args) < 3:
2307 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
2308 + self.get_cmd_help('linkpac'))
2310 rev, dummy = parseRevisionOption(opts.revision)
2312 src_project = args[0]
2313 src_package = args[1]
2314 dst_project = args[2]
2316 dst_package = args[3]
2318 dst_package = src_package
2320 if src_project == dst_project and src_package == dst_package:
2321 raise oscerr.WrongArgs('Error: source and destination are the same.')
2323 if src_project == dst_project and not opts.cicount:
2324 # in this case, the user usually wants to build different spec
2325 # files from the same source
2326 opts.cicount = "copy"
2329 rev = show_upstream_rev(apiurl, src_project, src_package)
2331 if rev and not checkRevision(src_project, src_package, rev):
2332 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2335 link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish)
2337 @cmdln.option('--nosources', action='store_true',
2338 help='ignore source packages when copying build results to destination project')
2339 @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]',
2340 help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]')
2341 @cmdln.option('-d', '--disable-publish', action='store_true',
2342 help='disable publishing of the aggregated package')
2343 def do_aggregatepac(self, subcmd, opts, *args):
2344 """${cmd_name}: "Aggregate" a package to another package
2346 Aggregation of a package means that the build results (binaries) of a
2347 package are basically copied into another project.
2348 This can be used to make packages available from building that are
2349 needed in a project but available only in a different project. Note
2350 that this is done at the expense of disk space. See
2351 http://en.opensuse.org/openSUSE:Build_Service_Tips_and_Tricks#link_and_aggregate
2352 for more information.
2354 The DESTPAC name is optional; the source packages' name will be used if
2358 osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
2362 args = slash_split(args)
2364 if not args or len(args) < 3:
2365 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
2366 + self.get_cmd_help('aggregatepac'))
2368 src_project = args[0]
2369 src_package = args[1]
2370 dst_project = args[2]
2372 dst_package = args[3]
2374 dst_package = src_package
2376 if src_project == dst_project and src_package == dst_package:
2377 raise oscerr.WrongArgs('Error: source and destination are the same.')
2381 for pair in opts.map_repo.split(','):
2382 src_tgt = pair.split('=')
2383 if len(src_tgt) != 2:
2384 raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo)
2385 repo_map[src_tgt[0]] = src_tgt[1]
2387 aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish, opts.nosources)
2390 @cmdln.option('-c', '--client-side-copy', action='store_true',
2391 help='do a (slower) client-side copy')
2392 @cmdln.option('-k', '--keep-maintainers', action='store_true',
2393 help='keep original maintainers. Default is remove all and replace with the one calling the script.')
2394 @cmdln.option('-d', '--keep-develproject', action='store_true',
2395 help='keep develproject tag in the package metadata')
2396 @cmdln.option('-r', '--revision', metavar='rev',
2397 help='link the specified revision.')
2398 @cmdln.option('-t', '--to-apiurl', metavar='URL',
2399 help='URL of destination api server. Default is the source api server.')
2400 @cmdln.option('-m', '--message', metavar='TEXT',
2401 help='specify message TEXT')
2402 @cmdln.option('-e', '--expand', action='store_true',
2403 help='if the source package is a link then copy the expanded version of the link')
2404 def do_copypac(self, subcmd, opts, *args):
2405 """${cmd_name}: Copy a package
2407 A way to copy package to somewhere else.
2409 It can be done across buildservice instances, if the -t option is used.
2410 In that case, a client-side copy and link expansion are implied.
2412 Using --client-side-copy always involves downloading all files, and
2413 uploading them to the target.
2415 The DESTPAC name is optional; the source packages' name will be used if
2419 osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
2423 args = slash_split(args)
2425 if not args or len(args) < 3:
2426 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
2427 + self.get_cmd_help('copypac'))
2429 src_project = args[0]
2430 src_package = args[1]
2431 dst_project = args[2]
2433 dst_package = args[3]
2435 dst_package = src_package
2437 src_apiurl = conf.config['apiurl']
2439 dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl)
2441 dst_apiurl = src_apiurl
2443 if src_apiurl != dst_apiurl:
2444 opts.client_side_copy = True
2447 rev, dummy = parseRevisionOption(opts.revision)
2450 comment = opts.message
2453 rev = show_upstream_rev(src_apiurl, src_project, src_package)
2454 comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev )
2456 if src_project == dst_project and \
2457 src_package == dst_package and \
2459 src_apiurl == dst_apiurl:
2460 raise oscerr.WrongArgs('Source and destination are the same.')
2462 r = copy_pac(src_apiurl, src_project, src_package,
2463 dst_apiurl, dst_project, dst_package,
2464 client_side_copy=opts.client_side_copy,
2465 keep_maintainers=opts.keep_maintainers,
2466 keep_develproject=opts.keep_develproject,
2473 @cmdln.option('-m', '--message', metavar='TEXT',
2474 help='specify message TEXT')
2475 def do_releaserequest(self, subcmd, opts, *args):
2476 """${cmd_name}: Create a request for releasing a maintenance update.
2478 [See http://doc.opensuse.org/products/draft/OBS/obs-reference-guide/cha.obs.maintenance_setup.html
2479 for information on this topic.]
2481 This command is used by the maintence team to start the release process of a maintenance update.
2482 This includes usually testing based on the defined reviewers of the update project.
2485 osc releaserequest [ SOURCEPROJECT ]
2490 # FIXME: additional parameters can be a certain repo list to create a partitial release
2492 args = slash_split(args)
2493 apiurl = self.get_api_url()
2495 source_project = None
2498 raise oscerr.WrongArgs('Too many arguments.')
2500 if len(args) == 0 and is_project_dir(os.curdir):
2501 source_project = store_read_project(os.curdir)
2502 elif len(args) == 0:
2503 raise oscerr.WrongArgs('Too few arguments.')
2505 source_project = args[0]
2507 if not opts.message:
2508 opts.message = edit_message()
2510 r = create_release_request(apiurl, source_project, opts.message)
2515 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
2516 help='Use this attribute to find default maintenance project (default is OBS:MaintenanceProject)')
2517 @cmdln.option('--noaccess', action='store_true',
2518 help='Create a hidden project')
2519 @cmdln.option('-m', '--message', metavar='TEXT',
2520 help='specify message TEXT')
2521 def do_createincident(self, subcmd, opts, *args):
2522 """${cmd_name}: Create a maintenance incident
2524 [See http://doc.opensuse.org/products/draft/OBS/obs-reference-guide/cha.obs.maintenance_setup.html
2525 for information on this topic.]
2527 This command is asking to open an empty maintence incident. This can usually only be done by a responsible
2529 Please see the "mbranch" command on how to full such a project content and
2530 the "patchinfo" command how add the required maintenance update information.
2533 osc createincident [ MAINTENANCEPROJECT ]
2537 args = slash_split(args)
2538 apiurl = self.get_api_url()
2539 maintenance_attribute = conf.config['maintenance_attribute']
2541 maintenance_attribute = opts.attribute
2543 source_project = target_project = None
2546 raise oscerr.WrongArgs('Too many arguments.')
2549 target_project = args[0]
2551 xpath = 'attribute/@name = \'%s\'' % maintenance_attribute
2552 res = search(apiurl, project_id=xpath)
2553 root = res['project_id']
2554 project = root.find('project')
2556 sys.exit('Unable to find defined OBS:MaintenanceProject project on server.')
2557 target_project = project.get('name')
2558 print 'Using target project \'%s\'' % target_project
2560 query = { 'cmd': 'createmaintenanceincident' }
2562 query["noaccess"] = 1
2563 url = makeurl(apiurl, ['source', target_project], query=query)
2564 r = http_POST(url, data=opts.message)
2566 for i in ET.fromstring(r.read()).findall('data'):
2567 if i.get('name') == 'targetproject':
2568 project = i.text.strip()
2570 print "Incident project created: ", project
2572 print ET.parse(r).getroot().get('code')
2573 print ET.parse(r).getroot().get('error')
2576 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
2577 help='Use this attribute to find default maintenance project (default is OBS:MaintenanceProject)')
2578 @cmdln.option('-m', '--message', metavar='TEXT',
2579 help='specify message TEXT')
2580 def do_maintenancerequest(self, subcmd, opts, *args):
2581 """${cmd_name}: Create a request for starting a maintenance incident.
2583 [See http://doc.opensuse.org/products/draft/OBS/obs-reference-guide/cha.obs.maintenance_setup.html
2584 for information on this topic.]
2586 This command is asking the maintence team to start a maintence incident based on a
2587 created maintenance update. Please see the "mbranch" command on how to create such a project and
2588 the "patchinfo" command how add the required maintenance update information.
2591 osc maintenancerequest [ SOURCEPROJECT [ TARGETPROJECT ] ]
2595 args = slash_split(args)
2596 apiurl = self.get_api_url()
2597 maintenance_attribute = conf.config['maintenance_attribute']
2599 maintenance_attribute = opts.attribute
2601 source_project = target_project = None
2604 raise oscerr.WrongArgs('Too many arguments.')
2606 if len(args) == 0 and is_project_dir(os.curdir):
2607 source_project = store_read_project(os.curdir)
2608 elif len(args) == 0:
2609 raise oscerr.WrongArgs('Too few arguments.')
2611 source_project = args[0]
2614 target_project = args[1]
2616 xpath = 'attribute/@name = \'%s\'' % maintenance_attribute
2617 res = search(apiurl, project_id=xpath)
2618 root = res['project_id']
2619 project = root.find('project')
2621 sys.exit('Unable to find defined OBS:MaintenanceProject project on server.')
2622 target_project = project.get('name')
2623 print 'Using target project \'%s\'' % target_project
2625 if not opts.message:
2626 opts.message = edit_message()
2628 r = create_maintenance_request(apiurl, source_project, target_project, opts.message)
2632 @cmdln.option('-c', '--checkout', action='store_true',
2633 help='Checkout branched package afterwards ' \
2634 '(\'osc bco\' is a shorthand for this option)' )
2635 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
2636 help='Use this attribute to find affected packages (default is OBS:Maintained)')
2637 @cmdln.option('--noaccess', action='store_true',
2638 help='Create a hidden project')
2639 @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE',
2640 help='Use this attribute to find update projects (default is OBS:UpdateProject) ')
2641 def do_mbranch(self, subcmd, opts, *args):
2642 """${cmd_name}: Multiple branch of a package
2644 [See http://en.opensuse.org/openSUSE:Build_Service_Concept_Maintenance
2645 for information on this topic.]
2647 This command is used for creating multiple links of defined version of a package
2648 in one project. This is esp. used for maintenance updates.
2650 The branched package will live in
2651 home:USERNAME:branches:ATTRIBUTE:PACKAGE
2652 if nothing else specified.
2655 osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ]
2658 args = slash_split(args)
2659 apiurl = self.get_api_url()
2662 maintained_attribute = conf.config['maintained_attribute']
2663 maintained_update_project_attribute = conf.config['maintained_update_project_attribute']
2665 if not len(args) or len(args) > 2:
2666 raise oscerr.WrongArgs('Wrong number of arguments.')
2672 r = attribute_branch_pkg(apiurl, maintained_attribute, maintained_update_project_attribute, \
2673 package, tproject, noaccess = opts.noaccess)
2676 print >>sys.stderr, 'ERROR: Attribute branch call came not back with a project.'
2679 print "Project " + r + " created."
2682 Project.init_project(apiurl, r, r, conf.config['do_package_tracking'])
2683 print statfrmt('A', r)
2686 for package in meta_get_packagelist(apiurl, r):
2688 checkout_package(apiurl, r, package, expand_link = True, prj_dir = r)
2690 print >>sys.stderr, 'Error while checkout package:\n', package
2692 if conf.config['verbose']:
2693 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
2696 @cmdln.alias('branchco')
2698 @cmdln.alias('getpac')
2699 @cmdln.option('--nodevelproject', action='store_true',
2700 help='do not follow a defined devel project ' \
2701 '(primary project where a package is developed)')
2702 @cmdln.option('-c', '--checkout', action='store_true',
2703 help='Checkout branched package afterwards using "co -e -S"' \
2704 '(\'osc bco\' is a shorthand for this option)' )
2705 @cmdln.option('-f', '--force', default=False, action="store_true",
2706 help='force branch, overwrite target')
2707 @cmdln.option('--noaccess', action='store_true',
2708 help='Create a hidden project')
2709 @cmdln.option('-m', '--message', metavar='TEXT',
2710 help='specify message TEXT')
2711 @cmdln.option('-r', '--revision', metavar='rev',
2712 help='branch against a specific revision')
2713 def do_branch(self, subcmd, opts, *args):
2714 """${cmd_name}: Branch a package
2716 [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration
2717 for information on this topic.]
2719 Create a source link from a package of an existing project to a new
2720 subproject of the requesters home project (home:branches:)
2722 The branched package will live in
2723 home:USERNAME:branches:PROJECT/PACKAGE
2724 if nothing else specified.
2726 With getpac or bco, the branched package will come from one of
2727 %(getpac_default_project)s
2728 (list of projects from oscrc:getpac_default_project)
2729 if nothing else is specfied on the command line.
2733 osc branch SOURCEPROJECT SOURCEPACKAGE
2734 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT
2735 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE
2736 osc getpac SOURCEPACKAGE
2741 if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': opts.checkout = True
2742 args = slash_split(args)
2743 tproject = tpackage = None
2745 if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1:
2746 def_p = find_default_project(self.get_api_url(), args[0])
2747 print >>sys.stderr, 'defaulting to %s/%s' % (def_p, args[0])
2748 # python has no args.unshift ???
2749 args = [ def_p, args[0] ]
2751 if len(args) == 0 and is_package_dir('.'):
2752 args = (store_read_project('.'), store_read_package('.'))
2754 if len(args) < 2 or len(args) > 4:
2755 raise oscerr.WrongArgs('Wrong number of arguments.')
2757 apiurl = self.get_api_url()
2759 expected = 'home:%s:branches:%s' % (conf.get_apiurl_usr(apiurl), args[0])
2761 expected = tproject = args[2]
2765 exists, targetprj, targetpkg, srcprj, srcpkg = \
2766 branch_pkg(apiurl, args[0], args[1],
2767 nodevelproject=opts.nodevelproject, rev=opts.revision,
2768 target_project=tproject, target_package=tpackage,
2769 return_existing=opts.checkout, msg=opts.message or '',
2770 force=opts.force, noaccess=opts.noaccess)
2772 print >>sys.stderr, 'Using existing branch project: %s' % targetprj
2775 if not exists and (srcprj != args[0] or srcpkg != args[1]):
2777 root = ET.fromstring(''.join(show_attribute_meta(apiurl, args[0], None, None,
2778 conf.config['maintained_update_project_attribute'], False, False)))
2779 # this might raise an AttributeError
2780 uproject = root.find('attribute').find('value').text
2781 print '\nNote: The branch has been created from the configured update project: %s' \
2783 except (AttributeError, urllib2.HTTPError), e:
2785 print '\nNote: The branch has been created of a different project,\n' \
2787 ' which is the primary location of where development for\n' \
2788 ' that package takes place.\n' \
2789 ' That\'s also where you would normally make changes against.\n' \
2790 ' A direct branch of the specified package can be forced\n' \
2791 ' with the --nodevelproject option.\n' % devloc
2793 package = tpackage or args[1]
2795 checkout_package(apiurl, targetprj, package, server_service_files=True,
2796 expand_link=True, prj_dir=targetprj)
2797 if conf.config['verbose']:
2798 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
2801 if conf.get_configParser().get('general', 'apiurl') != apiurl:
2802 apiopt = '-A %s ' % apiurl
2803 print 'A working copy of the branched package can be checked out with:\n\n' \
2805 % (apiopt, targetprj, package)
2806 print_request_list(apiurl, args[0], args[1])
2808 print_request_list(apiurl, devloc, srcpkg)
2811 @cmdln.option('-m', '--message', metavar='TEXT',
2812 help='specify log message TEXT')
2813 def do_undelete(self, subcmd, opts, *args):
2814 """${cmd_name}: Restores a deleted project or package on the server.
2816 The server restores a package including the sources and meta configuration.
2817 Binaries remain to be lost and will be rebuild.
2820 osc undelete PROJECT
2821 osc undelete PROJECT PACKAGE [PACKAGE ...]
2826 args = slash_split(args)
2828 raise oscerr.WrongArgs('Missing argument.')
2834 msg = edit_message()
2836 apiurl = self.get_api_url()
2842 undelete_package(apiurl, prj, pkg, msg)
2844 undelete_project(apiurl, prj, msg)
2847 @cmdln.option('-r', '--recursive', action='store_true',
2848 help='deletes a project with packages inside')
2849 @cmdln.option('-f', '--force', action='store_true',
2850 help='deletes a project where other depends on')
2851 @cmdln.option('-m', '--message', metavar='TEXT',
2852 help='specify log message TEXT')
2853 def do_rdelete(self, subcmd, opts, *args):
2854 """${cmd_name}: Delete a project or packages on the server.
2856 As a safety measure, project must be empty (i.e., you need to delete all
2857 packages first). Also, packages must have no requests pending (i.e., you need
2858 to accept/revoke such requests first).
2859 If you are sure that you want to remove this project and all
2860 its packages use \'--recursive\' switch.
2861 It may still not work because other depends on it. If you want to ignore this as
2862 well use \'--force\' switch.
2865 osc rdelete [-r] [-f] PROJECT [PACKAGE]
2870 args = slash_split(args)
2871 if len(args) < 1 or len(args) > 2:
2872 raise oscerr.WrongArgs('Wrong number of arguments')
2874 apiurl = self.get_api_url()
2881 msg = edit_message()
2883 # empty arguments result in recursive project delete ...
2885 raise oscerr.WrongArgs('Project argument is empty')
2891 raise oscerr.WrongArgs('Package argument is empty')
2893 ## FIXME: core.py:commitDelPackage() should have something similar
2894 rlist = get_request_list(apiurl, prj, pkg)
2895 for rq in rlist: print rq
2896 if len(rlist) >= 1 and not opts.force:
2897 print >>sys.stderr, 'Package has pending requests. Deleting the package will break them. '\
2898 'They should be accepted/declined/revoked before deleting the package. '\
2899 'Or just use \'--force\'.'
2902 delete_package(apiurl, prj, pkg, opts.force, msg)
2904 elif (not opts.recursive) and len(meta_get_packagelist(apiurl, prj)) >= 1:
2905 print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
2906 'If you are sure that you want to remove this project and all its ' \
2907 'packages use the \'--recursive\' switch.'
2910 delete_project(apiurl, prj, opts.force, msg)
2913 def do_deletepac(self, subcmd, opts, *args):
2914 print """${cmd_name} is obsolete !
2917 osc delete for checked out packages or projects
2919 osc rdelete for server side operations."""
2924 @cmdln.option('-f', '--force', action='store_true',
2925 help='deletes a project and its packages')
2926 def do_deleteprj(self, subcmd, opts, project):
2927 """${cmd_name} is obsolete !
2934 @cmdln.alias('metafromspec')
2935 @cmdln.option('', '--specfile', metavar='FILE',
2936 help='Path to specfile. (if you pass more than working copy this option is ignored)')
2937 def do_updatepacmetafromspec(self, subcmd, opts, *args):
2938 """${cmd_name}: Update package meta information from a specfile
2940 ARG, if specified, is a package working copy.
2946 args = parseargs(args)
2947 if opts.specfile and len(args) == 1:
2948 specfile = opts.specfile
2951 pacs = findpacs(args)
2953 p.read_meta_from_spec(specfile)
2954 p.update_package_meta()
2957 @cmdln.alias('linkdiff')
2958 @cmdln.alias('ldiff')
2960 @cmdln.option('-c', '--change', metavar='rev',
2961 help='the change made by revision rev (like -r rev-1:rev).'
2962 'If rev is negative this is like -r rev:rev-1.')
2963 @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
2964 help='If rev1 is specified it will compare your working copy against '
2965 'the revision (rev1) on the server. '
2966 'If rev1 and rev2 are specified it will compare rev1 against rev2 '
2967 '(NOTE: changes in your working copy are ignored in this case)')
2968 @cmdln.option('-p', '--plain', action='store_true',
2969 help='output the diff in plain (not unified) diff format')
2970 @cmdln.option('-l', '--link', action='store_true',
2971 help='(osc linkdiff): compare against the base revision of the link')
2972 @cmdln.option('--missingok', action='store_true',
2973 help='do not fail if the source or target project/package does not exist on the server')
2974 def do_diff(self, subcmd, opts, *args):
2975 """${cmd_name}: Generates a diff
2977 Generates a diff, comparing local changes against the repository
2981 ARG, if specified, is a filename to include in the diff.
2986 Compare current checkout directory against the link base.
2988 osc diff --link PROJ PACK
2989 osc linkdiff PROJ PACK
2990 Compare a package against the link base (ignoring working copy changes).
2995 if (subcmd == 'ldiff' or subcmd == 'linkdiff'):
2997 args = parseargs(args)
3000 if not opts.link or not len(args) == 2:
3001 pacs = findpacs(args)
3005 query = { 'rev': 'latest' }
3007 u = makeurl(pacs[0].apiurl, ['source', pacs[0].prjname, pacs[0].name], query=query)
3009 u = makeurl(self.get_api_url(), ['source', args[0], args[1]], query=query)
3011 root = ET.parse(f).getroot()
3012 linkinfo = root.find('linkinfo')
3013 if linkinfo == None:
3014 raise oscerr.APIError('package is not a source link')
3015 baserev = linkinfo.get('baserev')
3016 opts.revision = baserev
3018 print "diff working copy against linked revision %s\n" % baserev
3020 print "diff commited package against linked revision %s\n" % baserev
3021 run_pager(server_diff(self.get_api_url(), args[0], args[1], baserev,
3022 args[0], args[1], linkinfo.get('lsrcmd5'), not opts.plain, opts.missingok))
3027 rev = int(opts.change)
3037 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
3040 rev1, rev2 = parseRevisionOption(opts.revision)
3044 for i in pac.get_diff(rev1):
3045 sys.stdout.write(''.join(i))
3047 diff += server_diff_noex(pac.apiurl, pac.prjname, pac.name, rev1,
3048 pac.prjname, pac.name, rev2, not opts.plain, opts.missingok)
3052 @cmdln.option('--oldprj', metavar='OLDPRJ',
3053 help='project to compare against'
3054 ' (deprecated, use 3 argument form)')
3055 @cmdln.option('--oldpkg', metavar='OLDPKG',
3056 help='package to compare against'
3057 ' (deprecated, use 3 argument form)')
3058 @cmdln.option('-M', '--meta', action='store_true',
3059 help='diff meta data')
3060 @cmdln.option('-r', '--revision', metavar='N[:M]',
3061 help='revision id, where N = old revision and M = new revision')
3062 @cmdln.option('-p', '--plain', action='store_true',
3063 help='output the diff in plain (not unified) diff format')
3064 @cmdln.option('-c', '--change', metavar='rev',
3065 help='the change made by revision rev (like -r rev-1:rev). '
3066 'If rev is negative this is like -r rev:rev-1.')
3067 @cmdln.option('--missingok', action='store_true',
3068 help='do not fail if the source or target project/package does not exist on the server')
3069 @cmdln.option('-u', '--unexpand', action='store_true',
3070 help='diff unexpanded version if sources are linked')
3071 def do_rdiff(self, subcmd, opts, *args):
3072 """${cmd_name}: Server-side "pretty" diff of two packages
3074 Compares two packages (three or four arguments) or shows the
3075 changes of a specified revision of a package (two arguments)
3077 If no revision is specified the latest revision is used.
3079 Note that this command doesn't return a normal diff (which could be
3080 applied as patch), but a "pretty" diff, which also compares the content
3085 osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC]
3086 osc ${cmd_name} PROJECT PACKAGE
3090 args = slash_split(args)
3091 apiurl = self.get_api_url()