1 # Copyright (C) 2006 Novell Inc. All rights reserved.
2 # This program is free software; it may be used, copied, modified
3 # and distributed under the terms of the GNU General Public Licence,
4 # either version 2, or version 3 (at your option).
11 import urlgrabber.progress
12 from optparse import SUPPRESS_HELP
14 MAN_HEADER = r""".TH %(ucname)s "1" "%(date)s" "%(name)s %(version)s" "User Commands"
16 %(name)s \- openSUSE build service command-line tool.
19 [\fIGLOBALOPTS\fR] \fISUBCOMMAND \fR[\fIOPTS\fR] [\fIARGS\fR...]
24 openSUSE build service command-line tool.
28 Type 'osc help <subcommand>' for more detailed help on a specific subcommand.
30 For additional information, see
31 * http://en.opensuse.org/Build_Service_Tutorial
32 * http://en.opensuse.org/Build_Service/CLI
34 You can modify osc commands, or roll you own, via the plugin API:
35 * http://en.opensuse.org/Build_Service/osc_plugins
37 osc was written by several authors. This man page is automatically generated.
40 class Osc(cmdln.Cmdln):
41 """Usage: osc [GLOBALOPTS] SUBCOMMAND [OPTS] [ARGS...]
42 or: osc help SUBCOMMAND
44 openSUSE build service command-line tool.
45 Type 'osc help <subcommand>' for help on a specific subcommand.
50 For additional information, see
51 * http://en.opensuse.org/Build_Service_Tutorial
52 * http://en.opensuse.org/Build_Service/CLI
54 You can modify osc commands, or roll you own, via the plugin API:
55 * http://en.opensuse.org/Build_Service/osc_plugins
60 man_header = MAN_HEADER
61 man_footer = MAN_FOOTER
63 def __init__(self, *args, **kwargs):
64 cmdln.Cmdln.__init__(self, *args, **kwargs)
65 cmdln.Cmdln.do_help.aliases.append('h')
67 def get_version(self):
68 return get_osc_version()
70 def get_optparser(self):
71 """this is the parser for "global" options (not specific to subcommand)"""
73 optparser = cmdln.CmdlnOptionParser(self, version=get_osc_version())
74 optparser.add_option('--debugger', action='store_true',
75 help='jump into the debugger before executing anything')
76 optparser.add_option('--post-mortem', action='store_true',
77 help='jump into the debugger in case of errors')
78 optparser.add_option('-t', '--traceback', action='store_true',
79 help='print call trace in case of errors')
80 optparser.add_option('-H', '--http-debug', action='store_true',
81 help='debug HTTP traffic')
82 optparser.add_option('-d', '--debug', action='store_true',
83 help='print info useful for debugging')
84 optparser.add_option('-A', '--apiurl', dest='apiurl',
86 help='specify URL to access API server at or an alias')
87 optparser.add_option('-c', '--config', dest='conffile',
89 help='specify alternate configuration file')
90 optparser.add_option('--no-keyring', action='store_true',
91 help='disable usage of desktop keyring system')
92 optparser.add_option('--no-gnome-keyring', action='store_true',
93 help='disable usage of GNOME Keyring')
94 optparser.add_option('-v', '--verbose', dest='verbose', action='count', default=0,
95 help='increase verbosity')
96 optparser.add_option('-q', '--quiet', dest='verbose', action='store_const', const=-1,
97 help='be quiet, not verbose')
101 def postoptparse(self, try_again = True):
102 """merge commandline options into the config"""
104 conf.get_config(override_conffile = self.options.conffile,
105 override_apiurl = self.options.apiurl,
106 override_debug = self.options.debug,
107 override_http_debug = self.options.http_debug,
108 override_traceback = self.options.traceback,
109 override_post_mortem = self.options.post_mortem,
110 override_no_keyring = self.options.no_keyring,
111 override_no_gnome_keyring = self.options.no_gnome_keyring,
112 override_verbose = self.options.verbose)
113 except oscerr.NoConfigfile, e:
114 print >>sys.stderr, e.msg
115 print >>sys.stderr, 'Creating osc configuration file %s ...' % e.file
118 config['user'] = raw_input('Username: ')
119 config['pass'] = getpass.getpass()
120 if self.options.no_keyring:
121 config['use_keyring'] = '0'
122 if self.options.no_gnome_keyring:
123 config['gnome_keyring'] = '0'
124 if self.options.apiurl:
125 config['apiurl'] = self.options.apiurl
127 conf.write_initial_config(e.file, config)
128 print >>sys.stderr, 'done'
129 if try_again: self.postoptparse(try_again = False)
130 except oscerr.ConfigMissingApiurl, e:
131 print >>sys.stderr, e.msg
133 user = raw_input('Username: ')
134 passwd = getpass.getpass()
135 conf.add_section(e.file, e.url, user, passwd)
136 if try_again: self.postoptparse(try_again = False)
138 self.options.verbose = conf.config['verbose']
139 self.download_progress = None
140 if conf.config.get('show_download_progress', False):
141 from meter import TextMeter
142 self.download_progress = TextMeter(hide_finished=True)
145 def get_cmd_help(self, cmdname):
146 doc = self._get_cmd_handler(cmdname).__doc__
147 doc = self._help_reindent(doc)
148 doc = self._help_preprocess(doc, cmdname)
149 doc = doc.rstrip() + '\n' # trim down trailing space
150 return self._str(doc)
152 def get_api_url(self):
153 localdir = os.getcwd()
154 if (is_package_dir(localdir) or is_project_dir(localdir)) and not self.options.apiurl:
155 return store_read_apiurl(os.curdir)
157 return conf.config['apiurl']
159 # overridden from class Cmdln() to use config variables in help texts
160 def _help_preprocess(self, help, cmdname):
161 help = cmdln.Cmdln._help_preprocess(self, help, cmdname)
162 return help % conf.config
165 def do_init(self, subcmd, opts, project, package=None):
166 """${cmd_name}: Initialize a directory as working copy
168 Initialize an existing directory to be a working copy of an
169 (already existing) buildservice project/package.
171 (This is the same as checking out a package and then copying sources
172 into the directory. It does NOT create a new package. To create a
173 package, use 'osc meta pkg ... ...')
175 You wouldn't normally use this command.
177 To get a working copy of a package (e.g. for building it or working on
178 it, you would normally use the checkout command. Use "osc help
179 checkout" to get help for it.
188 init_project_dir(conf.config['apiurl'], os.curdir, project)
189 print 'Initializing %s (Project: %s)' % (os.curdir, project)
191 init_package_dir(conf.config['apiurl'], project, package, os.path.curdir)
192 print 'Initializing %s (Project: %s, Package: %s)' % (os.curdir, project, package)
198 @cmdln.option('-a', '--arch', metavar='ARCH',
199 help='specify architecture (only for binaries)')
200 @cmdln.option('-r', '--repo', metavar='REPO',
201 help='specify repository (only for binaries)')
202 @cmdln.option('-b', '--binaries', action='store_true',
203 help='list built binaries instead of sources')
204 @cmdln.option('-R', '--revision', metavar='REVISION',
205 help='specify revision (only for sources)')
206 @cmdln.option('-e', '--expand', action='store_true',
207 help='expand linked package (only for sources)')
208 @cmdln.option('-u', '--unexpand', action='store_true',
209 help='always work with unexpanded (source) packages')
210 @cmdln.option('-v', '--verbose', action='store_true',
211 help='print extra information')
212 @cmdln.option('-l', '--long', action='store_true', dest='verbose',
213 help='print extra information')
214 @cmdln.option('-D', '--deleted', action='store_true',
215 help='show only the former deleted projects or packages')
216 def do_list(self, subcmd, opts, *args):
217 """${cmd_name}: List sources or binaries on the server
219 Examples for listing sources:
220 ls # list all projects
221 ls PROJECT # list packages in a project
222 ls PROJECT PACKAGE # list source files of package of a project
223 ls PROJECT PACKAGE <file> # list <file> if this file exists
224 ls -v PROJECT PACKAGE # verbosely list source files of package
225 ls -l PROJECT PACKAGE # verbosely list source files of package
226 ll PROJECT PACKAGE # verbosely list source files of package
227 LL PROJECT PACKAGE # verbosely list source files of expanded link
229 With --verbose, the following fields will be shown for each item:
231 Revision number of the last commit
233 Date and time of the last commit
235 Examples for listing binaries:
236 ls -b PROJECT # list all binaries of a project
237 ls -b PROJECT -a ARCH # list ARCH binaries of a project
238 ls -b PROJECT -r REPO # list binaries in REPO
239 ls -b PROJECT PACKAGE REPO ARCH
242 ${cmd_name} [PROJECT [PACKAGE]]
243 ${cmd_name} -b [PROJECT [PACKAGE [REPO [ARCH]]]]
247 apiurl = conf.config['apiurl']
248 args = slash_split(args)
251 if subcmd == 'lL' or subcmd == 'LL':
263 raise oscerr.WrongArgs("Too many arguments when listing deleted packages")
266 raise oscerr.WrongArgs("Too many arguments when listing deleted packages")
269 if opts.repo != args[2]:
270 raise oscerr.WrongArgs("conflicting repos specified ('%s' vs '%s')"%(opts.repo, args[2]))
277 if not opts.binaries:
278 raise oscerr.WrongArgs('Too many arguments')
280 if opts.arch != args[3]:
281 raise oscerr.WrongArgs("conflicting archs specified ('%s' vs '%s')"%(opts.arch, args[3]))
286 if opts.binaries and opts.expand:
287 raise oscerr.WrongOptions('Sorry, --binaries and --expand are mutual exclusive.')
291 # ls -b toplevel doesn't make sense, so use info from
292 # current dir if available
295 if is_project_dir(dir):
296 project = store_read_project(dir)
297 elif is_package_dir(dir):
298 project = store_read_project(dir)
299 package = store_read_package(dir)
301 apiurl = self.get_api_url()
304 raise oscerr.WrongArgs('There are no binaries to list above project level.')
306 raise oscerr.WrongOptions('Sorry, the --revision option is not supported for binaries.')
310 if opts.repo and opts.arch:
311 repos.append(Repo(opts.repo, opts.arch))
312 elif opts.repo and not opts.arch:
313 repos = [repo for repo in get_repos_of_project(apiurl, project) if repo.name == opts.repo]
314 elif opts.arch and not opts.repo:
315 repos = [repo for repo in get_repos_of_project(apiurl, project) if repo.arch == opts.arch]
317 repos = get_repos_of_project(apiurl, project)
321 results.append((repo, get_binarylist(apiurl, project, repo.name, repo.arch, package=package, verbose=opts.verbose)))
323 for result in results:
326 print '%s/%s' % (result[0].name, result[0].arch)
331 print "%9d %s %-40s" % (f.size, shorttime(f.mtime), f.name)
337 elif not opts.binaries:
339 print '\n'.join(meta_get_project_list(conf.config['apiurl'], opts.deleted))
343 if self.options.verbose:
344 print >>sys.stderr, 'Sorry, the --verbose option is not implemented for projects.'
346 raise oscerr.WrongOptions('Sorry, the --expand option is not implemented for projects.')
348 print '\n'.join(meta_get_packagelist(conf.config['apiurl'], project, opts.deleted))
350 elif len(args) == 2 or len(args) == 3:
352 print_not_found = True
355 l = meta_get_filelist(conf.config['apiurl'],
358 verbose=opts.verbose,
361 link_seen = '_link' in l
363 out = [ '%s %7s %9d %s %s' % (i.md5, i.rev, i.size, shorttime(i.mtime), i.name) \
364 for i in l if not fname or fname == i.name ]
366 print_not_found = False
371 print_not_found = False
374 if opts.expand or opts.unexpand or not link_seen: break
375 m = show_files_meta(conf.config['apiurl'], project, package)
377 li.read(ET.fromstring(''.join(m)).find('linkinfo'))
379 raise oscerr.LinkExpandError(project, package, li.error)
380 project, package, rev = li.project, li.package, li.rev
382 print '# -> %s %s (%s)' % (project, package, rev)
384 print '# -> %s %s (latest)' % (project, package)
386 if fname and print_not_found:
387 print 'file \'%s\' does not exist' % fname
390 @cmdln.option('-f', '--force', action='store_true',
391 help='force generation of new patchinfo file')
392 @cmdln.option('--force-update', action='store_true',
393 help='drops away collected packages from an already built patch and let it collect again')
394 def do_patchinfo(self, subcmd, opts, *args):
395 """${cmd_name}: Generate and edit a patchinfo file.
397 A patchinfo file describes the packages for an update and the kind of
402 osc patchinfo PATCH_NAME
406 project_dir = localdir = os.getcwd()
407 if is_project_dir(localdir):
408 project = store_read_project(localdir)
409 apiurl = self.get_api_url()
411 sys.exit('This command must be called in a checked out project.')
413 for p in meta_get_packagelist(apiurl, project):
414 if p.startswith("_patchinfo:"):
417 if opts.force or not patchinfo:
418 print "Creating initial patchinfo..."
419 query='cmd=createpatchinfo'
421 query += "&name=" + args[0]
422 url = makeurl(apiurl, ['source', project], query=query)
424 for p in meta_get_packagelist(apiurl, project):
425 if p.startswith("_patchinfo:"):
428 if not os.path.exists(project_dir + "/" + patchinfo):
429 checkout_package(apiurl, project, patchinfo, prj_dir=project_dir)
431 filename = project_dir + "/" + patchinfo + "/_patchinfo"
435 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
436 help='affect only a given attribute')
437 @cmdln.option('--attribute-defaults', action='store_true',
438 help='include defined attribute defaults')
439 @cmdln.option('--attribute-project', action='store_true',
440 help='include project values, if missing in packages ')
441 @cmdln.option('-F', '--file', metavar='FILE',
442 help='read metadata from FILE, instead of opening an editor. '
443 '\'-\' denotes standard input. ')
444 @cmdln.option('-e', '--edit', action='store_true',
445 help='edit metadata')
446 @cmdln.option('-c', '--create', action='store_true',
447 help='create attribute without values')
448 @cmdln.option('-s', '--set', metavar='ATTRIBUTE_VALUES',
449 help='set attribute values')
450 @cmdln.option('--delete', action='store_true',
451 help='delete a pattern or attribute')
452 def do_meta(self, subcmd, opts, *args):
453 """${cmd_name}: Show meta information, or edit it
455 Show or edit build service metadata of type <prj|pkg|prjconf|user|pattern>.
457 This command displays metadata on buildservice objects like projects,
458 packages, or users. The type of metadata is specified by the word after
459 "meta", like e.g. "meta prj".
461 prj denotes metadata of a buildservice project.
462 prjconf denotes the (build) configuration of a project.
463 pkg denotes metadata of a buildservice package.
464 user denotes the metadata of a user.
465 pattern denotes installation patterns defined for a project.
467 To list patterns, use 'osc meta pattern PRJ'. An additional argument
468 will be the pattern file to view or edit.
470 With the --edit switch, the metadata can be edited. Per default, osc
471 opens the program specified by the environmental variable EDITOR with a
472 temporary file. Alternatively, content to be saved can be supplied via
473 the --file switch. If the argument is '-', input is taken from stdin:
474 osc meta prjconf home:user | sed ... | osc meta prjconf home:user -F -
476 When trying to edit a non-existing resource, it is created implicitly.
482 osc meta pkg PRJ PKG -e
483 osc meta attribute PRJ [PKG [SUBPACKAGE]] [--attribute ATTRIBUTE] [--create|--delete|--set [value_list]]
486 osc meta <prj|pkg|prjconf|user|pattern|attribute> ARGS...
487 osc meta <prj|pkg|prjconf|user|pattern|attribute> -e|--edit ARGS...
488 osc meta <prj|pkg|prjconf|user|pattern|attribute> -F|--file ARGS...
489 osc meta pattern --delete PRJ PATTERN
493 args = slash_split(args)
495 if not args or args[0] not in metatypes.keys():
496 raise oscerr.WrongArgs('Unknown meta type. Choose one of %s.' \
497 % ', '.join(metatypes))
503 min_args, max_args = 0, 2
504 elif cmd in ['pattern']:
505 min_args, max_args = 1, 2
506 elif cmd in ['attribute']:
507 min_args, max_args = 1, 3
508 elif cmd in ['prj', 'prjconf']:
509 min_args, max_args = 0, 1
511 min_args, max_args = 1, 1
513 if len(args) < min_args:
514 raise oscerr.WrongArgs('Too few arguments.')
515 if len(args) > max_args:
516 raise oscerr.WrongArgs('Too many arguments.')
520 if cmd in ['pkg', 'prj', 'prjconf' ]:
522 project = store_read_project(os.curdir)
528 package = store_read_package(os.curdir)
532 elif cmd == 'attribute':
538 if opts.attribute_project:
539 raise oscerr.WrongOptions('--attribute-project works only when also a package is given')
544 attributepath.append('source')
545 attributepath.append(project)
547 attributepath.append(package)
549 attributepath.append(subpackage)
550 attributepath.append('_attribute')
553 elif cmd == 'pattern':
559 # enforce pattern argument if needed
560 if opts.edit or opts.file:
561 raise oscerr.WrongArgs('A pattern file argument is required.')
564 if not opts.edit and not opts.file and not opts.delete and not opts.create and not opts.set:
566 sys.stdout.write(''.join(show_project_meta(conf.config['apiurl'], project)))
568 sys.stdout.write(''.join(show_package_meta(conf.config['apiurl'], project, package)))
569 elif cmd == 'attribute':
570 sys.stdout.write(''.join(show_attribute_meta(conf.config['apiurl'], project, package, subpackage, opts.attribute, opts.attribute_defaults, opts.attribute_project)))
571 elif cmd == 'prjconf':
572 sys.stdout.write(''.join(show_project_conf(conf.config['apiurl'], project)))
574 r = get_user_meta(conf.config['apiurl'], user)
576 sys.stdout.write(''.join(r))
577 elif cmd == 'pattern':
579 r = show_pattern_meta(conf.config['apiurl'], project, pattern)
581 sys.stdout.write(''.join(r))
583 r = show_pattern_metalist(conf.config['apiurl'], project)
585 sys.stdout.write('\n'.join(r) + '\n')
588 if opts.edit and not opts.file:
590 edit_meta(metatype='prj',
592 path_args=quote_plus(project),
595 'user': conf.config['user']}))
597 edit_meta(metatype='pkg',
599 path_args=(quote_plus(project), quote_plus(package)),
602 'user': conf.config['user']}))
603 elif cmd == 'prjconf':
604 edit_meta(metatype='prjconf',
606 path_args=quote_plus(project),
609 edit_meta(metatype='user',
611 path_args=(quote_plus(user)),
612 template_args=({'user': user}))
613 elif cmd == 'pattern':
614 edit_meta(metatype='pattern',
616 path_args=(project, pattern),
619 # create attribute entry
620 if (opts.create or opts.set) and cmd == 'attribute':
621 if not opts.attribute:
622 raise oscerr.WrongOptions('no attribute given to create')
625 opts.set = opts.set.replace('&', '&').replace('<', '<').replace('>', '>')
626 for i in opts.set.split(','):
627 values += '<value>%s</value>' % i
628 aname = opts.attribute.split(":")
629 d = '<attributes><attribute namespace=\'%s\' name=\'%s\' >%s</attribute></attributes>' % (aname[0], aname[1], values)
630 url = makeurl(conf.config['apiurl'], attributepath)
631 for data in streamfile(url, http_POST, data=d):
632 sys.stdout.write(data)
641 f = open(opts.file).read()
643 sys.exit('could not open file \'%s\'.' % opts.file)
646 edit_meta(metatype='prj',
649 path_args=quote_plus(project))
651 edit_meta(metatype='pkg',
654 path_args=(quote_plus(project), quote_plus(package)))
655 elif cmd == 'prjconf':
656 edit_meta(metatype='prjconf',
659 path_args=quote_plus(project))
661 edit_meta(metatype='user',
664 path_args=(quote_plus(user)))
665 elif cmd == 'pattern':
666 edit_meta(metatype='pattern',
669 path_args=(project, pattern))
674 path = metatypes[cmd]['path']
676 path = path % (project, pattern)
677 u = makeurl(conf.config['apiurl'], [path])
679 elif cmd == 'attribute':
680 if not opts.attribute:
681 raise oscerr.WrongOptions('no attribute given to create')
682 attributepath.append(opts.attribute)
683 u = makeurl(conf.config['apiurl'], attributepath)
684 for data in streamfile(u, http_DELETE):
685 sys.stdout.write(data)
687 raise oscerr.WrongOptions('The --delete switch is only for pattern metadata or attributes.')
690 @cmdln.option('-m', '--message', metavar='TEXT',
691 help='specify message TEXT')
692 @cmdln.option('-r', '--revision', metavar='REV',
693 help='for "create", specify a certain source revision ID (the md5 sum)')
694 @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
695 help='Superseding another request by this one')
696 @cmdln.option('--nodevelproject', action='store_true',
697 help='do not follow a defined devel project ' \
698 '(primary project where a package is developed)')
699 @cmdln.option('--cleanup', action='store_true',
700 help='remove package if submission gets accepted (default for home:<id>:branch projects)')
701 @cmdln.option('--no-cleanup', action='store_true',
702 help='never remove source package on accept, but update its content')
703 @cmdln.option('--no-update', action='store_true',
704 help='never touch source package on accept (will break source links)')
705 @cmdln.option('-d', '--diff', action='store_true',
706 help='show diff only instead of creating the actual request')
707 @cmdln.option('--yes', action='store_true',
708 help='proceed without asking.')
710 @cmdln.alias("submitreq")
711 @cmdln.alias("submitpac")
712 def do_submitrequest(self, subcmd, opts, *args):
713 """${cmd_name}: Create request to submit source into another Project
715 [See http://en.opensuse.org/Build_Service/Collaboration for information
718 See the "request" command for showing and modifing existing requests.
721 osc submitreq [OPTIONS]
722 osc submitreq [OPTIONS] DESTPRJ [DESTPKG]
723 osc submitreq [OPTIONS] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
727 src_update = conf.config['submitrequest_on_accept_action'] or None
728 # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
730 src_update = "cleanup"
731 elif opts.no_cleanup:
732 src_update = "update"
734 src_update = "noupdate"
736 args = slash_split(args)
738 # remove this block later again
739 oldcmds = ['create', 'list', 'log', 'show', 'decline', 'accept', 'delete', 'revoke']
740 if args and args[0] in oldcmds:
741 print "************************************************************************"
742 print "* WARNING: It looks that you are using this command with a *"
743 print "* deprecated syntax. *"
744 print "* Please run \"osc sr --help\" and \"osc rq --help\" *"
745 print "* to see the new syntax. *"
746 print "************************************************************************"
747 if args[0] == 'create':
753 raise oscerr.WrongArgs('Too many arguments.')
755 if len(args) > 0 and len(args) <= 2 and is_project_dir(os.getcwd()):
756 sys.exit('osc submitrequest from project directory is only working without target specs and for source linked files\n')
758 apiurl = self.get_api_url()
760 if len(args) == 0 and is_project_dir(os.getcwd()):
762 # submit requests for multiple packages are currently handled via multiple requests
763 # They could be also one request with multiple actions, but that avoids to accepts parts of it.
764 project = store_read_project(os.curdir)
770 # loop via all packages for checking their state
771 for p in meta_get_packagelist(apiurl, project):
772 if p.startswith("_patchinfo:"):
775 # get _link info from server, that knows about the local state ...
776 u = makeurl(apiurl, ['source', project, p])
778 root = ET.parse(f).getroot()
779 linkinfo = root.find('linkinfo')
781 print "Package ", p, " is not a source link."
782 sys.exit("This is currently not supported.")
783 if linkinfo.get('error'):
784 print "Package ", p, " is a broken source link."
785 sys.exit("Please fix this first")
786 t = linkinfo.get('project')
788 if len(root.findall('entry')) > 1: # This is not really correct, but should work mostly
789 # Real fix is to ask the api if sources are modificated
790 # but there is no such call yet.
791 targetprojects.append(t)
793 print "Submitting package ", p
795 print " Skipping package ", p
797 print "Skipping package ", p, " since it is a source link pointing inside the project."
801 print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
802 print "\nEverything fine? Can we create the requests ? [y/n]"
803 if sys.stdin.read(1) != "y":
804 print >>sys.stderr, 'Aborted...'
805 raise oscerr.UserAbort()
807 # loop via all packages to do the action
809 result = create_submit_request(apiurl, project, p)
812 sys.exit("submit request creation failed")
813 sr_ids.append(result)
815 # create submit requests for all found patchinfos
819 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
822 for t in targetprojects:
823 s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \
824 (project, p, t, p, options_block)
828 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
829 (actionxml, cgi.escape(opts.message or ""))
830 u = makeurl(apiurl, ['request'], query='cmd=create')
831 f = http_POST(u, data=xml)
833 root = ET.parse(f).getroot()
834 sr_ids.append(root.get('id'))
836 print "Requests created: ",
839 sys.exit('Successfull finished')
842 # try using the working copy at hand
843 p = findpacs(os.curdir)[0]
844 src_project = p.prjname
847 if len(args) == 0 and p.islink():
848 dst_project = p.linkinfo.project
849 dst_package = p.linkinfo.package
851 dst_project = args[0]
853 dst_package = args[1]
855 dst_package = src_package
857 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
858 'Please provide it the target via commandline arguments.' % p.name)
860 modified = [i for i in p.filenamelist if p.status(i) != ' ' and p.status(i) != '?']
861 if len(modified) > 0:
862 print 'Your working copy has local modifications.'
863 repl = raw_input('Proceed without committing the local changes? (y|N) ')
865 raise oscerr.UserAbort()
867 # get the arguments from the commandline
868 src_project, src_package, dst_project = args[0:3]
870 dst_package = args[3]
872 dst_package = src_package
874 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
875 + self.get_cmd_help('request'))
877 if not opts.nodevelproject:
880 devloc = show_develproject(apiurl, dst_project, dst_package)
881 except urllib2.HTTPError:
882 print >>sys.stderr, """\
883 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
884 % (dst_project, dst_package)
888 dst_project != devloc and \
889 src_project != devloc:
891 A different project, %s, is defined as the place where development
892 of the package %s primarily takes place.
893 Please submit there instead, or use --nodevelproject to force direct submission.""" \
894 % (devloc, dst_package)
899 if opts.diff or not opts.message:
901 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
902 rdiff += server_diff(apiurl,
903 dst_project, dst_package, opts.revision,
904 src_project, src_package, None, True)
910 reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit')
911 user = conf.get_apiurl_usr(apiurl)
912 myreqs = [ i for i in reqs if i.state.who == user ]
915 print 'You already created the following submit request: %s.' % \
916 ', '.join([str(i.reqid) for i in myreqs ])
917 repl = raw_input('Supersede the old requests? (y/n/c) ')
918 if repl.lower() == 'c':
919 print >>sys.stderr, 'Aborting'
920 raise oscerr.UserAbort()
925 changes_re = re.compile(r'^--- .*\.changes ')
926 for line in rdiff.split('\n'):
927 if line.startswith('--- '):
928 if changes_re.match(line):
933 difflines.append(line)
934 opts.message = edit_message(footer=rdiff, template='\n'.join(parse_diff_for_commit_message('\n'.join(difflines))))
936 result = create_submit_request(apiurl,
937 src_project, src_package,
938 dst_project, dst_package,
939 opts.message, orev=opts.revision, src_update=src_update)
940 if repl.lower() == 'y':
942 change_request_state(apiurl, str(req.reqid), 'superseded',
943 'superseded by %s' % result, result)
946 r = change_request_state(conf.config['apiurl'],
947 opts.supersede, 'superseded', opts.message or '', result)
949 print 'created request id', result
952 @cmdln.option('-m', '--message', metavar='TEXT',
953 help='specify message TEXT')
955 @cmdln.alias("deletereq")
956 def do_deleterequest(self, subcmd, opts, *args):
957 """${cmd_name}: Create request to delete a package or project
961 osc deletereq [-m TEXT] PROJECT [PACKAGE]
965 args = slash_split(args)
968 raise oscerr.WrongArgs('Please specify at least a project.')
970 raise oscerr.WrongArgs('Too many arguments.')
972 apiurl = conf.config['apiurl']
980 opts.message = edit_message()
982 result = create_delete_request(apiurl, project, package, opts.message)
986 @cmdln.option('-m', '--message', metavar='TEXT',
987 help='specify message TEXT')
989 @cmdln.alias("changedevelreq")
990 def do_changedevelrequest(self, subcmd, opts, *args):
991 """${cmd_name}: Create request to change the devel package definition.
993 [See http://en.opensuse.org/Build_Service/Collaboration for information
996 See the "request" command for showing and modifing existing requests.
998 osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
1002 raise oscerr.WrongArgs('Too many arguments.')
1004 apiurl = self.get_api_url()
1006 if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
1008 devel_project = store_read_project(wd)
1009 devel_package = package = store_read_package(wd)
1010 project = conf.config['getpac_default_project']
1013 raise oscerr.WrongArgs('Too few arguments.')
1015 devel_project = args[2]
1018 devel_package = package
1020 devel_package = args[3]
1022 if not opts.message:
1024 footer=textwrap.TextWrapper(width = 66).fill(
1025 'please explain why you like to change the devel project of %s/%s to %s/%s'
1026 % (project,package,devel_project,devel_package))
1027 opts.message = edit_message(footer)
1029 result = create_change_devel_request(apiurl,
1030 devel_project, devel_package,
1036 @cmdln.option('-d', '--diff', action='store_true',
1037 help='generate a diff')
1038 @cmdln.option('-u', '--unified', action='store_true',
1039 help='output the diff in the unified diff format')
1040 @cmdln.option('-m', '--message', metavar='TEXT',
1041 help='specify message TEXT')
1042 @cmdln.option('-t', '--type', metavar='TYPE',
1043 help='limit to requests which contain a given action type (submit/delete/change_devel)')
1044 @cmdln.option('-a', '--all', action='store_true',
1045 help='all states. Same as\'-s all\'')
1046 @cmdln.option('-s', '--state', default='', # default is 'all' if no args given, 'new' otherwise
1047 help='only list requests in one of the comma separated given states (new/accepted/revoked/declined) or "all" [default=new, or all, if no args given]')
1048 @cmdln.option('-D', '--days', metavar='DAYS',
1049 help='only list requests in state "new" or changed in the last DAYS. [default=%(request_list_days)s]')
1050 @cmdln.option('-U', '--user', metavar='USER',
1051 help='same as -M, but for the specified USER')
1052 @cmdln.option('-b', '--brief', action='store_true', default=False,
1053 help='print output in list view as list subcommand')
1054 @cmdln.option('-M', '--mine', action='store_true',
1055 help='only show requests created by yourself')
1056 @cmdln.option('-B', '--bugowner', action='store_true',
1057 help='also show requests about packages where I am bugowner')
1058 @cmdln.option('-i', '--interactive', action='store_true',
1059 help='interactive review of request')
1060 @cmdln.option('--non-interactive', action='store_true',
1061 help='non-interactive review of request')
1062 @cmdln.option('--exclude-target-project', action='append',
1063 help='exclude target project from request list')
1064 @cmdln.option('--involved-projects', action='store_true',
1065 help='show all requests for project/packages where USER is involved')
1067 @cmdln.alias("review")
1068 def do_request(self, subcmd, opts, *args):
1069 """${cmd_name}: Show and modify requests
1071 [See http://en.opensuse.org/Build_Service/Collaboration for information
1074 This command shows and modifies existing requests. To create new requests
1075 you need to call one of the following:
1078 osc changedevelrequest
1079 To send low level requests to the buildservice API, use:
1082 This command has the following sub commands:
1084 "list" lists open requests attached to a project or package or person.
1085 Uses the project/package of the current directory if none of
1086 -M, -U USER, project/package are given.
1088 "log" will show the history of the given ID
1090 "show" will show the request itself, and generate a diff for review, if
1091 used with the --diff option. The keyword show can be omitted if the ID is numeric.
1093 "decline" will change the request state to "declined" and append a
1094 message that you specify with the --message option.
1096 "wipe" will permanently delete a request.
1098 "revoke" will set the request state to "revoked" and append a
1099 message that you specify with the --message option.
1101 "accept" will change the request state to "accepted" and will trigger
1102 the actual submit process. That would normally be a server-side copy of
1103 the source package to the target package.
1105 "checkout" will checkout the request's source package. This only works for "submit" requests.
1108 osc request list [-M] [-U USER] [-s state] [-D DAYS] [-t type] [-B] [PRJ [PKG]]
1110 osc request [show] [-d] [-b] ID
1111 osc request accept [-m TEXT] ID
1112 osc request approvenew [-m TEXT] PROJECT
1113 osc request decline [-m TEXT] ID
1114 osc request revoke [-m TEXT] ID
1116 osc request checkout/co ID
1117 osc review accept [-m TEXT] ID
1118 osc review decline [-m TEXT] ID
1119 osc review new [-m TEXT] ID # for setting a temporary comment without changing the state
1123 args = slash_split(args)
1125 if opts.all and opts.state:
1126 raise oscerr.WrongOptions('Sorry, the options \'--all\' and \'--state\' ' \
1127 'are mutually exclusive.')
1128 if opts.mine and opts.user:
1129 raise oscerr.WrongOptions('Sorry, the options \'--user\' and \'--mine\' ' \
1130 'are mutually exclusive.')
1131 if opts.interactive and opts.non_interactive:
1132 raise oscerr.WrongOptions('Sorry, the options \'--interactive\' and ' \
1133 '\'--non-interactive\' are mutually exclusive')
1138 if opts.state == '':
1141 if opts.state == '':
1144 cmds = ['list', 'log', 'show', 'decline', 'accept', 'approvenew', 'wipe', 'revoke', 'checkout', 'co', 'help']
1145 if not args or args[0] not in cmds:
1146 raise oscerr.WrongArgs('Unknown request action %s. Choose one of %s.' \
1147 % (args[0],', '.join(cmds)))
1153 return self.do_help(['help', 'request'])
1156 min_args, max_args = 0, 2
1158 min_args, max_args = 1, 1
1159 if len(args) < min_args:
1160 raise oscerr.WrongArgs('Too few arguments.')
1161 if len(args) > max_args:
1162 raise oscerr.WrongArgs('Too many arguments.')
1164 apiurl = self.get_api_url()
1166 if cmd == 'list' or cmd == 'approvenew':
1171 elif not opts.mine and not opts.user:
1173 project = store_read_project(os.curdir)
1174 package = store_read_package(os.curdir)
1175 except oscerr.NoWorkingCopy:
1180 elif cmd in ['log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co']:
1183 # list and approvenew
1184 if cmd == 'list' or cmd == 'approvenew':
1185 states = ('new', 'accepted', 'revoked', 'declined')
1187 if cmd == 'approvenew':
1189 results = get_request_list(apiurl, project, package, '', ['new'])
1191 state_list = opts.state.split(',')
1192 if opts.state == 'all':
1193 state_list = ['all']
1195 for s in state_list:
1197 raise oscerr.WrongArgs('Unknown state \'%s\', try one of %s' % (s, ','.join(states)))
1199 who = conf.get_apiurl_usr(apiurl)
1203 state_list = ['all']
1205 ## FIXME -B not implemented!
1207 if (self.options.debug):
1208 print 'list: option --bugowner ignored: not impl.'
1210 if opts.involved_projects:
1211 who = who or conf.get_apiurl_usr(apiurl)
1212 results = get_user_projpkgs_request_list(apiurl, who, req_state=state_list,
1213 req_type=opts.type, exclude_projects=opts.exclude_target_project or [])
1215 results = get_request_list(apiurl, project, package, who,
1216 state_list, opts.type, opts.exclude_target_project or [])
1218 results.sort(reverse=True)
1220 days = opts.days or conf.config['request_list_days']
1227 since = time.strftime('%Y-%m-%dT%H:%M:%S',time.localtime(time.time()-days*24*3600))
1230 ## bs has received 2009-09-20 a new xquery compare() function
1231 ## which allows us to limit the list inside of get_request_list
1232 ## That would be much faster for coolo. But counting the remainder
1233 ## would not be possible with current xquery implementation.
1234 ## Workaround: fetch all, and filter on client side.
1236 ## FIXME: date filtering should become implemented on server side
1237 for result in results:
1238 if days == 0 or result.state.when > since or result.state.name == 'new':
1239 print result.list_view()
1243 print "There are %d requests older than %s days.\n" % (skipped, days)
1245 if cmd == 'approvenew':
1246 print "\n *** Approve them all ? [y/n] ***"
1247 if sys.stdin.read(1) == "y":
1249 if not opts.message:
1250 opts.message = edit_message()
1251 for result in results:
1252 print result.reqid, ": ",
1253 r = change_request_state(conf.config['apiurl'],
1254 str(result.reqid), 'accepted', opts.message or '')
1257 print >>sys.stderr, 'Aborted...'
1258 raise oscerr.UserAbort()
1261 for l in get_request_log(conf.config['apiurl'], reqid):
1266 r = get_request(conf.config['apiurl'], reqid)
1269 elif (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive:
1270 return request_interactive_review(conf.config['apiurl'], r)
1273 # fixme: will inevitably fail if the given target doesn't exist
1274 if opts.diff and r.actions[0].type != 'submit':
1275 raise oscerr.WrongOptions('\'--diff\' is not possible for request type: \'%s\'' % r.actions[0].type)
1278 print server_diff(conf.config['apiurl'],
1279 r.actions[0].dst_project, r.actions[0].dst_package, None,
1280 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, True)
1281 except urllib2.HTTPError, e:
1283 e.osc_msg = 'Diff not possible'
1285 # backward compatiblity: only a recent api/backend supports the missingok parameter
1287 print server_diff(conf.config['apiurl'],
1288 r.actions[0].dst_project, r.actions[0].dst_package, None,
1289 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, False)
1290 except urllib2.HTTPError, e:
1291 e.osc_msg = 'Diff not possible'
1295 elif cmd == 'checkout' or cmd == 'co':
1296 r = get_request(conf.config['apiurl'], reqid)
1297 submits = [ i for i in r.actions if i.type == 'submit' ]
1298 if not len(submits):
1299 raise oscerr.WrongArgs('\'checkout\' only works for \'submit\' requests')
1300 checkout_package(conf.config['apiurl'], submits[0].src_project, submits[0].src_package, \
1301 submits[0].src_rev, expand_link=True, prj_dir=submits[0].src_project)
1304 if not opts.message:
1305 opts.message = edit_message()
1306 state_map = {'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked'}
1307 # Change review state only
1308 if subcmd == 'review':
1309 if cmd in ['accept', 'decline', 'new']:
1310 r = change_review_state(conf.config['apiurl'],
1311 reqid, state_map[cmd], conf.config['user'], '', opts.message or '')
1313 # Change state of entire request
1314 elif cmd in ['accept', 'decline', 'wipe', 'revoke']:
1315 r = change_request_state(conf.config['apiurl'],
1316 reqid, state_map[cmd], opts.message or '')
1319 # editmeta and its aliases are all depracated
1320 @cmdln.alias("editprj")
1321 @cmdln.alias("createprj")
1322 @cmdln.alias("editpac")
1323 @cmdln.alias("createpac")
1324 @cmdln.alias("edituser")
1325 @cmdln.alias("usermeta")
1327 def do_editmeta(self, subcmd, opts, *args):
1330 Obsolete command to edit metadata. Use 'meta' now.
1332 See the help output of 'meta'.
1336 print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
1337 print >>sys.stderr, 'See \'osc help meta\'.'
1338 #self.do_help([None, 'meta'])
1342 @cmdln.option('-r', '--revision', metavar='rev',
1343 help='use the specified revision.')
1344 @cmdln.option('-u', '--unset', action='store_true',
1345 help='remove revision in link, it will point always to latest revision')
1346 def do_setlinkrev(self, subcmd, opts, *args):
1347 """${cmd_name}: Updates a revision number in a source link.
1349 This command adds or updates a specified revision number in a source link.
1350 The current revision of the source is used, if no revision number is specified.
1354 osc setlinkrev PROJECT [PACKAGE]
1358 args = slash_split(args)
1359 apiurl = conf.config['apiurl']
1362 p = findpacs(os.curdir)[0]
1367 sys.exit('Local directory is no checked out source link package, aborting')
1368 elif len(args) == 2:
1371 elif len(args) == 1:
1374 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1375 + self.get_cmd_help('setlinkrev'))
1378 packages = [ package ]
1380 packages = meta_get_packagelist(apiurl, project)
1383 print "setting revision for package", p
1387 rev, dummy = parseRevisionOption(opts.revision)
1388 set_link_rev(apiurl, project, p, rev)
1391 def do_linktobranch(self, subcmd, opts, *args):
1392 """${cmd_name}: Convert a package containing a classic link with patch to a branch
1394 This command tells the server to convert a _link with or without a project.diff
1395 to a branch. This is a full copy with a _link file pointing to the branched place.
1398 osc linktobranch # can be used in checked out package
1399 osc linktobranch PROJECT PACKAGE
1402 args = slash_split(args)
1403 apiurl = self.get_api_url()
1407 project = store_read_project(wd)
1408 package = store_read_package(wd)
1409 update_local_dir = True
1411 raise oscerr.WrongArgs('Too few arguments (required none or two)')
1413 raise oscerr.WrongArgs('Too many arguments (required none or two)')
1417 update_local_dir = False
1420 link_to_branch(apiurl, project, package)
1421 if update_local_dir:
1423 pac.update(rev=pac.latest_rev())
1426 @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'],
1427 help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.')
1428 @cmdln.option('-c', '--current', action='store_true',
1429 help='link fixed against current revision.')
1430 @cmdln.option('-r', '--revision', metavar='rev',
1431 help='link the specified revision.')
1432 @cmdln.option('-f', '--force', action='store_true',
1433 help='overwrite an existing link file if it is there.')
1434 @cmdln.option('-d', '--disable-publish', action='store_true',
1435 help='disable publishing of the linked package')
1436 def do_linkpac(self, subcmd, opts, *args):
1437 """${cmd_name}: "Link" a package to another package
1439 A linked package is a clone of another package, but plus local
1440 modifications. It can be cross-project.
1442 The DESTPAC name is optional; the source packages' name will be used if
1445 Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
1447 To add a patch, add the patch as file and add it to the _link file.
1448 You can also specify text which will be inserted at the top of the spec file.
1450 See the examples in the _link file.
1453 osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1457 args = slash_split(args)
1459 if not args or len(args) < 3:
1460 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1461 + self.get_cmd_help('linkpac'))
1463 rev, dummy = parseRevisionOption(opts.revision)
1465 src_project = args[0]
1466 src_package = args[1]
1467 dst_project = args[2]
1469 dst_package = args[3]
1471 dst_package = src_package
1473 if src_project == dst_project and src_package == dst_package:
1474 raise oscerr.WrongArgs('Error: source and destination are the same.')
1476 if src_project == dst_project and not opts.cicount:
1477 # in this case, the user usually wants to build different spec
1478 # files from the same source
1479 opts.cicount = "copy"
1482 rev = show_upstream_rev(conf.config['apiurl'], src_project, src_package)
1484 if rev and not checkRevision(src_project, src_package, rev):
1485 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
1488 link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish)
1490 @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]',
1491 help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]')
1492 @cmdln.option('-d', '--disable-publish', action='store_true',
1493 help='disable publishing of the aggregated package')
1494 def do_aggregatepac(self, subcmd, opts, *args):
1495 """${cmd_name}: "Aggregate" a package to another package
1497 Aggregation of a package means that the build results (binaries) of a
1498 package are basically copied into another project.
1499 This can be used to make packages available from building that are
1500 needed in a project but available only in a different project. Note
1501 that this is done at the expense of disk space. See
1502 http://en.opensuse.org/Build_Service/Tips_and_Tricks#_link_and__aggregate
1503 for more information.
1505 The DESTPAC name is optional; the source packages' name will be used if
1509 osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1513 args = slash_split(args)
1515 if not args or len(args) < 3:
1516 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1517 + self.get_cmd_help('aggregatepac'))
1519 src_project = args[0]
1520 src_package = args[1]
1521 dst_project = args[2]
1523 dst_package = args[3]
1525 dst_package = src_package
1527 if src_project == dst_project and src_package == dst_package:
1528 raise oscerr.WrongArgs('Error: source and destination are the same.')
1532 for pair in opts.map_repo.split(','):
1533 src_tgt = pair.split('=')
1534 if len(src_tgt) != 2:
1535 raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo)
1536 repo_map[src_tgt[0]] = src_tgt[1]
1538 aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish)
1541 @cmdln.option('-c', '--client-side-copy', action='store_true',
1542 help='do a (slower) client-side copy')
1543 @cmdln.option('-k', '--keep-maintainers', action='store_true',
1544 help='keep original maintainers. Default is remove all and replace with the one calling the script.')
1545 @cmdln.option('-d', '--keep-develproject', action='store_true',
1546 help='keep develproject tag in the package metadata')
1547 @cmdln.option('-r', '--revision', metavar='rev',
1548 help='link the specified revision.')
1549 @cmdln.option('-t', '--to-apiurl', metavar='URL',
1550 help='URL of destination api server. Default is the source api server.')
1551 @cmdln.option('-m', '--message', metavar='TEXT',
1552 help='specify message TEXT')
1553 @cmdln.option('-e', '--expand', action='store_true',
1554 help='if the source package is a link then copy the expanded version of the link')
1555 def do_copypac(self, subcmd, opts, *args):
1556 """${cmd_name}: Copy a package
1558 A way to copy package to somewhere else.
1560 It can be done across buildservice instances, if the -t option is used.
1561 In that case, a client-side copy is implied.
1563 Using --client-side-copy always involves downloading all files, and
1564 uploading them to the target.
1566 The DESTPAC name is optional; the source packages' name will be used if
1570 osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1574 args = slash_split(args)
1576 if not args or len(args) < 3:
1577 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1578 + self.get_cmd_help('copypac'))
1580 src_project = args[0]
1581 src_package = args[1]
1582 dst_project = args[2]
1584 dst_package = args[3]
1586 dst_package = src_package
1588 src_apiurl = conf.config['apiurl']
1590 dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl)
1592 dst_apiurl = src_apiurl
1594 if src_apiurl != dst_apiurl:
1595 opts.client_side_copy = True
1597 rev, dummy = parseRevisionOption(opts.revision)
1600 comment = opts.message
1603 rev = show_upstream_rev(src_apiurl, src_project, src_package)
1604 comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev )
1606 if src_project == dst_project and \
1607 src_package == dst_package and \
1609 src_apiurl == dst_apiurl:
1610 raise oscerr.WrongArgs('Source and destination are the same.')
1612 r = copy_pac(src_apiurl, src_project, src_package,
1613 dst_apiurl, dst_project, dst_package,
1614 client_side_copy=opts.client_side_copy,
1615 keep_maintainers=opts.keep_maintainers,
1616 keep_develproject=opts.keep_develproject,
1623 @cmdln.option('-c', '--checkout', action='store_true',
1624 help='Checkout branched package afterwards ' \
1625 '(\'osc bco\' is a shorthand for this option)' )
1626 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
1627 help='Use this attribute to find affected packages (default is OBS:Maintained)')
1628 @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE',
1629 help='Use this attribute to find update projects (default is OBS:UpdateProject) ')
1630 def do_mbranch(self, subcmd, opts, *args):
1631 """${cmd_name}: Multiple branch of a package
1633 [See http://en.opensuse.org/Build_Service/Concepts/Maintenance for information
1636 This command is used for creating multiple links of defined version of a package
1637 in one project. This is esp. used for maintenance updates.
1639 The branched package will live in
1640 home:USERNAME:branches:ATTRIBUTE:PACKAGE
1641 if nothing else specified.
1644 osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ]
1647 args = slash_split(args)
1650 maintained_attribute = conf.config['maintained_attribute']
1651 maintained_update_project_attribute = conf.config['maintained_update_project_attribute']
1653 if not len(args) or len(args) > 2:
1654 raise oscerr.WrongArgs('Wrong number of arguments.')
1660 r = attribute_branch_pkg(conf.config['apiurl'], maintained_attribute, maintained_update_project_attribute, \
1664 print >>sys.stderr, 'ERROR: Attribute branch call came not back with a project.'
1667 print "Project " + r + " created."
1670 init_project_dir(conf.config['apiurl'], r, r)
1671 print statfrmt('A', r)
1674 for package in meta_get_packagelist(conf.config['apiurl'], r):
1676 checkout_package(conf.config['apiurl'], r, package, expand_link = True, prj_dir = r)
1678 print >>sys.stderr, 'Error while checkout package:\n', package
1680 if conf.config['verbose']:
1681 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1684 @cmdln.alias('branchco')
1686 @cmdln.alias('getpac')
1687 @cmdln.option('--nodevelproject', action='store_true',
1688 help='do not follow a defined devel project ' \
1689 '(primary project where a package is developed)')
1690 @cmdln.option('-c', '--checkout', action='store_true',
1691 help='Checkout branched package afterwards ' \
1692 '(\'osc bco\' is a shorthand for this option)' )
1693 @cmdln.option('-f', '--force', default=False, action="store_true",
1694 help='force branch, overwrite target')
1695 @cmdln.option('-m', '--message', metavar='TEXT',
1696 help='specify message TEXT')
1697 @cmdln.option('-r', '--revision', metavar='rev',
1698 help='branch against a specific revision')
1699 def do_branch(self, subcmd, opts, *args):
1700 """${cmd_name}: Branch a package
1702 [See http://en.opensuse.org/Build_Service/Collaboration for information
1705 Create a source link from a package of an existing project to a new
1706 subproject of the requesters home project (home:branches:)
1708 The branched package will live in
1709 home:USERNAME:branches:PROJECT/PACKAGE
1710 if nothing else specified.
1712 With getpac or bco, the branched package will come from
1713 %(getpac_default_project)s
1714 if nothing else specified.
1718 osc branch SOURCEPROJECT SOURCEPACKAGE
1719 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT
1720 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE
1721 osc getpac SOURCEPACKAGE
1726 if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': opts.checkout = True
1727 args = slash_split(args)
1728 tproject = tpackage = None
1730 if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1:
1731 print >>sys.stderr, 'defaulting to %s/%s' % (conf.config['getpac_default_project'], args[0])
1732 # python has no args.unshift ???
1733 args = [ conf.config['getpac_default_project'] , args[0] ]
1735 if len(args) == 0 and is_package_dir('.'):
1736 args = (store_read_project('.'), store_read_package('.'))
1738 if len(args) < 2 or len(args) > 4:
1739 raise oscerr.WrongArgs('Wrong number of arguments.')
1741 expected = 'home:%s:branches:%s' % (conf.config['user'], args[0])
1743 expected = tproject = args[2]
1747 if not opts.message:
1748 footer='please specify the purpose of your branch'
1749 template='This package was branched from %s in order to ...\n' % args[0]
1750 opts.message = edit_message(footer, template)
1752 exists, targetprj, targetpkg, srcprj, srcpkg = \
1753 branch_pkg(conf.config['apiurl'], args[0], args[1],
1754 nodevelproject=opts.nodevelproject, rev=opts.revision,
1755 target_project=tproject, target_package=tpackage,
1756 return_existing=opts.checkout, msg=opts.message or '',
1759 print >>sys.stderr, 'Using existing branch project: %s' % targetprj
1762 if not exists and (srcprj is not None and srcprj != args[0] or \
1763 srcprj is None and targetprj != expected):
1764 devloc = srcprj or targetprj
1765 if not srcprj and 'branches:' in targetprj:
1766 devloc = targetprj.split('branches:')[1]
1767 print '\nNote: The branch has been created of a different project,\n' \
1769 ' which is the primary location of where development for\n' \
1770 ' that package takes place.\n' \
1771 ' That\'s also where you would normally make changes against.\n' \
1772 ' A direct branch of the specified package can be forced\n' \
1773 ' with the --nodevelproject option.\n' % devloc
1775 package = tpackage or args[1]
1777 checkout_package(conf.config['apiurl'], targetprj, package,
1778 expand_link=True, prj_dir=targetprj)
1779 if conf.config['verbose']:
1780 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1783 if conf.get_configParser().get('general', 'apiurl') != conf.config['apiurl']:
1784 apiopt = '-A %s ' % conf.config['apiurl']
1785 print 'A working copy of the branched package can be checked out with:\n\n' \
1787 % (apiopt, targetprj, package)
1788 print_request_list(conf.config['apiurl'], args[0], args[1])
1790 print_request_list(conf.config['apiurl'], devloc, args[1])
1793 def do_undelete(self, subcmd, opts, *args):
1794 """${cmd_name}: Restores a deleted project or package on the server.
1796 The server restores a package including the sources and meta configuration.
1797 Binaries remain to be lost and will be rebuild.
1800 osc undelete PROJECT
1801 osc undelete PROJECT PACKAGE [PACKAGE ...]
1806 args = slash_split(args)
1808 raise oscerr.WrongArgs('Missing argument.')
1814 undelete_package(conf.config['apiurl'], prj, pkg)
1816 undelete_project(conf.config['apiurl'], prj)
1819 @cmdln.option('-f', '--force', action='store_true',
1820 help='deletes a package or an empty project')
1821 def do_rdelete(self, subcmd, opts, *args):
1822 """${cmd_name}: Delete a project or packages on the server.
1824 As a safety measure, project must be empty (i.e., you need to delete all
1825 packages first). If you are sure that you want to remove this project and all
1826 its packages use \'--force\' switch.
1829 osc rdelete -f PROJECT
1830 osc rdelete PROJECT PACKAGE [PACKAGE ...]
1835 args = slash_split(args)
1837 raise oscerr.WrongArgs('Missing argument.')
1843 # careful: if pkg is an empty string, the package delete request results
1844 # into a project delete request - which works recursively...
1846 delete_package(conf.config['apiurl'], prj, pkg)
1847 elif len(meta_get_packagelist(conf.config['apiurl'], prj)) >= 1 and not opts.force:
1848 print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
1849 'If you are sure that you want to remove this project and all its ' \
1850 'packages use the \'--force\' switch'
1853 delete_project(conf.config['apiurl'], prj)
1856 def do_deletepac(self, subcmd, opts, *args):
1857 print """${cmd_name} is obsolete !
1860 osc delete for checked out packages or projects
1862 osc rdelete for server side operations."""
1867 @cmdln.option('-f', '--force', action='store_true',
1868 help='deletes a project and its packages')
1869 def do_deleteprj(self, subcmd, opts, project):
1870 """${cmd_name} is obsolete !
1877 @cmdln.alias('metafromspec')
1878 @cmdln.option('', '--specfile', metavar='FILE',
1879 help='Path to specfile. (if you pass more than working copy this option is ignored)')
1880 def do_updatepacmetafromspec(self, subcmd, opts, *args):
1881 """${cmd_name}: Update package meta information from a specfile
1883 ARG, if specified, is a package working copy.
1889 args = parseargs(args)
1890 if opts.specfile and len(args) == 1:
1891 specfile = opts.specfile
1894 pacs = findpacs(args)
1896 p.read_meta_from_spec(specfile)
1897 p.update_package_meta()
1901 @cmdln.option('-c', '--change', metavar='rev',
1902 help='the change made by revision rev (like -r rev-1:rev).'
1903 'If rev is negative this is like -r rev:rev-1.')
1904 @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
1905 help='If rev1 is specified it will compare your working copy against '
1906 'the revision (rev1) on the server. '
1907 'If rev1 and rev2 are specified it will compare rev1 against rev2 '
1908 '(NOTE: changes in your working copy are ignored in this case)')
1909 @cmdln.option('-p', '--plain', action='store_true',
1910 help='output the diff in plain (not unified) diff format')
1911 @cmdln.option('--missingok', action='store_true',
1912 help='do not fail if the source or target project/package does not exist on the server')
1913 def do_diff(self, subcmd, opts, *args):
1914 """${cmd_name}: Generates a diff
1916 Generates a diff, comparing local changes against the repository
1919 ARG, specified, is a filename to include in the diff.
1925 args = parseargs(args)
1926 pacs = findpacs(args)
1930 rev = int(opts.change)
1940 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1943 rev1, rev2 = parseRevisionOption(opts.revision)
1947 diff += ''.join(make_diff(pac, rev1))
1949 diff += server_diff(pac.apiurl, pac.prjname, pac.name, rev1,
1950 pac.prjname, pac.name, rev2, not opts.plain, opts.missingok)
1955 @cmdln.option('--oldprj', metavar='OLDPRJ',
1956 help='project to compare against'
1957 ' (deprecated, use 3 argument form)')
1958 @cmdln.option('--oldpkg', metavar='OLDPKG',
1959 help='package to compare against'
1960 ' (deprecated, use 3 argument form)')
1961 @cmdln.option('-r', '--revision', metavar='N[:M]',
1962 help='revision id, where N = old revision and M = new revision')
1963 @cmdln.option('-p', '--plain', action='store_true',
1964 help='output the diff in plain (not unified) diff format')
1965 @cmdln.option('-c', '--change', metavar='rev',
1966 help='the change made by revision rev (like -r rev-1:rev). '
1967 'If rev is negative this is like -r rev:rev-1.')
1968 @cmdln.option('--missingok', action='store_true',
1969 help='do not fail if the source or target project/package does not exist on the server')
1970 def do_rdiff(self, subcmd, opts, *args):
1971 """${cmd_name}: Server-side "pretty" diff of two packages
1973 Compares two packages (three or four arguments) or shows the
1974 changes of a specified revision of a package (two arguments)
1976 If no revision is specified the latest revision is used.
1978 Note that this command doesn't return a normal diff (which could be
1979 applied as patch), but a "pretty" diff, which also compares the content
1984 osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC]
1985 osc ${cmd_name} PROJECT PACKAGE
1989 args = slash_split(args)
2000 new_project = args[0]
2001 new_package = args[1]
2003 old_project = opts.oldprj
2005 old_package = opts.oldpkg
2006 elif len(args) == 3 or len(args) == 4:
2007 if opts.oldprj or opts.oldpkg:
2008 raise oscerr.WrongArgs('--oldpkg and --oldprj are only valid with two arguments')
2009 old_project = args[0]
2010 new_package = old_package = args[1]
2011 new_project = args[2]
2013 new_package = args[3]
2015 raise oscerr.WrongArgs('Wrong number of arguments')
2020 rev = int(opts.change)
2030 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
2034 rev1, rev2 = parseRevisionOption(opts.revision)
2036 rdiff = server_diff(conf.config['apiurl'],
2037 old_project, old_package, rev1,
2038 new_project, new_package, rev2, not opts.plain, opts.missingok)
2043 def do_install(self, subcmd, opts, *args):
2044 """${cmd_name}: install a package after build via zypper in -r
2046 Not implemented yet. Use osc repourls,
2047 select the url you best like (standard),
2048 chop off after the last /, this should work with zypper.
2055 args = slash_split(args)
2056 args = expand_proj_pack(args)
2059 ## if there is only one argument, and it ends in .ymp
2060 ## then fetch it, Parse XML to get the first
2061 ## metapackage.group.repositories.repository.url
2062 ## and construct zypper cmd's for all
2063 ## metapackage.group.software.item.name
2065 ## if args[0] is already an url, the use it as is.
2067 cmd = "sudo zypper -p http://download.opensuse.org/repositories/%s/%s --no-refresh -v in %s" % (re.sub(':',':/',args[0]), 'openSUSE_11.1', args[1])
2068 print self.do_install.__doc__
2069 print "Example: \n" + cmd
2072 def do_repourls(self, subcmd, opts, *args):
2073 """${cmd_name}: Shows URLs of .repo files
2075 Shows URLs on which to access the project .repos files (yum-style
2076 metadata) on download.opensuse.org.
2079 osc repourls [PROJECT]
2084 apiurl = self.get_api_url()
2088 elif len(args) == 0:
2089 project = store_read_project('.')
2091 raise oscerr.WrongArgs('Wrong number of arguments')
2093 # XXX: API should somehow tell that
2094 url_tmpl = 'http://download.opensuse.org/repositories/%s/%s/%s.repo'
2095 repos = get_repositories_of_project(apiurl, project)
2097 print url_tmpl % (project.replace(':', ':/'), repo, project)
2100 @cmdln.option('-r', '--revision', metavar='rev',
2101 help='checkout the specified revision. '
2102 'NOTE: if you checkout the complete project '
2103 'this option is ignored!')
2104 @cmdln.option('-e', '--expand-link', action='store_true',
2105 help='if a package is a link, check out the expanded '
2106 'sources (no-op, since this became the default)')
2107 @cmdln.option('-u', '--unexpand-link', action='store_true',
2108 help='if a package is a link, check out the _link file ' \
2109 'instead of the expanded sources')
2110 @cmdln.option('-m', '--meta', action='store_true',
2111 help='checkout out meta data instead of sources' )
2112 @cmdln.option('-c', '--current-dir', action='store_true',
2113 help='place PACKAGE folder in the current directory' \
2114 'instead of a PROJECT/PACKAGE directory')
2115 @cmdln.option('-s', '--source-service-files', action='store_true',
2116 help='server side generated files of source services' \
2117 'gets downloaded as well' )
2118 @cmdln.option('-l', '--limit-size', metavar='limit_size',
2119 help='Skip all files with a given size')
2121 def do_checkout(self, subcmd, opts, *args):
2122 """${cmd_name}: Check out content from the repository
2124 Check out content from the repository server, creating a local working
2127 When checking out a single package, the option --revision can be used
2128 to specify a revision of the package to be checked out.
2130 When a package is a source link, then it will be checked out in
2131 expanded form. If --unexpand-link option is used, the checkout will
2132 instead produce the raw _link file plus patches.
2135 osc co PROJECT [PACKAGE] [FILE]
2136 osc co PROJECT # entire project
2137 osc co PROJECT PACKAGE # a package
2138 osc co PROJECT PACKAGE FILE # single file -> to current dir
2140 while inside a project directory:
2141 osc co PACKAGE # check out PACKAGE from project
2146 if opts.unexpand_link:
2150 if opts.source_service_files:
2151 service_files = True
2153 service_files = False
2155 args = slash_split(args)
2156 project = package = filename = None
2158 apiurl = self.get_api_url()
2161 project = project_dir = args[0]
2167 if args and len(args) == 1:
2168 localdir = os.getcwd()
2169 if is_project_dir(localdir):
2170 project = store_read_project(localdir)
2171 project_dir = localdir
2174 rev, dummy = parseRevisionOption(opts.revision)
2178 if rev and rev != "latest" and not checkRevision(project, package, rev):
2179 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2183 get_source_file(apiurl, project, package, filename, revision=rev, progress_obj=self.download_progress)
2186 if opts.current_dir:
2188 checkout_package(apiurl, project, package, rev, expand_link=expand_link, \
2189 prj_dir=project_dir, service_files=service_files, progress_obj=self.download_progress, limit_size=opts.limit_size, meta=opts.meta)
2190 print_request_list(apiurl, project, package)
2194 if sys.platform[:3] == 'win':
2195 prj_dir = prj_dir.replace(':', ';')
2196 if os.path.exists(prj_dir):
2197 sys.exit('osc: project \'%s\' already exists' % project)
2199 # check if the project does exist (show_project_meta will throw an exception)
2200 show_project_meta(apiurl, project)
2202 init_project_dir(apiurl, prj_dir, project)
2203 print statfrmt('A', prj_dir)
2206 for package in meta_get_packagelist(apiurl, project):
2208 checkout_package(apiurl, project, package, expand_link = expand_link, \
2209 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress, limit_size=opts.limit_size, meta=opts.meta)
2210 except oscerr.LinkExpandError, e:
2211 print >>sys.stderr, 'Link cannot be expanded:\n', e
2212 print >>sys.stderr, 'Use "osc repairlink" for fixing merge conflicts:\n'
2213 # check out in unexpanded form at least
2214 checkout_package(apiurl, project, package, expand_link = False, \
2215 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress, limit_size=opts.limit_size, meta=opts.meta)
2216 print_request_list(apiurl, project)
2219 raise oscerr.WrongArgs('Missing argument.\n\n' \
2220 + self.get_cmd_help('checkout'))
2223 @cmdln.option('-q', '--quiet', action='store_true',
2224 help='print as little as possible')
2225 @cmdln.option('-v', '--verbose', action='store_true',
2226 help='print extra information')
2228 def do_status(self, subcmd, opts, *args):
2229 """${cmd_name}: Show status of files in working copy
2231 Show the status of files in a local working copy, indicating whether
2232 files have been changed locally, deleted, added, ...
2234 The first column in the output specifies the status and is one of the
2235 following characters:
2236 ' ' no modifications
2241 '?' item is not under version control
2242 '!' item is missing (removed by non-osc command) or incomplete
2247 osc st file1 file2 ...
2250 osc status [OPTS] [PATH...]
2254 args = parseargs(args)
2256 # storage for single Package() objects
2258 # storage for a project dir ( { prj_instance : [ package objects ] } )
2261 # when 'status' is run inside a project dir, it should
2262 # stat all packages existing in the wc
2263 if is_project_dir(arg):
2264 prj = Project(arg, False)
2266 if conf.config['do_package_tracking']:
2268 for pac in prj.pacs_have:
2269 # we cannot create package objects if the dir does not exist
2270 if not pac in prj.pacs_broken:
2271 prjpacs[prj].append(os.path.join(arg, pac))
2273 pacpaths += [arg + '/' + n for n in prj.pacs_have]
2274 elif is_package_dir(arg):
2275 pacpaths.append(arg)
2276 elif os.path.isfile(arg):
2277 pacpaths.append(arg)
2279 msg = '\'%s\' is neither a project or a package directory' % arg
2280 raise oscerr.NoWorkingCopy, msg
2282 # process single packages
2283 lines = getStatus(findpacs(pacpaths), None, opts.verbose, opts.quiet)
2284 # process project dirs
2285 for prj, pacs in prjpacs.iteritems():
2286 lines += getStatus(findpacs(pacs), prj, opts.verbose, opts.quiet)
2288 print '\n'.join(lines)
2291 def do_add(self, subcmd, opts, *args):
2292 """${cmd_name}: Mark files to be added upon the next commit
2294 In case a URL is given the file will get downloaded and registered to be downloaded
2295 by the server as well via the download_url source service.
2297 This is recommended for release tar balls to track their source and to help
2298 others to review your changes esp. on version upgrades.
2301 osc add URL [URL...]
2302 osc add FILE [FILE...]
2306 raise oscerr.WrongArgs('Missing argument.\n\n' \
2307 + self.get_cmd_help('add'))
2309 # Do some magic here, when adding a url. We want that the server to download the tar ball and to verify it
2310 for arg in parseargs(args):
2311 if arg.startswith('http://') or arg.startswith('https://') or arg.startswith('ftp://'):
2312 addDownloadUrlService(arg)
2317 def do_mkpac(self, subcmd, opts, *args):
2318 """${cmd_name}: Create a new package under version control
2321 osc mkpac new_package
2324 if not conf.config['do_package_tracking']:
2325 print >>sys.stderr, "to use this feature you have to enable \'do_package_tracking\' " \
2326 "in the [general] section in the configuration file"
2330 raise oscerr.WrongArgs('Wrong number of arguments.')
2332 createPackageDir(args[0])
2334 @cmdln.option('-r', '--recursive', action='store_true',
2335 help='If CWD is a project dir then scan all package dirs as well')
2337 def do_addremove(self, subcmd, opts, *args):
2338 """${cmd_name}: Adds new files, removes disappeared files
2340 Adds all files new in the local copy, and removes all disappeared files.
2342 ARG, if specified, is a package working copy.
2348 args = parseargs(args)
2350 for arg in arg_list:
2351 if is_project_dir(arg) and conf.config['do_package_tracking']:
2352 prj = Project(arg, False)
2353 for pac in prj.pacs_unvers:
2354 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2355 if os.path.isdir(pac_dir):
2356 addFiles([pac_dir], prj)
2357 for pac in prj.pacs_broken:
2358 if prj.get_state(pac) != 'D':
2359 prj.set_state(pac, 'D')
2360 print statfrmt('D', getTransActPath(os.path.join(prj.dir, pac)))
2362 for pac in prj.pacs_have:
2363 state = prj.get_state(pac)
2364 if state != None and state != 'D':
2365 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2366 args.append(pac_dir)
2368 prj.write_packages()
2369 elif is_project_dir(arg):
2370 print >>sys.stderr, 'osc: addremove is not supported in a project dir unless ' \
2371 '\'do_package_tracking\' is enabled in the configuration file'
2374 pacs = findpacs(args)
2376 p.todo = p.filenamelist + p.filenamelist_unvers
2378 for filename in p.todo:
2379 if os.path.isdir(filename):
2381 # ignore foo.rXX, foo.mine for files which are in 'C' state
2382 if os.path.splitext(filename)[0] in p.in_conflict:
2384 state = p.status(filename)
2387 # TODO: should ignore typical backup files suffix ~ or .orig
2389 print statfrmt('A', getTransActPath(os.path.join(p.dir, filename)))
2391 p.put_on_deletelist(filename)
2392 p.write_deletelist()
2393 os.unlink(os.path.join(p.storedir, filename))
2394 print statfrmt('D', getTransActPath(os.path.join(p.dir, filename)))
2399 @cmdln.alias('checkin')
2400 @cmdln.option('-m', '--message', metavar='TEXT',
2401 help='specify log message TEXT')
2402 @cmdln.option('-F', '--file', metavar='FILE',
2403 help='read log message from FILE')
2404 @cmdln.option('-f', '--force', default=False, action="store_true",
2405 help='force commit - do not tests a file list')
2406 @cmdln.option('--skip-validation', default=False, action="store_true",
2407 help='Skip the source validation')
2408 @cmdln.option('--verbose-validation', default=False, action="store_true",
2409 help='Run the source validation with verbose informations')
2410 def do_commit(self, subcmd, opts, *args):
2411 """${cmd_name}: Upload content to the repository server
2413 Upload content which is changed in your working copy, to the repository
2416 Optionally checks the state of a working copy, if found a file with
2417 unknown state, it requests an user input:
2418 * skip - don't change anything, just move to another file
2419 * remove - remove a file from dir
2420 * edit file list - edit filelist using EDITOR
2421 * commit - don't check anything and commit package
2422 * abort - abort commit - this is default value
2423 This can be supressed by check_filelist config item, or -f/--force
2424 command line option.
2427 osc ci # current dir
2429 osc ci file1 file2 ...
2435 args = parseargs(args)
2437 validators = conf.config['source_validator_directory']
2438 if opts.skip_validation:
2440 elif not os.path.exists(validators):
2441 print "WARNING: validator directory", validators, "configured, but not existing. Skipping ..."
2443 if opts.verbose_validation:
2444 verbose_validation = 1
2451 msg = open(opts.file).read()
2453 sys.exit('could not open file \'%s\'.' % opts.file)
2456 for arg in arg_list:
2457 if conf.config['do_package_tracking'] and is_project_dir(arg):
2459 msg = edit_message()
2461 Project(arg).commit(msg=msg, validators=validators, verbose_validation=verbose_validation)
2462 except oscerr.RuntimeError, e:
2463 print >>sys.stderr, "ERROR: source_validator failed", e
2467 pacs = findpacs(args)
2469 if conf.config['check_filelist'] and not opts.force:
2470 check_filelist_before_commit(pacs)
2473 template = store_read_file(os.path.abspath('.'), '_commit_msg')
2474 # open editor for commit message
2475 # but first, produce status and diff to append to the template
2479 changed = getStatus([pac], quiet=True)
2482 diffs += ['\nDiff for working copy: %s' % pac.dir]
2483 diffs += make_diff(pac, 0)
2484 lines.extend(get_commit_message_template(pac))
2485 if template == None:
2486 template='\n'.join(lines)
2487 # if footer is empty, there is nothing to commit, and no edit needed.
2489 msg = edit_message(footer='\n'.join(footer), template=template)
2492 store_write_string(os.path.abspath('.'), '_commit_msg', msg)
2494 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2496 if conf.config['do_package_tracking'] and len(pacs) > 0:
2500 # it is possible to commit packages from different projects at the same
2501 # time: iterate over all pacs and put each pac to the right project in the dict
2503 path = os.path.normpath(os.path.join(pac.dir, os.pardir))
2504 if is_project_dir(path):
2505 pac_path = os.path.basename(os.path.normpath(pac.absdir))
2506 prj_paths.setdefault(path, []).append(pac_path)
2507 files[pac_path] = pac.todo
2509 single_paths.append(pac.dir)
2510 for prj, packages in prj_paths.iteritems():
2512 Project(prj).commit(tuple(packages), msg, files, validators=validators, verbose_validation=verbose_validation)
2513 except oscerr.RuntimeError, e:
2514 print >>sys.stderr, "ERROR: source_validator failed", e
2516 for pac in single_paths:
2518 Package(pac).commit(msg, validators=validators, verbose_validation=verbose_validation)
2519 except oscerr.RuntimeError, e:
2520 print >>sys.stderr, "ERROR: source_validator failed", e
2524 p.commit(msg, validators=validators, verbose_validation=verbose_validation)
2526 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2528 @cmdln.option('-r', '--revision', metavar='REV',
2529 help='update to specified revision (this option will be ignored '
2530 'if you are going to update the complete project or more than '
2532 @cmdln.option('-u', '--unexpand-link', action='store_true',
2533 help='if a package is an expanded link, update to the raw _link file')
2534 @cmdln.option('-e', '--expand-link', action='store_true',
2535 help='if a package is a link, update to the expanded sources')
2536 @cmdln.option('-s', '--source-service-files', action='store_true',
2537 help='Use server side generated sources instead of local generation.' )
2538 @cmdln.option('-l', '--limit-size', metavar='limit_size',
2539 help='Skip all files with a given size')
2541 def do_update(self, subcmd, opts, *args):
2542 """${cmd_name}: Update a working copy
2547 If the current working directory is a package, update it.
2548 If the directory is a project directory, update all contained
2549 packages, AND check out newly added packages.
2551 To update only checked out packages, without checking out new
2552 ones, you might want to use "osc up *" from within the project
2556 Update the packages specified by the path argument(s)
2558 When --expand-link is used with source link packages, the expanded
2559 sources will be checked out. Without this option, the _link file and
2560 patches will be checked out. The option --unexpand-link can be used to
2561 switch back to the "raw" source with a _link file plus patch(es).
2567 if (opts.expand_link and opts.unexpand_link) \
2568 or (opts.expand_link and opts.revision) \
2569 or (opts.unexpand_link and opts.revision):
2570 raise oscerr.WrongOptions('Sorry, the options --expand-link, --unexpand-link and '
2571 '--revision are mutually exclusive.')
2573 if opts.source_service_files: service_files = True
2574 else: service_files = False
2576 args = parseargs(args)
2579 for arg in arg_list:
2580 if is_project_dir(arg):
2581 prj = Project(arg, progress_obj=self.download_progress)
2583 if conf.config['do_package_tracking']:
2584 prj.update(expand_link=opts.expand_link,
2585 unexpand_link=opts.unexpand_link)
2588 # if not tracking package, and 'update' is run inside a project dir,
2589 # it should do the following:
2590 # (a) update all packages
2591 args += prj.pacs_have
2592 # (b) fetch new packages
2593 prj.checkout_missing_pacs(expand_link = not opts.unexpand_link)
2595 print_request_list(prj.apiurl, prj.name)
2598 pacs = findpacs(args, progress_obj=self.download_progress)
2600 if opts.revision and len(args) == 1:
2601 rev, dummy = parseRevisionOption(opts.revision)
2602 if not checkRevision(pacs[0].prjname, pacs[0].name, rev, pacs[0].apiurl):
2603 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2610 print 'Updating %s' % p.name
2612 # FIXME: ugly workaround for #399247
2613 if opts.expand_link or opts.unexpand_link:
2614 if [ i for i in p.filenamelist+p.filenamelist_unvers if p.status(i) != ' ' and p.status(i) != '?']:
2615 print >>sys.stderr, 'osc: cannot expand/unexpand because your working ' \
2616 'copy has local modifications.\nPlease revert/commit them ' \
2621 if opts.expand_link and p.islink() and not p.isexpanded():
2622 if p.haslinkerror():
2624 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev)
2626 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev, linkrev="base")
2629 p.update(rev, service_files, opts.limit_size)
2630 rev = p.linkinfo.xsrcmd5
2631 print 'Expanding to rev', rev
2632 elif opts.unexpand_link and p.islink() and p.isexpanded():
2633 print 'Unexpanding to rev', p.linkinfo.lsrcmd5
2634 p.update(rev, service_files, opts.limit_size)
2635 rev = p.linkinfo.lsrcmd5
2636 elif p.islink() and p.isexpanded():
2637 rev = p.latest_rev()
2639 p.update(rev, service_files, opts.limit_size)
2640 if opts.unexpand_link:
2643 print_request_list(p.apiurl, p.prjname, p.name)
2646 @cmdln.option('-f', '--force', action='store_true',
2647 help='forces removal of entire package and its files')
2650 @cmdln.alias('remove')
2651 def do_delete(self, subcmd, opts, *args):
2652 """${cmd_name}: Mark files or package directories to be deleted upon the next 'checkin'
2655 cd .../PROJECT/PACKAGE
2656 osc delete FILE [...]
2658 osc delete PACKAGE [...]
2660 This command works on check out copies. Use "rdelete" for working on server
2661 side only. This is needed for removing the entire project.
2663 As a safety measure, projects must be empty (i.e., you need to delete all
2666 If you are sure that you want to remove a package and all
2667 its files use \'--force\' switch. Sometimes this also works without --force.
2673 raise oscerr.WrongArgs('Missing argument.\n\n' \
2674 + self.get_cmd_help('delete'))
2676 args = parseargs(args)
2677 # check if args contains a package which was removed by
2678 # a non-osc command and mark it with the 'D'-state
2681 if not os.path.exists(i):
2682 prj_dir, pac_dir = getPrjPacPaths(i)
2683 if is_project_dir(prj_dir):
2684 prj = Project(prj_dir, False)
2685 if i in prj.pacs_broken:
2686 if prj.get_state(i) != 'A':
2687 prj.set_state(pac_dir, 'D')
2689 prj.del_package_node(i)
2690 print statfrmt('D', getTransActPath(i))
2692 prj.write_packages()
2693 pacs = findpacs(args)
2697 prj_dir, pac_dir = getPrjPacPaths(p.absdir)
2698 if is_project_dir(prj_dir):
2699 if conf.config['do_package_tracking']:
2700 prj = Project(prj_dir, False)
2701 prj.delPackage(p, opts.force)
2703 print "WARNING: package tracking is disabled, operation skipped !"
2705 pathn = getTransActPath(p.dir)
2706 for filename in p.todo:
2707 ret, state = p.delete_file(filename, opts.force)
2709 print statfrmt('D', os.path.join(pathn, filename))
2712 sys.exit('\'%s\' is not under version control' % filename)
2713 elif state in ['A', 'M'] and not opts.force:
2714 sys.exit('\'%s\' has local modifications (use --force to remove this file)' % filename)
2717 def do_resolved(self, subcmd, opts, *args):
2718 """${cmd_name}: Remove 'conflicted' state on working copy files
2720 If an upstream change can't be merged automatically, a file is put into
2721 in 'conflicted' ('C') state. Within the file, conflicts are marked with
2722 special <<<<<<< as well as ======== and >>>>>>> lines.
2724 After manually resolving all conflicting parts, use this command to
2725 remove the 'conflicted' state.
2727 Note: this subcommand does not semantically resolve conflicts or
2728 remove conflict markers; it merely removes the conflict-related
2729 artifact files and allows PATH to be committed again.
2732 osc resolved FILE [FILE...]
2737 raise oscerr.WrongArgs('Missing argument.\n\n' \
2738 + self.get_cmd_help('resolved'))
2740 args = parseargs(args)
2741 pacs = findpacs(args)
2744 for filename in p.todo:
2745 print 'Resolved conflicted state of "%s"' % filename
2746 p.clear_from_conflictlist(filename)
2749 @cmdln.alias('platforms')
2750 def do_repositories(self, subcmd, opts, *args):
2751 """${cmd_name}: Shows available repositories
2755 Shows all available repositories/build targets
2757 2. osc repositories <project>
2758 Shows the configured repositories/build targets of a project
2766 print '\n'.join(get_repositories_of_project(conf.config['apiurl'], project))
2768 print '\n'.join(get_repositories(conf.config['apiurl']))
2772 def do_results_meta(self, subcmd, opts, *args):
2773 print "Command results_meta is obsolete. Please use: osc results --xml"
2777 @cmdln.option('-l', '--last-build', action='store_true',
2778 help='show last build results (succeeded/failed/unknown)')
2779 @cmdln.option('-r', '--repo', action='append', default = [],
2780 help='Show results only for specified repo(s)')
2781 @cmdln.option('-a', '--arch', action='append', default = [],
2782 help='Show results only for specified architecture(s)')
2783 @cmdln.option('', '--xml', action='store_true',
2784 help='generate output in XML (former results_meta)')
2785 def do_rresults(self, subcmd, opts, *args):
2786 print "Command rresults is obsolete. Running 'osc results' instead"
2787 self.do_results('results', opts, *args)
2791 @cmdln.option('-f', '--force', action='store_true', default=False,
2792 help="Don't ask and delete files")
2793 def do_rremove(self, subcmd, opts, project, package, *files):
2794 """${cmd_name}: Remove source files from selected package
2801 if not '/' in project:
2802 raise oscerr.WrongArgs("Missing operand, type osc help rremove for help")
2805 project, package = project.split('/')
2809 resp = raw_input("rm: remove source file `%s' from `%s/%s'? (yY|nN) " % (file, project, package))
2810 if resp not in ('y', 'Y'):
2813 delete_files(conf.config['apiurl'], project, package, (file, ))
2814 except urllib2.HTTPError, e:
2816 print >>sys.stderr, e
2818 if e.code in [ 400, 403, 404, 500 ]:
2819 if '<summary>' in body:
2820 msg = body.split('<summary>')[1]
2821 msg = msg.split('</summary>')[0]
2822 print >>sys.stderr, msg
2827 @cmdln.option('-l', '--last-build', action='store_true',
2828 help='show last build results (succeeded/failed/unknown)')
2829 @cmdln.option('-r', '--repo', action='append', default = [],
2830 help='Show results only for specified repo(s)')
2831 @cmdln.option('-a', '--arch', action='append', default = [],
2832 help='Show results only for specified architecture(s)')
2833 @cmdln.option('', '--xml', action='store_true', default=False,
2834 help='generate output in XML (former results_meta)')
2835 @cmdln.option('', '--csv', action='store_true', default=False,
2836 help='generate output in CSV format')
2837 @cmdln.option('', '--format', default='%(repository)s|%(arch)s|%(state)s|%(dirty)s|%(code)s|%(details)s',
2838 help='format string for csv output')
2839 def do_results(self, subcmd, opts, *args):
2840 """${cmd_name}: Shows the build results of a package
2843 osc results (inside working copy)
2844 osc results remote_project remote_package
2849 args = slash_split(args)
2851 apiurl = self.get_api_url()
2854 if is_project_dir(wd):
2858 opts.hide_legend = None
2859 opts.name_filter = None
2860 opts.status_filter = None
2861 opts.vertical = None
2862 self.do_prjresults('prjresults', opts, *args)
2865 project = store_read_project(wd)
2866 package = store_read_package(wd)
2868 raise oscerr.WrongArgs('Too few arguments (required none or two)')
2870 raise oscerr.WrongArgs('Too many arguments (required none or two)')
2875 if opts.xml and opts.csv:
2876 raise oscerr.WrongOptions("--xml and --csv are mutual exclusive")
2879 func = show_results_meta
2883 return format_results(get_package_results(*args), opts.format)
2890 print delim.join(func(apiurl, project, package, opts.last_build, opts.repo, opts.arch))
2892 # WARNING: this function is also called by do_results. You need to set a default there
2893 # as well when adding a new option!
2894 @cmdln.option('-q', '--hide-legend', action='store_true',
2895 help='hide the legend')
2896 @cmdln.option('-c', '--csv', action='store_true',
2898 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2899 help='show only packages with buildstatus STATUS (see legend)')
2900 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2901 help='show only packages whose names match EXPR')
2902 @cmdln.option('-a', '--arch', metavar='ARCH',
2903 help='show results only for specified architecture(s)')
2904 @cmdln.option('-r', '--repo', metavar='REPO',
2905 help='show results only for specified repo(s)')
2906 @cmdln.option('-V', '--vertical', action='store_true',
2907 help='list packages vertically instead horizontally')
2909 def do_prjresults(self, subcmd, opts, *args):
2910 """${cmd_name}: Shows project-wide build results
2913 osc prjresults (inside working copy)
2914 osc prjresults PROJECT
2918 apiurl = self.get_api_url()
2924 raise oscerr.WrongArgs('Wrong number of arguments.')
2927 project = store_read_project(wd)
2929 print '\n'.join(get_prj_results(apiurl, project, hide_legend=opts.hide_legend, csv=opts.csv, status_filter=opts.status_filter, name_filter=opts.name_filter, repo=opts.repo, arch=opts.arch, vertical=opts.vertical))
2932 @cmdln.option('-q', '--hide-legend', action='store_true',
2933 help='hide the legend')
2934 @cmdln.option('-c', '--csv', action='store_true',
2936 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2937 help='show only packages with buildstatus STATUS (see legend)')
2938 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2939 help='show only packages whose names match EXPR')
2942 def do_rprjresults(self, subcmd, opts, *args):
2943 print "Command rprjresults is obsolete. Please use 'osc prjresults'"
2947 @cmdln.option('-s', '--start', metavar='START',
2948 help='get log starting from the offset')
2949 def do_buildlog(self, subcmd, opts, *args):
2950 """${cmd_name}: Shows the build log of a package
2952 Shows the log file of the build of a package. Can be used to follow the
2953 log while it is being written.
2954 Needs to be called from within a package directory.
2956 The arguments REPOSITORY and ARCH are the first two columns in the 'osc
2957 results' output. If the buildlog url is used buildlog command has the
2958 same behavior as remotebuildlog.
2960 ${cmd_usage} [REPOSITORY ARCH | BUILDLOGURL]
2964 repository = arch = None
2966 apiurl = self.get_api_url()
2968 if len(args) == 1 and args[0].startswith('http'):
2969 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2972 package = store_read_package(wd)
2973 project = store_read_project(wd)
2977 offset = int(opts.start)
2979 if not repository or not arch:
2983 repository = args[0]
2986 print_buildlog(apiurl, project, package, repository, arch, offset)
2989 def print_repos(self):
2992 if is_package_dir(wd):
2995 elif is_project_dir(wd):
3000 print 'Valid arguments for this %s are:' % str
3002 self.do_repos(None, None)
3004 raise oscerr.WrongArgs('Missing arguments')
3007 @cmdln.alias('rbuildlog')
3008 @cmdln.option('-s', '--start', metavar='START',
3009 help='get log starting from the offset')
3010 def do_remotebuildlog(self, subcmd, opts, *args):
3011 """${cmd_name}: Shows the build log of a package
3013 Shows the log file of the build of a package. Can be used to follow the
3014 log while it is being written.
3017 osc remotebuildlog project package repository arch
3019 osc remotebuildlog project/package/repository/arch
3021 osc remotebuildlog buildlogurl
3024 if len(args) == 1 and args[0].startswith('http'):
3025 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
3027 args = slash_split(args)
3028 apiurl = conf.config['apiurl']
3030 raise oscerr.WrongArgs('Too few arguments.')
3032 raise oscerr.WrongArgs('Too many arguments.')
3034 project, package, repository, arch = args
3038 offset = int(opts.start)
3040 print_buildlog(apiurl, project, package, repository, arch, offset)
3043 @cmdln.option('-s', '--start', metavar='START',
3044 help='get log starting from offset')
3045 def do_localbuildlog(self, subcmd, opts, *args):
3046 """${cmd_name}: Shows the build log of a local buildchroot
3049 osc lbl [REPOSITORY ARCH]
3050 osc lbl # show log of newest last local build
3054 if conf.config['build-type']:
3055 # FIXME: raise Exception instead
3056 print >>sys.stderr, 'Not implemented for VMs'
3060 package = store_read_package('.')
3062 files = glob.glob(os.path.join(os.getcwd(), store, "_buildinfo-*"))
3065 raise oscerr.WrongArgs('No buildconfig found, please specify repo and arch manually.')
3069 if os.stat(f).st_mtime > os.stat(cfg).st_mtime:
3071 root = ET.parse(cfg).getroot()
3072 project = root.get("project")
3073 repo = root.get("repository")
3074 arch = root.find("arch").text
3075 elif len(args) == 2:
3076 project = store_read_project('.')
3077 package = store_read_package('.')
3081 if is_package_dir(os.curdir):
3083 raise oscerr.WrongArgs('Wrong number of arguments.')