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)
153 # overridden from class Cmdln() to use config variables in help texts
154 def _help_preprocess(self, help, cmdname):
155 help = cmdln.Cmdln._help_preprocess(self, help, cmdname)
156 return help % conf.config
159 def do_init(self, subcmd, opts, project, package=None):
160 """${cmd_name}: Initialize a directory as working copy
162 Initialize an existing directory to be a working copy of an
163 (already existing) buildservice project/package.
165 (This is the same as checking out a package and then copying sources
166 into the directory. It does NOT create a new package. To create a
167 package, use 'osc meta pkg ... ...')
169 You wouldn't normally use this command.
171 To get a working copy of a package (e.g. for building it or working on
172 it, you would normally use the checkout command. Use "osc help
173 checkout" to get help for it.
182 init_project_dir(conf.config['apiurl'], os.curdir, project)
183 print 'Initializing %s (Project: %s)' % (os.curdir, project)
185 init_package_dir(conf.config['apiurl'], project, package, os.path.curdir)
186 print 'Initializing %s (Project: %s, Package: %s)' % (os.curdir, project, package)
192 @cmdln.option('-a', '--arch', metavar='ARCH',
193 help='specify architecture (only for binaries)')
194 @cmdln.option('-r', '--repo', metavar='REPO',
195 help='specify repository (only for binaries)')
196 @cmdln.option('-b', '--binaries', action='store_true',
197 help='list built binaries instead of sources')
198 @cmdln.option('-R', '--revision', metavar='REVISION',
199 help='specify revision (only for sources)')
200 @cmdln.option('-e', '--expand', action='store_true',
201 help='expand linked package (only for sources)')
202 @cmdln.option('-u', '--unexpand', action='store_true',
203 help='always work with unexpanded (source) packages')
204 @cmdln.option('-v', '--verbose', action='store_true',
205 help='print extra information')
206 @cmdln.option('-l', '--long', action='store_true', dest='verbose',
207 help='print extra information')
208 def do_list(self, subcmd, opts, *args):
209 """${cmd_name}: List sources or binaries on the server
211 Examples for listing sources:
212 ls # list all projects
213 ls PROJECT # list packages in a project
214 ls PROJECT PACKAGE # list source files of package of a project
215 ls PROJECT PACKAGE <file> # list <file> if this file exists
216 ls -v PROJECT PACKAGE # verbosely list source files of package
217 ls -l PROJECT PACKAGE # verbosely list source files of package
218 ll PROJECT PACKAGE # verbosely list source files of package
219 LL PROJECT PACKAGE # verbosely list source files of expanded link
221 With --verbose, the following fields will be shown for each item:
223 Revision number of the last commit
225 Date and time of the last commit
227 Examples for listing binaries:
228 ls -b PROJECT # list all binaries of a project
229 ls -b PROJECT -a ARCH # list ARCH binaries of a project
230 ls -b PROJECT -r REPO # list binaries in REPO
231 ls -b PROJECT PACKAGE REPO ARCH
234 ${cmd_name} [PROJECT [PACKAGE]]
235 ${cmd_name} -b [PROJECT [PACKAGE [REPO [ARCH]]]]
239 apiurl = conf.config['apiurl']
240 args = slash_split(args)
243 if subcmd == 'lL' or subcmd == 'LL':
257 if opts.repo != args[2]:
258 raise oscerr.WrongArgs("conflicting repos specified ('%s' vs '%s')"%(opts.repo, args[2]))
265 if not opts.binaries:
266 raise oscerr.WrongArgs('Too many arguments')
268 if opts.arch != args[3]:
269 raise oscerr.WrongArgs("conflicting archs specified ('%s' vs '%s')"%(opts.arch, args[3]))
274 if opts.binaries and opts.expand:
275 raise oscerr.WrongOptions('Sorry, --binaries and --expand are mutual exclusive.')
279 # ls -b toplevel doesn't make sense, so use info from
280 # current dir if available
283 if is_project_dir(dir):
284 project = store_read_project(dir)
285 apiurl = store_read_apiurl(dir)
286 elif is_package_dir(dir):
287 project = store_read_project(dir)
288 package = store_read_package(dir)
289 apiurl = store_read_apiurl(dir)
292 raise oscerr.WrongArgs('There are no binaries to list above project level.')
294 raise oscerr.WrongOptions('Sorry, the --revision option is not supported for binaries.')
298 if opts.repo and opts.arch:
299 repos.append(Repo(opts.repo, opts.arch))
300 elif opts.repo and not opts.arch:
301 repos = [repo for repo in get_repos_of_project(apiurl, project) if repo.name == opts.repo]
302 elif opts.arch and not opts.repo:
303 repos = [repo for repo in get_repos_of_project(apiurl, project) if repo.arch == opts.arch]
305 repos = get_repos_of_project(apiurl, project)
309 results.append((repo, get_binarylist(apiurl, project, repo.name, repo.arch, package=package, verbose=opts.verbose)))
311 for result in results:
314 print '%s/%s' % (result[0].name, result[0].arch)
319 print "%9d %s %-40s" % (f.size, shorttime(f.mtime), f.name)
325 elif not opts.binaries:
327 print '\n'.join(meta_get_project_list(conf.config['apiurl']))
331 if self.options.verbose:
332 print >>sys.stderr, 'Sorry, the --verbose option is not implemented for projects.'
334 raise oscerr.WrongOptions('Sorry, the --expand option is not implemented for projects.')
336 print '\n'.join(meta_get_packagelist(conf.config['apiurl'], project))
338 elif len(args) == 2 or len(args) == 3:
340 print_not_found = True
343 l = meta_get_filelist(conf.config['apiurl'],
346 verbose=opts.verbose,
349 link_seen = '_link' in l
351 out = [ '%s %7s %9d %s %s' % (i.md5, i.rev, i.size, shorttime(i.mtime), i.name) \
352 for i in l if not fname or fname == i.name ]
354 print_not_found = False
359 print_not_found = False
362 if opts.expand or opts.unexpand or not link_seen: break
363 m = show_files_meta(conf.config['apiurl'], project, package)
365 li.read(ET.fromstring(''.join(m)).find('linkinfo'))
367 raise oscerr.LinkExpandError(project, package, li.error)
368 project, package, rev = li.project, li.package, li.rev
370 print '# -> %s %s (%s)' % (project, package, rev)
372 print '# -> %s %s (latest)' % (project, package)
374 if fname and print_not_found:
375 print 'file \'%s\' does not exist' % fname
378 @cmdln.option('-f', '--force', action='store_true',
379 help='force generation of new patchinfo file')
380 @cmdln.option('--force-update', action='store_true',
381 help='drops away collected packages from an already built patch and let it collect again')
382 def do_patchinfo(self, subcmd, opts, *args):
383 """${cmd_name}: Generate and edit a patchinfo file.
385 A patchinfo file describes the packages for an update and the kind of
390 osc patchinfo PATCH_NAME
394 project_dir = localdir = os.getcwd()
395 if is_project_dir(localdir):
396 project = store_read_project(localdir)
397 apiurl = store_read_apiurl(localdir)
399 sys.exit('This command must be called in a checked out project.')
401 for p in meta_get_packagelist(apiurl, project):
402 if p.startswith("_patchinfo:"):
405 if opts.force or not patchinfo:
406 print "Creating initial patchinfo..."
407 query='cmd=createpatchinfo'
409 query += "&name=" + args[0]
410 url = makeurl(apiurl, ['source', project], query=query)
412 for p in meta_get_packagelist(apiurl, project):
413 if p.startswith("_patchinfo:"):
416 if not os.path.exists(project_dir + "/" + patchinfo):
417 checkout_package(apiurl, project, patchinfo, prj_dir=project_dir)
419 filename = project_dir + "/" + patchinfo + "/_patchinfo"
423 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
424 help='affect only a given attribute')
425 @cmdln.option('--attribute-defaults', action='store_true',
426 help='include defined attribute defaults')
427 @cmdln.option('--attribute-project', action='store_true',
428 help='include project values, if missing in packages ')
429 @cmdln.option('-F', '--file', metavar='FILE',
430 help='read metadata from FILE, instead of opening an editor. '
431 '\'-\' denotes standard input. ')
432 @cmdln.option('-e', '--edit', action='store_true',
433 help='edit metadata')
434 @cmdln.option('-c', '--create', action='store_true',
435 help='create attribute without values')
436 @cmdln.option('-s', '--set', metavar='ATTRIBUTE_VALUES',
437 help='set attribute values')
438 @cmdln.option('--delete', action='store_true',
439 help='delete a pattern or attribute')
440 def do_meta(self, subcmd, opts, *args):
441 """${cmd_name}: Show meta information, or edit it
443 Show or edit build service metadata of type <prj|pkg|prjconf|user|pattern>.
445 This command displays metadata on buildservice objects like projects,
446 packages, or users. The type of metadata is specified by the word after
447 "meta", like e.g. "meta prj".
449 prj denotes metadata of a buildservice project.
450 prjconf denotes the (build) configuration of a project.
451 pkg denotes metadata of a buildservice package.
452 user denotes the metadata of a user.
453 pattern denotes installation patterns defined for a project.
455 To list patterns, use 'osc meta pattern PRJ'. An additional argument
456 will be the pattern file to view or edit.
458 With the --edit switch, the metadata can be edited. Per default, osc
459 opens the program specified by the environmental variable EDITOR with a
460 temporary file. Alternatively, content to be saved can be supplied via
461 the --file switch. If the argument is '-', input is taken from stdin:
462 osc meta prjconf home:user | sed ... | osc meta prjconf home:user -F -
464 When trying to edit a non-existing resource, it is created implicitly.
470 osc meta pkg PRJ PKG -e
471 osc meta attribute PRJ [PKG [SUBPACKAGE]] [--attribute ATTRIBUTE] [--create|--delete|--set [value_list]]
474 osc meta <prj|pkg|prjconf|user|pattern|attribute> ARGS...
475 osc meta <prj|pkg|prjconf|user|pattern|attribute> -e|--edit ARGS...
476 osc meta <prj|pkg|prjconf|user|pattern|attribute> -F|--file ARGS...
477 osc meta pattern --delete PRJ PATTERN
481 args = slash_split(args)
483 if not args or args[0] not in metatypes.keys():
484 raise oscerr.WrongArgs('Unknown meta type. Choose one of %s.' \
485 % ', '.join(metatypes))
491 min_args, max_args = 0, 2
492 elif cmd in ['pattern']:
493 min_args, max_args = 1, 2
494 elif cmd in ['attribute']:
495 min_args, max_args = 1, 3
496 elif cmd in ['prj', 'prjconf']:
497 min_args, max_args = 0, 1
499 min_args, max_args = 1, 1
501 if len(args) < min_args:
502 raise oscerr.WrongArgs('Too few arguments.')
503 if len(args) > max_args:
504 raise oscerr.WrongArgs('Too many arguments.')
508 if cmd in ['pkg', 'prj', 'prjconf' ]:
510 project = store_read_project(os.curdir)
516 package = store_read_package(os.curdir)
520 elif cmd == 'attribute':
526 if opts.attribute_project:
527 raise oscerr.WrongOptions('--attribute-project works only when also a package is given')
532 attributepath.append('source')
533 attributepath.append(project)
535 attributepath.append(package)
537 attributepath.append(subpackage)
538 attributepath.append('_attribute')
541 elif cmd == 'pattern':
547 # enforce pattern argument if needed
548 if opts.edit or opts.file:
549 raise oscerr.WrongArgs('A pattern file argument is required.')
552 if not opts.edit and not opts.file and not opts.delete and not opts.create and not opts.set:
554 sys.stdout.write(''.join(show_project_meta(conf.config['apiurl'], project)))
556 sys.stdout.write(''.join(show_package_meta(conf.config['apiurl'], project, package)))
557 elif cmd == 'attribute':
558 sys.stdout.write(''.join(show_attribute_meta(conf.config['apiurl'], project, package, subpackage, opts.attribute, opts.attribute_defaults, opts.attribute_project)))
559 elif cmd == 'prjconf':
560 sys.stdout.write(''.join(show_project_conf(conf.config['apiurl'], project)))
562 r = get_user_meta(conf.config['apiurl'], user)
564 sys.stdout.write(''.join(r))
565 elif cmd == 'pattern':
567 r = show_pattern_meta(conf.config['apiurl'], project, pattern)
569 sys.stdout.write(''.join(r))
571 r = show_pattern_metalist(conf.config['apiurl'], project)
573 sys.stdout.write('\n'.join(r) + '\n')
576 if opts.edit and not opts.file:
578 edit_meta(metatype='prj',
580 path_args=quote_plus(project),
583 'user': conf.config['user']}))
585 edit_meta(metatype='pkg',
587 path_args=(quote_plus(project), quote_plus(package)),
590 'user': conf.config['user']}))
591 elif cmd == 'prjconf':
592 edit_meta(metatype='prjconf',
594 path_args=quote_plus(project),
597 edit_meta(metatype='user',
599 path_args=(quote_plus(user)),
600 template_args=({'user': user}))
601 elif cmd == 'pattern':
602 edit_meta(metatype='pattern',
604 path_args=(project, pattern),
607 # create attribute entry
608 if (opts.create or opts.set) and cmd == 'attribute':
609 if not opts.attribute:
610 raise oscerr.WrongOptions('no attribute given to create')
613 opts.set = opts.set.replace('&', '&').replace('<', '<').replace('>', '>')
614 for i in opts.set.split(','):
615 values += '<value>%s</value>' % i
616 aname = opts.attribute.split(":")
617 d = '<attributes><attribute namespace=\'%s\' name=\'%s\' >%s</attribute></attributes>' % (aname[0], aname[1], values)
618 url = makeurl(conf.config['apiurl'], attributepath)
619 for data in streamfile(url, http_POST, data=d):
620 sys.stdout.write(data)
629 f = open(opts.file).read()
631 sys.exit('could not open file \'%s\'.' % opts.file)
634 edit_meta(metatype='prj',
637 path_args=quote_plus(project))
639 edit_meta(metatype='pkg',
642 path_args=(quote_plus(project), quote_plus(package)))
643 elif cmd == 'prjconf':
644 edit_meta(metatype='prjconf',
647 path_args=quote_plus(project))
649 edit_meta(metatype='user',
652 path_args=(quote_plus(user)))
653 elif cmd == 'pattern':
654 edit_meta(metatype='pattern',
657 path_args=(project, pattern))
662 path = metatypes[cmd]['path']
664 path = path % (project, pattern)
665 u = makeurl(conf.config['apiurl'], [path])
667 elif cmd == 'attribute':
668 if not opts.attribute:
669 raise oscerr.WrongOptions('no attribute given to create')
670 attributepath.append(opts.attribute)
671 u = makeurl(conf.config['apiurl'], attributepath)
672 for data in streamfile(u, http_DELETE):
673 sys.stdout.write(data)
675 raise oscerr.WrongOptions('The --delete switch is only for pattern metadata or attributes.')
678 @cmdln.option('-m', '--message', metavar='TEXT',
679 help='specify message TEXT')
680 @cmdln.option('-r', '--revision', metavar='REV',
681 help='for "create", specify a certain source revision ID (the md5 sum)')
682 @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
683 help='Superseding another request by this one')
684 @cmdln.option('--nodevelproject', action='store_true',
685 help='do not follow a defined devel project ' \
686 '(primary project where a package is developed)')
687 @cmdln.option('--cleanup', action='store_true',
688 help='remove package if submission gets accepted (default for home:<id>:branch projects)')
689 @cmdln.option('--no-cleanup', action='store_true',
690 help='never remove source package on accept, but update its content')
691 @cmdln.option('--no-update', action='store_true',
692 help='never touch source package on accept (will break source links)')
693 @cmdln.option('-d', '--diff', action='store_true',
694 help='show diff only instead of creating the actual request')
695 @cmdln.option('--yes', action='store_true',
696 help='proceed without asking.')
698 @cmdln.alias("submitreq")
699 @cmdln.alias("submitpac")
700 def do_submitrequest(self, subcmd, opts, *args):
701 """${cmd_name}: Create request to submit source into another Project
703 [See http://en.opensuse.org/Build_Service/Collaboration for information
706 See the "request" command for showing and modifing existing requests.
709 osc submitreq [OPTIONS]
710 osc submitreq [OPTIONS] DESTPRJ [DESTPKG]
711 osc submitreq [OPTIONS] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
715 src_update = conf.config['submitrequest_on_accept_action'] or None
716 # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
718 src_update = "cleanup"
719 elif opts.no_cleanup:
720 src_update = "update"
722 src_update = "noupdate"
724 args = slash_split(args)
726 # remove this block later again
727 oldcmds = ['create', 'list', 'log', 'show', 'decline', 'accept', 'delete', 'revoke']
728 if args and args[0] in oldcmds:
729 print "************************************************************************"
730 print "* WARNING: It looks that you are using this command with a *"
731 print "* deprecated syntax. *"
732 print "* Please run \"osc sr --help\" and \"osc rq --help\" *"
733 print "* to see the new syntax. *"
734 print "************************************************************************"
735 if args[0] == 'create':
741 raise oscerr.WrongArgs('Too many arguments.')
743 if len(args) > 0 and len(args) <= 2 and is_project_dir(os.getcwd()):
744 sys.exit('osc submitrequest from project directory is only working without target specs and for source linked files\n')
746 apiurl = conf.config['apiurl']
748 if len(args) == 0 and is_project_dir(os.getcwd()):
750 # submit requests for multiple packages are currently handled via multiple requests
751 # They could be also one request with multiple actions, but that avoids to accepts parts of it.
752 project = store_read_project(os.curdir)
753 apiurl = store_read_apiurl(os.curdir)
759 # loop via all packages for checking their state
760 for p in meta_get_packagelist(apiurl, project):
761 if p.startswith("_patchinfo:"):
764 # get _link info from server, that knows about the local state ...
765 u = makeurl(apiurl, ['source', project, p])
767 root = ET.parse(f).getroot()
768 linkinfo = root.find('linkinfo')
770 print "Package ", p, " is not a source link."
771 sys.exit("This is currently not supported.")
772 if linkinfo.get('error'):
773 print "Package ", p, " is a broken source link."
774 sys.exit("Please fix this first")
775 t = linkinfo.get('project')
777 if len(root.findall('entry')) > 1: # This is not really correct, but should work mostly
778 # Real fix is to ask the api if sources are modificated
779 # but there is no such call yet.
780 targetprojects.append(t)
782 print "Submitting package ", p
784 print " Skipping package ", p
786 print "Skipping package ", p, " since it is a source link pointing inside the project."
790 print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
791 print "\nEverything fine? Can we create the requests ? [y/n]"
792 if sys.stdin.read(1) != "y":
793 print >>sys.stderr, 'Aborted...'
794 raise oscerr.UserAbort()
796 # loop via all packages to do the action
798 result = create_submit_request(apiurl, project, p)
801 sys.exit("submit request creation failed")
802 sr_ids.append(result)
804 # create submit requests for all found patchinfos
808 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
811 for t in targetprojects:
812 s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \
813 (project, p, t, p, options_block)
817 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
818 (actionxml, cgi.escape(opts.message or ""))
819 u = makeurl(apiurl, ['request'], query='cmd=create')
820 f = http_POST(u, data=xml)
822 root = ET.parse(f).getroot()
823 sr_ids.append(root.get('id'))
825 print "Requests created: ",
828 sys.exit('Successfull finished')
831 # try using the working copy at hand
832 p = findpacs(os.curdir)[0]
833 src_project = p.prjname
836 if len(args) == 0 and p.islink():
837 dst_project = p.linkinfo.project
838 dst_package = p.linkinfo.package
840 dst_project = args[0]
842 dst_package = args[1]
844 dst_package = src_package
846 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
847 'Please provide it the target via commandline arguments.' % p.name)
849 modified = [i for i in p.filenamelist if p.status(i) != ' ' and p.status(i) != '?']
850 if len(modified) > 0:
851 print 'Your working copy has local modifications.'
852 repl = raw_input('Proceed without committing the local changes? (y|N) ')
854 raise oscerr.UserAbort()
856 # get the arguments from the commandline
857 src_project, src_package, dst_project = args[0:3]
859 dst_package = args[3]
861 dst_package = src_package
863 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
864 + self.get_cmd_help('request'))
866 if not opts.nodevelproject:
869 devloc = show_develproject(apiurl, dst_project, dst_package)
870 except urllib2.HTTPError:
871 print >>sys.stderr, """\
872 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
873 % (dst_project, dst_package)
877 dst_project != devloc and \
878 src_project != devloc:
880 A different project, %s, is defined as the place where development
881 of the package %s primarily takes place.
882 Please submit there instead, or use --nodevelproject to force direct submission.""" \
883 % (devloc, dst_package)
888 if opts.diff or not opts.message:
890 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
891 rdiff += server_diff(apiurl,
892 dst_project, dst_package, opts.revision,
893 src_project, src_package, None, True)
899 reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit')
900 user = conf.get_apiurl_usr(apiurl)
901 myreqs = [ i for i in reqs if i.state.who == user ]
904 print 'You already created the following submit request: %s.' % \
905 ', '.join([str(i.reqid) for i in myreqs ])
906 repl = raw_input('Supersede the old requests? (y/n/c) ')
907 if repl.lower() == 'c':
908 print >>sys.stderr, 'Aborting'
909 raise oscerr.UserAbort()
914 changes_re = re.compile(r'^--- .*\.changes ')
915 for line in rdiff.split('\n'):
916 if line.startswith('--- '):
917 if changes_re.match(line):
922 difflines.append(line)
923 opts.message = edit_message(footer=rdiff, template='\n'.join(parse_diff_for_commit_message('\n'.join(difflines))))
925 result = create_submit_request(apiurl,
926 src_project, src_package,
927 dst_project, dst_package,
928 opts.message, orev=opts.revision, src_update=src_update)
929 if repl.lower() == 'y':
931 change_request_state(apiurl, str(req.reqid), 'superseded',
932 'superseded by %s' % result, result)
935 r = change_request_state(conf.config['apiurl'],
936 opts.supersede, 'superseded', opts.message or '', result)
938 print 'created request id', result
941 @cmdln.option('-m', '--message', metavar='TEXT',
942 help='specify message TEXT')
944 @cmdln.alias("deletereq")
945 def do_deleterequest(self, subcmd, opts, *args):
946 """${cmd_name}: Create request to delete a package or project
950 osc deletereq [-m TEXT] PROJECT [PACKAGE]
954 args = slash_split(args)
957 raise oscerr.WrongArgs('Please specify at least a project.')
959 raise oscerr.WrongArgs('Too many arguments.')
961 apiurl = conf.config['apiurl']
969 opts.message = edit_message()
971 result = create_delete_request(apiurl, project, package, opts.message)
975 @cmdln.option('-m', '--message', metavar='TEXT',
976 help='specify message TEXT')
978 @cmdln.alias("changedevelreq")
979 def do_changedevelrequest(self, subcmd, opts, *args):
980 """${cmd_name}: Create request to change the devel package definition.
982 [See http://en.opensuse.org/Build_Service/Collaboration for information
985 See the "request" command for showing and modifing existing requests.
987 osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
991 raise oscerr.WrongArgs('Too many arguments.')
993 if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
995 devel_project = store_read_project(wd)
996 devel_package = package = store_read_package(wd)
997 apiurl = store_read_apiurl(wd)
998 project = conf.config['getpac_default_project']
1001 raise oscerr.WrongArgs('Too few arguments.')
1003 apiurl = conf.config['apiurl']
1005 devel_project = args[2]
1008 devel_package = package
1010 devel_package = args[3]
1012 if not opts.message:
1014 footer=textwrap.TextWrapper(width = 66).fill(
1015 'please explain why you like to change the devel project of %s/%s to %s/%s'
1016 % (project,package,devel_project,devel_package))
1017 opts.message = edit_message(footer)
1019 result = create_change_devel_request(apiurl,
1020 devel_project, devel_package,
1026 @cmdln.option('-d', '--diff', action='store_true',
1027 help='generate a diff')
1028 @cmdln.option('-u', '--unified', action='store_true',
1029 help='output the diff in the unified diff format')
1030 @cmdln.option('-m', '--message', metavar='TEXT',
1031 help='specify message TEXT')
1032 @cmdln.option('-t', '--type', metavar='TYPE',
1033 help='limit to requests which contain a given action type (submit/delete/change_devel)')
1034 @cmdln.option('-a', '--all', action='store_true',
1035 help='all states. Same as\'-s all\'')
1036 @cmdln.option('-s', '--state', default='', # default is 'all' if no args given, 'new' otherwise
1037 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]')
1038 @cmdln.option('-D', '--days', metavar='DAYS',
1039 help='only list requests in state "new" or changed in the last DAYS. [default=%(request_list_days)s]')
1040 @cmdln.option('-U', '--user', metavar='USER',
1041 help='same as -M, but for the specified USER')
1042 @cmdln.option('-b', '--brief', action='store_true', default=False,
1043 help='print output in list view as list subcommand')
1044 @cmdln.option('-M', '--mine', action='store_true',
1045 help='only show requests created by yourself')
1046 @cmdln.option('-B', '--bugowner', action='store_true',
1047 help='also show requests about packages where I am bugowner')
1048 @cmdln.option('-i', '--interactive', action='store_true',
1049 help='interactive review of request')
1050 @cmdln.option('--non-interactive', action='store_true',
1051 help='non-interactive review of request')
1052 @cmdln.option('--exclude-target-project', action='append',
1053 help='exclude target project from request list')
1054 @cmdln.option('--involved-projects', action='store_true',
1055 help='show all requests for project/packages where USER is involved')
1057 @cmdln.alias("review")
1058 def do_request(self, subcmd, opts, *args):
1059 """${cmd_name}: Show and modify requests
1061 [See http://en.opensuse.org/Build_Service/Collaboration for information
1064 This command shows and modifies existing requests. To create new requests
1065 you need to call one of the following:
1068 osc changedevelrequest
1069 To send low level requests to the buildservice API, use:
1072 This command has the following sub commands:
1074 "list" lists open requests attached to a project or package or person.
1075 Uses the project/package of the current directory if none of
1076 -M, -U USER, project/package are given.
1078 "log" will show the history of the given ID
1080 "show" will show the request itself, and generate a diff for review, if
1081 used with the --diff option. The keyword show can be omitted if the ID is numeric.
1083 "decline" will change the request state to "declined" and append a
1084 message that you specify with the --message option.
1086 "wipe" will permanently delete a request.
1088 "revoke" will set the request state to "revoked" and append a
1089 message that you specify with the --message option.
1091 "accept" will change the request state to "accepted" and will trigger
1092 the actual submit process. That would normally be a server-side copy of
1093 the source package to the target package.
1095 "checkout" will checkout the request's source package. This only works for "submit" requests.
1098 osc request list [-M] [-U USER] [-s state] [-D DAYS] [-t type] [-B] [PRJ [PKG]]
1100 osc request [show] [-d] [-b] ID
1101 osc request accept [-m TEXT] ID
1102 osc request decline [-m TEXT] ID
1103 osc request revoke [-m TEXT] ID
1105 osc request checkout/co ID
1106 osc review accept [-m TEXT] ID
1107 osc review decline [-m TEXT] ID
1111 args = slash_split(args)
1113 if opts.all and opts.state:
1114 raise oscerr.WrongOptions('Sorry, the options \'--all\' and \'--state\' ' \
1115 'are mutually exclusive.')
1116 if opts.mine and opts.user:
1117 raise oscerr.WrongOptions('Sorry, the options \'--user\' and \'--mine\' ' \
1118 'are mutually exclusive.')
1119 if opts.interactive and opts.non_interactive:
1120 raise oscerr.WrongOptions('Sorry, the options \'--interactive\' and ' \
1121 '\'--non-interactive\' are mutually exclusive')
1126 if opts.state == '':
1129 if opts.state == '':
1132 cmds = ['list', 'log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co', 'help']
1133 if not args or args[0] not in cmds:
1134 raise oscerr.WrongArgs('Unknown request action %s. Choose one of %s.' \
1135 % (args[0],', '.join(cmds)))
1141 return self.do_help(['help', 'request'])
1144 min_args, max_args = 1, 1
1145 elif cmd in ['list']:
1146 min_args, max_args = 0, 2
1148 min_args, max_args = 1, 1
1149 if len(args) < min_args:
1150 raise oscerr.WrongArgs('Too few arguments.')
1151 if len(args) > max_args:
1152 raise oscerr.WrongArgs('Too many arguments.')
1154 apiurl = conf.config['apiurl']
1161 elif not opts.mine and not opts.user:
1163 project = store_read_project(os.curdir)
1164 apiurl = store_read_apiurl(os.curdir)
1165 package = store_read_package(os.curdir)
1166 except oscerr.NoWorkingCopy:
1171 elif cmd in ['log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co']:
1176 states = ('new', 'accepted', 'revoked', 'declined')
1177 state_list = opts.state.split(',')
1178 if opts.state == 'all':
1179 state_list = ['all']
1181 for s in state_list:
1183 raise oscerr.WrongArgs('Unknown state \'%s\', try one of %s' % (s, ','.join(states)))
1186 who = conf.get_apiurl_usr(apiurl)
1190 state_list = ['all']
1192 ## FIXME -B not implemented!
1194 if (self.options.debug):
1195 print 'list: option --bugowner ignored: not impl.'
1197 if opts.involved_projects:
1198 who = who or conf.get_apiurl_usr(apiurl)
1199 results = get_user_projpkgs_request_list(apiurl, who, req_state=state_list,
1200 req_type=opts.type, exclude_projects=opts.exclude_target_project or [])
1202 results = get_request_list(apiurl, project, package, who,
1203 state_list, opts.type, opts.exclude_target_project or [])
1204 results.sort(reverse=True)
1206 days = opts.days or conf.config['request_list_days']
1213 since = time.strftime('%Y-%m-%dT%H:%M:%S',time.localtime(time.time()-days*24*3600))
1216 ## bs has received 2009-09-20 a new xquery compare() function
1217 ## which allows us to limit the list inside of get_request_list
1218 ## That would be much faster for coolo. But counting the remainder
1219 ## would not be possible with current xquery implementation.
1220 ## Workaround: fetch all, and filter on client side.
1222 ## FIXME: date filtering should become implemented on server side
1223 for result in results:
1224 if days == 0 or result.state.when > since or result.state.name == 'new':
1225 print result.list_view()
1229 print "There are %d requests older than %s days.\n" % (skipped, days)
1232 for l in get_request_log(conf.config['apiurl'], reqid):
1237 r = get_request(conf.config['apiurl'], reqid)
1240 elif (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive:
1241 return request_interactive_review(conf.config['apiurl'], r)
1244 # fixme: will inevitably fail if the given target doesn't exist
1245 if opts.diff and r.actions[0].type != 'submit':
1246 raise oscerr.WrongOptions('\'--diff\' is not possible for request type: \'%s\'' % r.actions[0].type)
1249 print server_diff(conf.config['apiurl'],
1250 r.actions[0].dst_project, r.actions[0].dst_package, None,
1251 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, True)
1252 except urllib2.HTTPError, e:
1254 e.osc_msg = 'Diff not possible'
1256 # backward compatiblity: only a recent api/backend supports the missingok parameter
1258 print server_diff(conf.config['apiurl'],
1259 r.actions[0].dst_project, r.actions[0].dst_package, None,
1260 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, False)
1261 except urllib2.HTTPError, e:
1262 e.osc_msg = 'Diff not possible'
1266 elif cmd == 'checkout' or cmd == 'co':
1267 r = get_request(conf.config['apiurl'], reqid)
1268 submits = [ i for i in r.actions if i.type == 'submit' ]
1269 if not len(submits):
1270 raise oscerr.WrongArgs('\'checkout\' only works for \'submit\' requests')
1271 checkout_package(conf.config['apiurl'], submits[0].src_project, submits[0].src_package, \
1272 submits[0].src_rev, expand_link=True, prj_dir=submits[0].src_project)
1275 if not opts.message:
1276 opts.message = edit_message()
1277 state_map = {'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked'}
1278 # Change review state only
1279 if subcmd == 'review':
1280 if cmd in ['accept', 'decline']:
1281 r = change_review_state(conf.config['apiurl'],
1282 reqid, state_map[cmd], conf.config['user'], '', opts.message or '')
1284 # Change state of entire request
1285 elif cmd in ['accept', 'decline', 'wipe', 'revoke']:
1286 r = change_request_state(conf.config['apiurl'],
1287 reqid, state_map[cmd], opts.message or '')
1290 # editmeta and its aliases are all depracated
1291 @cmdln.alias("editprj")
1292 @cmdln.alias("createprj")
1293 @cmdln.alias("editpac")
1294 @cmdln.alias("createpac")
1295 @cmdln.alias("edituser")
1296 @cmdln.alias("usermeta")
1298 def do_editmeta(self, subcmd, opts, *args):
1301 Obsolete command to edit metadata. Use 'meta' now.
1303 See the help output of 'meta'.
1307 print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
1308 print >>sys.stderr, 'See \'osc help meta\'.'
1309 #self.do_help([None, 'meta'])
1313 @cmdln.option('-r', '--revision', metavar='rev',
1314 help='use the specified revision.')
1315 @cmdln.option('-u', '--unset', action='store_true',
1316 help='remove revision in link, it will point always to latest revision')
1317 def do_setlinkrev(self, subcmd, opts, *args):
1318 """${cmd_name}: Updates a revision number in a source link.
1320 This command adds or updates a specified revision number in a source link.
1321 The current revision of the source is used, if no revision number is specified.
1325 osc setlinkrev PROJECT [PACKAGE]
1329 args = slash_split(args)
1330 apiurl = conf.config['apiurl']
1333 p = findpacs(os.curdir)[0]
1338 sys.exit('Local directory is no checked out source link package, aborting')
1339 elif len(args) == 2:
1342 elif len(args) == 1:
1345 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1346 + self.get_cmd_help('setlinkrev'))
1349 packages = [ package ]
1351 packages = meta_get_packagelist(apiurl, project)
1354 print "setting revision for package", p
1358 rev, dummy = parseRevisionOption(opts.revision)
1359 set_link_rev(apiurl, project, p, rev)
1362 def do_linktobranch(self, subcmd, opts, *args):
1363 """${cmd_name}: Convert a package containing a classic link with patch to a branch
1365 This command tells the server to convert a _link with or without a project.diff
1366 to a branch. This is a full copy with a _link file pointing to the branched place.
1369 osc linktobranch # can be used in checked out package
1370 osc linktobranch PROJECT PACKAGE
1374 args = slash_split(args)
1377 project = store_read_project(wd)
1378 package = store_read_package(wd)
1379 apiurl = store_read_apiurl(wd)
1380 update_local_dir = True
1382 raise oscerr.WrongArgs('Too few arguments (required none or two)')
1384 raise oscerr.WrongArgs('Too many arguments (required none or two)')
1386 apiurl = conf.config['apiurl']
1389 update_local_dir = False
1392 link_to_branch(apiurl, project, package)
1393 if update_local_dir:
1395 pac.update(rev=pac.latest_rev())
1398 @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'],
1399 help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.')
1400 @cmdln.option('-c', '--current', action='store_true',
1401 help='link fixed against current revision.')
1402 @cmdln.option('-r', '--revision', metavar='rev',
1403 help='link the specified revision.')
1404 @cmdln.option('-f', '--force', action='store_true',
1405 help='overwrite an existing link file if it is there.')
1406 @cmdln.option('-d', '--disable-publish', action='store_true',
1407 help='disable publishing of the linked package')
1408 def do_linkpac(self, subcmd, opts, *args):
1409 """${cmd_name}: "Link" a package to another package
1411 A linked package is a clone of another package, but plus local
1412 modifications. It can be cross-project.
1414 The DESTPAC name is optional; the source packages' name will be used if
1417 Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
1419 To add a patch, add the patch as file and add it to the _link file.
1420 You can also specify text which will be inserted at the top of the spec file.
1422 See the examples in the _link file.
1425 osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1429 args = slash_split(args)
1431 if not args or len(args) < 3:
1432 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1433 + self.get_cmd_help('linkpac'))
1435 rev, dummy = parseRevisionOption(opts.revision)
1437 src_project = args[0]
1438 src_package = args[1]
1439 dst_project = args[2]
1441 dst_package = args[3]
1443 dst_package = src_package
1445 if src_project == dst_project and src_package == dst_package:
1446 raise oscerr.WrongArgs('Error: source and destination are the same.')
1448 if src_project == dst_project and not opts.cicount:
1449 # in this case, the user usually wants to build different spec
1450 # files from the same source
1451 opts.cicount = "copy"
1454 rev = show_upstream_rev(conf.config['apiurl'], src_project, src_package)
1456 if rev and not checkRevision(src_project, src_package, rev):
1457 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
1460 link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish)
1462 @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]',
1463 help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]')
1464 @cmdln.option('-d', '--disable-publish', action='store_true',
1465 help='disable publishing of the aggregated package')
1466 def do_aggregatepac(self, subcmd, opts, *args):
1467 """${cmd_name}: "Aggregate" a package to another package
1469 Aggregation of a package means that the build results (binaries) of a
1470 package are basically copied into another project.
1471 This can be used to make packages available from building that are
1472 needed in a project but available only in a different project. Note
1473 that this is done at the expense of disk space. See
1474 http://en.opensuse.org/Build_Service/Tips_and_Tricks#_link_and__aggregate
1475 for more information.
1477 The DESTPAC name is optional; the source packages' name will be used if
1481 osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1485 args = slash_split(args)
1487 if not args or len(args) < 3:
1488 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1489 + self.get_cmd_help('aggregatepac'))
1491 src_project = args[0]
1492 src_package = args[1]
1493 dst_project = args[2]
1495 dst_package = args[3]
1497 dst_package = src_package
1499 if src_project == dst_project and src_package == dst_package:
1500 raise oscerr.WrongArgs('Error: source and destination are the same.')
1504 for pair in opts.map_repo.split(','):
1505 src_tgt = pair.split('=')
1506 if len(src_tgt) != 2:
1507 raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo)
1508 repo_map[src_tgt[0]] = src_tgt[1]
1510 aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish)
1513 @cmdln.option('-c', '--client-side-copy', action='store_true',
1514 help='do a (slower) client-side copy')
1515 @cmdln.option('-k', '--keep-maintainers', action='store_true',
1516 help='keep original maintainers. Default is remove all and replace with the one calling the script.')
1517 @cmdln.option('-d', '--keep-develproject', action='store_true',
1518 help='keep develproject tag in the package metadata')
1519 @cmdln.option('-r', '--revision', metavar='rev',
1520 help='link the specified revision.')
1521 @cmdln.option('-t', '--to-apiurl', metavar='URL',
1522 help='URL of destination api server. Default is the source api server.')
1523 @cmdln.option('-m', '--message', metavar='TEXT',
1524 help='specify message TEXT')
1525 @cmdln.option('-e', '--expand', action='store_true',
1526 help='if the source package is a link then copy the expanded version of the link')
1527 def do_copypac(self, subcmd, opts, *args):
1528 """${cmd_name}: Copy a package
1530 A way to copy package to somewhere else.
1532 It can be done across buildservice instances, if the -t option is used.
1533 In that case, a client-side copy is implied.
1535 Using --client-side-copy always involves downloading all files, and
1536 uploading them to the target.
1538 The DESTPAC name is optional; the source packages' name will be used if
1542 osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1546 args = slash_split(args)
1548 if not args or len(args) < 3:
1549 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1550 + self.get_cmd_help('copypac'))
1552 src_project = args[0]
1553 src_package = args[1]
1554 dst_project = args[2]
1556 dst_package = args[3]
1558 dst_package = src_package
1560 src_apiurl = conf.config['apiurl']
1562 dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl)
1564 dst_apiurl = src_apiurl
1566 if src_project == dst_project and \
1567 src_package == dst_package and \
1568 src_apiurl == dst_apiurl:
1569 raise oscerr.WrongArgs('Source and destination are the same.')
1571 if src_apiurl != dst_apiurl:
1572 opts.client_side_copy = True
1574 rev, dummy = parseRevisionOption(opts.revision)
1577 comment = opts.message
1580 rev = show_upstream_rev(src_apiurl, src_project, src_package)
1581 comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev )
1583 r = copy_pac(src_apiurl, src_project, src_package,
1584 dst_apiurl, dst_project, dst_package,
1585 client_side_copy=opts.client_side_copy,
1586 keep_maintainers=opts.keep_maintainers,
1587 keep_develproject=opts.keep_develproject,
1594 @cmdln.option('-c', '--checkout', action='store_true',
1595 help='Checkout branched package afterwards ' \
1596 '(\'osc bco\' is a shorthand for this option)' )
1597 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
1598 help='Use this attribute to find affected packages (default is OBS:Maintained)')
1599 @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE',
1600 help='Use this attribute to find update projects (default is OBS:UpdateProject) ')
1601 def do_mbranch(self, subcmd, opts, *args):
1602 """${cmd_name}: Multiple branch of a package
1604 [See http://en.opensuse.org/Build_Service/Concepts/Maintenance for information
1607 This command is used for creating multiple links of defined version of a package
1608 in one project. This is esp. used for maintenance updates.
1610 The branched package will live in
1611 home:USERNAME:branches:ATTRIBUTE:PACKAGE
1612 if nothing else specified.
1615 osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ]
1618 args = slash_split(args)
1621 maintained_attribute = conf.config['maintained_attribute']
1622 maintained_update_project_attribute = conf.config['maintained_update_project_attribute']
1624 if not len(args) or len(args) > 2:
1625 raise oscerr.WrongArgs('Wrong number of arguments.')
1631 r = attribute_branch_pkg(conf.config['apiurl'], maintained_attribute, maintained_update_project_attribute, \
1635 print >>sys.stderr, 'ERROR: Attribute branch call came not back with a project.'
1638 print "Project " + r + " created."
1641 init_project_dir(conf.config['apiurl'], r, r)
1642 print statfrmt('A', r)
1645 for package in meta_get_packagelist(conf.config['apiurl'], r):
1647 checkout_package(conf.config['apiurl'], r, package, expand_link = True, prj_dir = r)
1649 print >>sys.stderr, 'Error while checkout package:\n', package
1651 if conf.config['verbose']:
1652 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1655 @cmdln.alias('branchco')
1657 @cmdln.alias('getpac')
1658 @cmdln.option('--nodevelproject', action='store_true',
1659 help='do not follow a defined devel project ' \
1660 '(primary project where a package is developed)')
1661 @cmdln.option('-c', '--checkout', action='store_true',
1662 help='Checkout branched package afterwards ' \
1663 '(\'osc bco\' is a shorthand for this option)' )
1664 @cmdln.option('-r', '--revision', metavar='rev',
1665 help='branch against a specific revision')
1666 @cmdln.option('-m', '--message', metavar='TEXT',
1667 help='specify message TEXT')
1668 def do_branch(self, subcmd, opts, *args):
1669 """${cmd_name}: Branch a package
1671 [See http://en.opensuse.org/Build_Service/Collaboration for information
1674 Create a source link from a package of an existing project to a new
1675 subproject of the requesters home project (home:branches:)
1677 The branched package will live in
1678 home:USERNAME:branches:PROJECT/PACKAGE
1679 if nothing else specified.
1681 With getpac or bco, the branched package will come from
1682 %(getpac_default_project)s
1683 if nothing else specified.
1687 osc branch SOURCEPROJECT SOURCEPACKAGE
1688 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT
1689 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE
1690 osc getpac SOURCEPACKAGE
1695 if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': opts.checkout = True
1696 args = slash_split(args)
1697 tproject = tpackage = None
1699 if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1:
1700 print >>sys.stderr, 'defaulting to %s/%s' % (conf.config['getpac_default_project'], args[0])
1701 # python has no args.unshift ???
1702 args = [ conf.config['getpac_default_project'] , args[0] ]
1704 if len(args) == 0 and is_package_dir('.'):
1705 args = (store_read_project('.'), store_read_package('.'))
1707 if len(args) < 2 or len(args) > 4:
1708 raise oscerr.WrongArgs('Wrong number of arguments.')
1710 expected = 'home:%s:branches:%s' % (conf.config['user'], args[0])
1712 expected = tproject = args[2]
1716 if not opts.message:
1717 footer='please specify the purpose of your branch'
1718 template='This package was branched from %s in order to ...\n' % args[0]
1719 opts.message = edit_message(footer, template)
1721 exists, targetprj, targetpkg, srcprj, srcpkg = \
1722 branch_pkg(conf.config['apiurl'], args[0], args[1],
1723 nodevelproject=opts.nodevelproject, rev=opts.revision,
1724 target_project=tproject, target_package=tpackage,
1725 return_existing=opts.checkout, msg=opts.message or '')
1727 print >>sys.stderr, 'Using existing branch project: %s' % targetprj
1730 if not exists and (srcprj is not None and srcprj != args[0] or \
1731 srcprj is None and targetprj != expected):
1732 devloc = srcprj or targetprj
1733 if not srcprj and 'branches:' in targetprj:
1734 devloc = targetprj.split('branches:')[1]
1735 print '\nNote: The branch has been created of a different project,\n' \
1737 ' which is the primary location of where development for\n' \
1738 ' that package takes place.\n' \
1739 ' That\'s also where you would normally make changes against.\n' \
1740 ' A direct branch of the specified package can be forced\n' \
1741 ' with the --nodevelproject option.\n' % devloc
1743 package = tpackage or args[1]
1745 checkout_package(conf.config['apiurl'], targetprj, package,
1746 expand_link=True, prj_dir=targetprj)
1747 if conf.config['verbose']:
1748 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1751 if conf.get_configParser().get('general', 'apiurl') != conf.config['apiurl']:
1752 apiopt = '-A %s ' % conf.config['apiurl']
1753 print 'A working copy of the branched package can be checked out with:\n\n' \
1755 % (apiopt, targetprj, package)
1756 print_request_list(conf.config['apiurl'], args[0], args[1])
1758 print_request_list(conf.config['apiurl'], devloc, args[1])
1762 @cmdln.option('-f', '--force', action='store_true',
1763 help='deletes a package or an empty project')
1764 def do_rdelete(self, subcmd, opts, *args):
1765 """${cmd_name}: Delete a project or packages on the server.
1767 As a safety measure, project must be empty (i.e., you need to delete all
1768 packages first). If you are sure that you want to remove this project and all
1769 its packages use \'--force\' switch.
1772 osc rdelete -f PROJECT
1773 osc rdelete PROJECT PACKAGE [PACKAGE ...]
1778 args = slash_split(args)
1780 raise oscerr.WrongArgs('Missing argument.')
1786 # careful: if pkg is an empty string, the package delete request results
1787 # into a project delete request - which works recursively...
1789 delete_package(conf.config['apiurl'], prj, pkg)
1790 elif len(meta_get_packagelist(conf.config['apiurl'], prj)) >= 1 and not opts.force:
1791 print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
1792 'If you are sure that you want to remove this project and all its ' \
1793 'packages use the \'--force\' switch'
1796 delete_project(conf.config['apiurl'], prj)
1799 def do_deletepac(self, subcmd, opts, *args):
1800 print """${cmd_name} is obsolete !
1803 osc delete for checked out packages or projects
1805 osc rdelete for server side operations."""
1810 @cmdln.option('-f', '--force', action='store_true',
1811 help='deletes a project and its packages')
1812 def do_deleteprj(self, subcmd, opts, project):
1813 """${cmd_name} is obsolete !
1820 @cmdln.alias('metafromspec')
1821 @cmdln.option('', '--specfile', metavar='FILE',
1822 help='Path to specfile. (if you pass more than working copy this option is ignored)')
1823 def do_updatepacmetafromspec(self, subcmd, opts, *args):
1824 """${cmd_name}: Update package meta information from a specfile
1826 ARG, if specified, is a package working copy.
1832 args = parseargs(args)
1833 if opts.specfile and len(args) == 1:
1834 specfile = opts.specfile
1837 pacs = findpacs(args)
1839 p.read_meta_from_spec(specfile)
1840 p.update_package_meta()
1844 @cmdln.option('-c', '--change', metavar='rev',
1845 help='the change made by revision rev (like -r rev-1:rev).'
1846 'If rev is negative this is like -r rev:rev-1.')
1847 @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
1848 help='If rev1 is specified it will compare your working copy against '
1849 'the revision (rev1) on the server. '
1850 'If rev1 and rev2 are specified it will compare rev1 against rev2 '
1851 '(NOTE: changes in your working copy are ignored in this case)')
1852 @cmdln.option('-p', '--plain', action='store_true',
1853 help='output the diff in plain (not unified) diff format')
1854 @cmdln.option('--missingok', action='store_true',
1855 help='do not fail if the source or target project/package does not exist on the server')
1856 def do_diff(self, subcmd, opts, *args):
1857 """${cmd_name}: Generates a diff
1859 Generates a diff, comparing local changes against the repository
1862 ARG, specified, is a filename to include in the diff.
1868 args = parseargs(args)
1869 pacs = findpacs(args)
1873 rev = int(opts.change)
1883 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1886 rev1, rev2 = parseRevisionOption(opts.revision)
1890 diff += ''.join(make_diff(pac, rev1))
1892 diff += server_diff(pac.apiurl, pac.prjname, pac.name, rev1,
1893 pac.prjname, pac.name, rev2, not opts.plain, opts.missingok)
1898 @cmdln.option('--oldprj', metavar='OLDPRJ',
1899 help='project to compare against'
1900 ' (deprecated, use 3 argument form)')
1901 @cmdln.option('--oldpkg', metavar='OLDPKG',
1902 help='package to compare against'
1903 ' (deprecated, use 3 argument form)')
1904 @cmdln.option('-r', '--revision', metavar='N[:M]',
1905 help='revision id, where N = old revision and M = new revision')
1906 @cmdln.option('-p', '--plain', action='store_true',
1907 help='output the diff in plain (not unified) diff format')
1908 @cmdln.option('-c', '--change', metavar='rev',
1909 help='the change made by revision rev (like -r rev-1:rev). '
1910 'If rev is negative this is like -r rev:rev-1.')
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_rdiff(self, subcmd, opts, *args):
1914 """${cmd_name}: Server-side "pretty" diff of two packages
1916 Compares two packages (three or four arguments) or shows the
1917 changes of a specified revision of a package (two arguments)
1919 If no revision is specified the latest revision is used.
1921 Note that this command doesn't return a normal diff (which could be
1922 applied as patch), but a "pretty" diff, which also compares the content
1927 osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC]
1928 osc ${cmd_name} PROJECT PACKAGE
1932 args = slash_split(args)
1943 new_project = args[0]
1944 new_package = args[1]
1946 old_project = opts.oldprj
1948 old_package = opts.oldpkg
1949 elif len(args) == 3 or len(args) == 4:
1950 if opts.oldprj or opts.oldpkg:
1951 raise oscerr.WrongArgs('--oldpkg and --oldprj are only valid with two arguments')
1952 old_project = args[0]
1953 new_package = old_package = args[1]
1954 new_project = args[2]
1956 new_package = args[3]
1958 raise oscerr.WrongArgs('Wrong number of arguments')
1963 rev = int(opts.change)
1973 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1977 rev1, rev2 = parseRevisionOption(opts.revision)
1979 rdiff = server_diff(conf.config['apiurl'],
1980 old_project, old_package, rev1,
1981 new_project, new_package, rev2, not opts.plain, opts.missingok)
1986 def do_install(self, subcmd, opts, *args):
1987 """${cmd_name}: install a package after build via zypper in -r
1989 Not implemented yet. Use osc repourls,
1990 select the url you best like (standard),
1991 chop off after the last /, this should work with zypper.
1998 args = slash_split(args)
1999 args = expand_proj_pack(args)
2002 ## if there is only one argument, and it ends in .ymp
2003 ## then fetch it, Parse XML to get the first
2004 ## metapackage.group.repositories.repository.url
2005 ## and construct zypper cmd's for all
2006 ## metapackage.group.software.item.name
2008 ## if args[0] is already an url, the use it as is.
2010 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])
2011 print self.do_install.__doc__
2012 print "Example: \n" + cmd
2015 def do_repourls(self, subcmd, opts, *args):
2016 """${cmd_name}: Shows URLs of .repo files
2018 Shows URLs on which to access the project .repos files (yum-style
2019 metadata) on download.opensuse.org.
2022 osc repourls [PROJECT]
2027 apiurl = conf.config['apiurl']
2031 elif len(args) == 0:
2032 project = store_read_project('.')
2033 apiurl = store_read_apiurl('.')
2035 raise oscerr.WrongArgs('Wrong number of arguments')
2037 # XXX: API should somehow tell that
2038 url_tmpl = 'http://download.opensuse.org/repositories/%s/%s/%s.repo'
2039 repos = get_repositories_of_project(apiurl, project)
2041 print url_tmpl % (project.replace(':', ':/'), repo, project)
2044 @cmdln.option('-r', '--revision', metavar='rev',
2045 help='checkout the specified revision. '
2046 'NOTE: if you checkout the complete project '
2047 'this option is ignored!')
2048 @cmdln.option('-e', '--expand-link', action='store_true',
2049 help='if a package is a link, check out the expanded '
2050 'sources (no-op, since this became the default)')
2051 @cmdln.option('-u', '--unexpand-link', action='store_true',
2052 help='if a package is a link, check out the _link file ' \
2053 'instead of the expanded sources')
2054 @cmdln.option('-c', '--current-dir', action='store_true',
2055 help='place PACKAGE folder in the current directory' \
2056 'instead of a PROJECT/PACKAGE directory')
2057 @cmdln.option('-s', '--source-service-files', action='store_true',
2058 help='server side generated files of source services' \
2059 'gets downloaded as well' )
2060 @cmdln.option('-l', '--limit-size', metavar='limit_size',
2061 help='Skip all files with a given size')
2063 def do_checkout(self, subcmd, opts, *args):
2064 """${cmd_name}: Check out content from the repository
2066 Check out content from the repository server, creating a local working
2069 When checking out a single package, the option --revision can be used
2070 to specify a revision of the package to be checked out.
2072 When a package is a source link, then it will be checked out in
2073 expanded form. If --unexpand-link option is used, the checkout will
2074 instead produce the raw _link file plus patches.
2077 osc co PROJECT [PACKAGE] [FILE]
2078 osc co PROJECT # entire project
2079 osc co PROJECT PACKAGE # a package
2080 osc co PROJECT PACKAGE FILE # single file -> to current dir
2082 while inside a project directory:
2083 osc co PACKAGE # check out PACKAGE from project
2088 if opts.unexpand_link:
2092 if opts.source_service_files:
2093 service_files = True
2095 service_files = False
2097 args = slash_split(args)
2098 project = package = filename = None
2099 apiurl = conf.config['apiurl']
2101 project = project_dir = args[0]
2107 if args and len(args) == 1:
2108 localdir = os.getcwd()
2109 if is_project_dir(localdir):
2110 project = store_read_project(localdir)
2111 project_dir = localdir
2113 apiurl = store_read_apiurl(localdir)
2115 rev, dummy = parseRevisionOption(opts.revision)
2119 if rev and rev != "latest" and not checkRevision(project, package, rev):
2120 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2124 get_source_file(apiurl, project, package, filename, revision=rev, progress_obj=self.download_progress)
2127 if opts.current_dir:
2129 checkout_package(apiurl, project, package, rev, expand_link=expand_link, \
2130 prj_dir=project_dir, service_files=service_files, progress_obj=self.download_progress, limit_size=opts.limit_size)
2131 print_request_list(apiurl, project, package)
2135 if sys.platform[:3] == 'win':
2136 prj_dir = prj_dir.replace(':', ';')
2137 if os.path.exists(prj_dir):
2138 sys.exit('osc: project \'%s\' already exists' % project)
2140 # check if the project does exist (show_project_meta will throw an exception)
2141 show_project_meta(apiurl, project)
2143 init_project_dir(apiurl, prj_dir, project)
2144 print statfrmt('A', prj_dir)
2147 for package in meta_get_packagelist(apiurl, project):
2149 checkout_package(apiurl, project, package, expand_link = expand_link, \
2150 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress, limit_size=opts.limit_size)
2151 except oscerr.LinkExpandError, e:
2152 print >>sys.stderr, 'Link cannot be expanded:\n', e
2153 print >>sys.stderr, 'Use "osc repairlink" for fixing merge conflicts:\n'
2154 # check out in unexpanded form at least
2155 checkout_package(apiurl, project, package, expand_link = False, \
2156 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress, limit_size=opts.limit_size)
2157 print_request_list(apiurl, project)
2160 raise oscerr.WrongArgs('Missing argument.\n\n' \
2161 + self.get_cmd_help('checkout'))
2164 @cmdln.option('-q', '--quiet', action='store_true',
2165 help='print as little as possible')
2166 @cmdln.option('-v', '--verbose', action='store_true',
2167 help='print extra information')
2169 def do_status(self, subcmd, opts, *args):
2170 """${cmd_name}: Show status of files in working copy
2172 Show the status of files in a local working copy, indicating whether
2173 files have been changed locally, deleted, added, ...
2175 The first column in the output specifies the status and is one of the
2176 following characters:
2177 ' ' no modifications
2182 '?' item is not under version control
2183 '!' item is missing (removed by non-osc command) or incomplete
2188 osc st file1 file2 ...
2191 osc status [OPTS] [PATH...]
2195 args = parseargs(args)
2197 # storage for single Package() objects
2199 # storage for a project dir ( { prj_instance : [ package objects ] } )
2202 # when 'status' is run inside a project dir, it should
2203 # stat all packages existing in the wc
2204 if is_project_dir(arg):
2205 prj = Project(arg, False)
2207 if conf.config['do_package_tracking']:
2209 for pac in prj.pacs_have:
2210 # we cannot create package objects if the dir does not exist
2211 if not pac in prj.pacs_broken:
2212 prjpacs[prj].append(os.path.join(arg, pac))
2214 pacpaths += [arg + '/' + n for n in prj.pacs_have]
2215 elif is_package_dir(arg):
2216 pacpaths.append(arg)
2217 elif os.path.isfile(arg):
2218 pacpaths.append(arg)
2220 msg = '\'%s\' is neither a project or a package directory' % arg
2221 raise oscerr.NoWorkingCopy, msg
2223 # process single packages
2224 lines = getStatus(findpacs(pacpaths), None, opts.verbose, opts.quiet)
2225 # process project dirs
2226 for prj, pacs in prjpacs.iteritems():
2227 lines += getStatus(findpacs(pacs), prj, opts.verbose, opts.quiet)
2229 print '\n'.join(lines)
2232 def do_add(self, subcmd, opts, *args):
2233 """${cmd_name}: Mark files to be added upon the next commit
2236 osc add FILE [FILE...]
2240 raise oscerr.WrongArgs('Missing argument.\n\n' \
2241 + self.get_cmd_help('add'))
2243 filenames = parseargs(args)
2247 def do_mkpac(self, subcmd, opts, *args):
2248 """${cmd_name}: Create a new package under version control
2251 osc mkpac new_package
2254 if not conf.config['do_package_tracking']:
2255 print >>sys.stderr, "to use this feature you have to enable \'do_package_tracking\' " \
2256 "in the [general] section in the configuration file"
2260 raise oscerr.WrongArgs('Wrong number of arguments.')
2262 createPackageDir(args[0])
2264 @cmdln.option('-r', '--recursive', action='store_true',
2265 help='If CWD is a project dir then scan all package dirs as well')
2267 def do_addremove(self, subcmd, opts, *args):
2268 """${cmd_name}: Adds new files, removes disappeared files
2270 Adds all files new in the local copy, and removes all disappeared files.
2272 ARG, if specified, is a package working copy.
2278 args = parseargs(args)
2280 for arg in arg_list:
2281 if is_project_dir(arg) and conf.config['do_package_tracking']:
2282 prj = Project(arg, False)
2283 for pac in prj.pacs_unvers:
2284 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2285 if os.path.isdir(pac_dir):
2286 addFiles([pac_dir], prj)
2287 for pac in prj.pacs_broken:
2288 if prj.get_state(pac) != 'D':
2289 prj.set_state(pac, 'D')
2290 print statfrmt('D', getTransActPath(os.path.join(prj.dir, pac)))
2292 for pac in prj.pacs_have:
2293 state = prj.get_state(pac)
2294 if state != None and state != 'D':
2295 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2296 args.append(pac_dir)
2298 prj.write_packages()
2299 elif is_project_dir(arg):
2300 print >>sys.stderr, 'osc: addremove is not supported in a project dir unless ' \
2301 '\'do_package_tracking\' is enabled in the configuration file'
2304 pacs = findpacs(args)
2306 p.todo = p.filenamelist + p.filenamelist_unvers
2308 for filename in p.todo:
2309 if os.path.isdir(filename):
2311 # ignore foo.rXX, foo.mine for files which are in 'C' state
2312 if os.path.splitext(filename)[0] in p.in_conflict:
2314 state = p.status(filename)
2317 # TODO: should ignore typical backup files suffix ~ or .orig
2319 print statfrmt('A', getTransActPath(os.path.join(p.dir, filename)))
2321 p.put_on_deletelist(filename)
2322 p.write_deletelist()
2323 os.unlink(os.path.join(p.storedir, filename))
2324 print statfrmt('D', getTransActPath(os.path.join(p.dir, filename)))
2329 @cmdln.alias('checkin')
2330 @cmdln.option('-m', '--message', metavar='TEXT',
2331 help='specify log message TEXT')
2332 @cmdln.option('-F', '--file', metavar='FILE',
2333 help='read log message from FILE')
2334 @cmdln.option('-f', '--force', default=False, action="store_true",
2335 help='force commit - do not tests a file list')
2336 def do_commit(self, subcmd, opts, *args):
2337 """${cmd_name}: Upload content to the repository server
2339 Upload content which is changed in your working copy, to the repository
2342 Optionally checks the state of a working copy, if found a file with
2343 unknown state, it requests an user input:
2344 * skip - don't change anything, just move to another file
2345 * remove - remove a file from dir
2346 * edit file list - edit filelist using EDITOR
2347 * commit - don't check anything and commit package
2348 * abort - abort commit - this is default value
2349 This can be supressed by check_filelist config item, or -f/--force
2350 command line option.
2353 osc ci # current dir
2355 osc ci file1 file2 ...
2361 args = parseargs(args)
2368 msg = open(opts.file).read()
2370 sys.exit('could not open file \'%s\'.' % opts.file)
2373 for arg in arg_list:
2374 if conf.config['do_package_tracking'] and is_project_dir(arg):
2375 Project(arg).commit(msg=msg)
2377 msg = edit_message()
2380 pacs = findpacs(args)
2382 if conf.config['check_filelist'] and not opts.force:
2383 check_filelist_before_commit(pacs)
2386 template = store_read_file(os.path.abspath('.'), '_commit_msg')
2387 # open editor for commit message
2388 # but first, produce status and diff to append to the template
2392 changed = getStatus([pac], quiet=True)
2395 diffs += ['\nDiff for working copy: %s' % pac.dir]
2396 diffs += make_diff(pac, 0)
2397 lines.extend(get_commit_message_template(pac))
2398 if template == None:
2399 template='\n'.join(lines)
2400 # if footer is empty, there is nothing to commit, and no edit needed.
2402 msg = edit_message(footer='\n'.join(footer), template=template)
2405 store_write_string(os.path.abspath('.'), '_commit_msg', msg)
2407 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2409 if conf.config['do_package_tracking'] and len(pacs) > 0:
2413 # it is possible to commit packages from different projects at the same
2414 # time: iterate over all pacs and put each pac to the right project in the dict
2416 path = os.path.normpath(os.path.join(pac.dir, os.pardir))
2417 if is_project_dir(path):
2418 pac_path = os.path.basename(os.path.normpath(pac.absdir))
2419 prj_paths.setdefault(path, []).append(pac_path)
2420 files[pac_path] = pac.todo
2422 single_paths.append(pac.dir)
2423 for prj, packages in prj_paths.iteritems():
2424 Project(prj).commit(tuple(packages), msg, files)
2425 for pac in single_paths:
2426 Package(pac).commit(msg)
2431 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2433 @cmdln.option('-r', '--revision', metavar='REV',
2434 help='update to specified revision (this option will be ignored '
2435 'if you are going to update the complete project or more than '
2437 @cmdln.option('-u', '--unexpand-link', action='store_true',
2438 help='if a package is an expanded link, update to the raw _link file')
2439 @cmdln.option('-e', '--expand-link', action='store_true',
2440 help='if a package is a link, update to the expanded sources')
2441 @cmdln.option('-s', '--source-service-files', action='store_true',
2442 help='Use server side generated sources instead of local generation.' )
2443 @cmdln.option('-l', '--limit-size', metavar='limit_size',
2444 help='Skip all files with a given size')
2446 def do_update(self, subcmd, opts, *args):
2447 """${cmd_name}: Update a working copy
2452 If the current working directory is a package, update it.
2453 If the directory is a project directory, update all contained
2454 packages, AND check out newly added packages.
2456 To update only checked out packages, without checking out new
2457 ones, you might want to use "osc up *" from within the project
2461 Update the packages specified by the path argument(s)
2463 When --expand-link is used with source link packages, the expanded
2464 sources will be checked out. Without this option, the _link file and
2465 patches will be checked out. The option --unexpand-link can be used to
2466 switch back to the "raw" source with a _link file plus patch(es).
2472 if (opts.expand_link and opts.unexpand_link) \
2473 or (opts.expand_link and opts.revision) \
2474 or (opts.unexpand_link and opts.revision):
2475 raise oscerr.WrongOptions('Sorry, the options --expand-link, --unexpand-link and '
2476 '--revision are mutually exclusive.')
2478 if opts.source_service_files: service_files = True
2479 else: service_files = False
2481 args = parseargs(args)
2484 for arg in arg_list:
2485 if is_project_dir(arg):
2486 prj = Project(arg, progress_obj=self.download_progress)
2488 if conf.config['do_package_tracking']:
2489 prj.update(expand_link=opts.expand_link,
2490 unexpand_link=opts.unexpand_link)
2493 # if not tracking package, and 'update' is run inside a project dir,
2494 # it should do the following:
2495 # (a) update all packages
2496 args += prj.pacs_have
2497 # (b) fetch new packages
2498 prj.checkout_missing_pacs(expand_link = not opts.unexpand_link)
2500 print_request_list(prj.apiurl, prj.name)
2503 pacs = findpacs(args, progress_obj=self.download_progress)
2505 if opts.revision and len(args) == 1:
2506 rev, dummy = parseRevisionOption(opts.revision)
2507 if not checkRevision(pacs[0].prjname, pacs[0].name, rev, pacs[0].apiurl):
2508 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2515 print 'Updating %s' % p.name
2517 # FIXME: ugly workaround for #399247
2518 if opts.expand_link or opts.unexpand_link:
2519 if [ i for i in p.filenamelist+p.filenamelist_unvers if p.status(i) != ' ' and p.status(i) != '?']:
2520 print >>sys.stderr, 'osc: cannot expand/unexpand because your working ' \
2521 'copy has local modifications.\nPlease revert/commit them ' \
2526 if opts.expand_link and p.islink() and not p.isexpanded():
2527 if p.haslinkerror():
2529 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev)
2531 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev, linkrev="base")
2534 rev = p.linkinfo.xsrcmd5
2535 print 'Expanding to rev', rev
2536 elif opts.unexpand_link and p.islink() and p.isexpanded():
2537 print 'Unexpanding to rev', p.linkinfo.lsrcmd5
2538 rev = p.linkinfo.lsrcmd5
2539 elif p.islink() and p.isexpanded():
2540 rev = p.latest_rev()
2542 p.update(rev, service_files, opts.limit_size)
2543 if opts.unexpand_link:
2546 print_request_list(p.apiurl, p.prjname, p.name)
2549 @cmdln.option('-f', '--force', action='store_true',
2550 help='forces removal of entire package and its files')
2553 @cmdln.alias('remove')
2554 def do_delete(self, subcmd, opts, *args):
2555 """${cmd_name}: Mark files or package directories to be deleted upon the next 'checkin'
2558 cd .../PROJECT/PACKAGE
2559 osc delete FILE [...]
2561 osc delete PACKAGE [...]
2563 This command works on check out copies. Use "rdelete" for working on server
2564 side only. This is needed for removing the entire project.
2566 As a safety measure, projects must be empty (i.e., you need to delete all
2569 If you are sure that you want to remove a package and all
2570 its files use \'--force\' switch. Sometimes this also works without --force.
2576 raise oscerr.WrongArgs('Missing argument.\n\n' \
2577 + self.get_cmd_help('delete'))
2579 args = parseargs(args)
2580 # check if args contains a package which was removed by
2581 # a non-osc command and mark it with the 'D'-state
2584 if not os.path.exists(i):
2585 prj_dir, pac_dir = getPrjPacPaths(i)
2586 if is_project_dir(prj_dir):
2587 prj = Project(prj_dir, False)
2588 if i in prj.pacs_broken:
2589 if prj.get_state(i) != 'A':
2590 prj.set_state(pac_dir, 'D')
2592 prj.del_package_node(i)
2593 print statfrmt('D', getTransActPath(i))
2595 prj.write_packages()
2596 pacs = findpacs(args)
2600 prj_dir, pac_dir = getPrjPacPaths(p.absdir)
2601 if is_project_dir(prj_dir):
2602 if conf.config['do_package_tracking']:
2603 prj = Project(prj_dir, False)
2604 prj.delPackage(p, opts.force)
2606 print "WARNING: package tracking is disabled, operation skipped !"
2608 pathn = getTransActPath(p.dir)
2609 for filename in p.todo:
2610 ret, state = p.delete_file(filename, opts.force)
2612 print statfrmt('D', os.path.join(pathn, filename))
2615 sys.exit('\'%s\' is not under version control' % filename)
2616 elif state in ['A', 'M'] and not opts.force:
2617 sys.exit('\'%s\' has local modifications (use --force to remove this file)' % filename)
2620 def do_resolved(self, subcmd, opts, *args):
2621 """${cmd_name}: Remove 'conflicted' state on working copy files
2623 If an upstream change can't be merged automatically, a file is put into
2624 in 'conflicted' ('C') state. Within the file, conflicts are marked with
2625 special <<<<<<< as well as ======== and >>>>>>> lines.
2627 After manually resolving all conflicting parts, use this command to
2628 remove the 'conflicted' state.
2630 Note: this subcommand does not semantically resolve conflicts or
2631 remove conflict markers; it merely removes the conflict-related
2632 artifact files and allows PATH to be committed again.
2635 osc resolved FILE [FILE...]
2640 raise oscerr.WrongArgs('Missing argument.\n\n' \
2641 + self.get_cmd_help('resolved'))
2643 args = parseargs(args)
2644 pacs = findpacs(args)
2647 for filename in p.todo:
2648 print 'Resolved conflicted state of "%s"' % filename
2649 p.clear_from_conflictlist(filename)
2652 @cmdln.alias('platforms')
2653 def do_repositories(self, subcmd, opts, *args):
2654 """${cmd_name}: Shows available repositories
2658 Shows all available repositories/build targets
2660 2. osc repositories <project>
2661 Shows the configured repositories/build targets of a project
2669 print '\n'.join(get_repositories_of_project(conf.config['apiurl'], project))
2671 print '\n'.join(get_repositories(conf.config['apiurl']))
2675 def do_results_meta(self, subcmd, opts, *args):
2676 print "Command results_meta is obsolete. Please use: osc results --xml"
2680 @cmdln.option('-l', '--last-build', action='store_true',
2681 help='show last build results (succeeded/failed/unknown)')
2682 @cmdln.option('-r', '--repo', action='append', default = [],
2683 help='Show results only for specified repo(s)')
2684 @cmdln.option('-a', '--arch', action='append', default = [],
2685 help='Show results only for specified architecture(s)')
2686 @cmdln.option('', '--xml', action='store_true',
2687 help='generate output in XML (former results_meta)')
2688 def do_rresults(self, subcmd, opts, *args):
2689 print "Command rresults is obsolete. Running 'osc results' instead"
2690 self.do_results('results', opts, *args)
2694 @cmdln.option('-f', '--force', action='store_true', default=False,
2695 help="Don't ask and delete files")
2696 def do_rremove(self, subcmd, opts, project, package, *files):
2697 """${cmd_name}: Remove source files from selected package
2704 if not '/' in project:
2705 raise oscerr.WrongArgs("Missing operand, type osc help rremove for help")
2708 project, package = project.split('/')
2712 resp = raw_input("rm: remove source file `%s' from `%s/%s'? (yY|nN) " % (file, project, package))
2713 if resp not in ('y', 'Y'):
2716 delete_files(conf.config['apiurl'], project, package, (file, ))
2717 except urllib2.HTTPError, e:
2719 print >>sys.stderr, e
2721 if e.code in [ 400, 403, 404, 500 ]:
2722 if '<summary>' in body:
2723 msg = body.split('<summary>')[1]
2724 msg = msg.split('</summary>')[0]
2725 print >>sys.stderr, msg
2730 @cmdln.option('-l', '--last-build', action='store_true',
2731 help='show last build results (succeeded/failed/unknown)')
2732 @cmdln.option('-r', '--repo', action='append', default = [],
2733 help='Show results only for specified repo(s)')
2734 @cmdln.option('-a', '--arch', action='append', default = [],
2735 help='Show results only for specified architecture(s)')
2736 @cmdln.option('', '--xml', action='store_true', default=False,
2737 help='generate output in XML (former results_meta)')
2738 @cmdln.option('', '--csv', action='store_true', default=False,
2739 help='generate output in CSV format')
2740 @cmdln.option('', '--format', default='%(repository)s|%(arch)s|%(state)s|%(dirty)s|%(code)s|%(details)s',
2741 help='format string for csv output')
2742 def do_results(self, subcmd, opts, *args):
2743 """${cmd_name}: Shows the build results of a package
2746 osc results (inside working copy)
2747 osc results remote_project remote_package
2752 args = slash_split(args)
2754 apiurl = conf.config['apiurl']
2757 if is_project_dir(wd):
2761 opts.hide_legend = None
2762 opts.name_filter = None
2763 opts.status_filter = None
2764 opts.vertical = None
2765 self.do_prjresults('prjresults', opts, *args)
2768 project = store_read_project(wd)
2769 package = store_read_package(wd)
2770 apiurl = store_read_apiurl(wd)
2772 raise oscerr.WrongArgs('Too few arguments (required none or two)')
2774 raise oscerr.WrongArgs('Too many arguments (required none or two)')
2779 if opts.xml and opts.csv:
2780 raise oscerr.WrongOptions("--xml and --csv are mutual exclusive")
2783 func = show_results_meta
2787 return format_results(get_package_results(*args), opts.format)
2794 print delim.join(func(apiurl, project, package, opts.last_build, opts.repo, opts.arch))
2796 # WARNING: this function is also called by do_results. You need to set a default there
2797 # as well when adding a new option!
2798 @cmdln.option('-q', '--hide-legend', action='store_true',
2799 help='hide the legend')
2800 @cmdln.option('-c', '--csv', action='store_true',
2802 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2803 help='show only packages with buildstatus STATUS (see legend)')
2804 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2805 help='show only packages whose names match EXPR')
2806 @cmdln.option('-a', '--arch', metavar='ARCH',
2807 help='show results only for specified architecture(s)')
2808 @cmdln.option('-r', '--repo', metavar='REPO',
2809 help='show results only for specified repo(s)')
2810 @cmdln.option('-V', '--vertical', action='store_true',
2811 help='list packages vertically instead horizontally')
2813 def do_prjresults(self, subcmd, opts, *args):
2814 """${cmd_name}: Shows project-wide build results
2817 osc prjresults (inside working copy)
2818 osc prjresults PROJECT
2824 apiurl = conf.config['apiurl']
2828 raise oscerr.WrongArgs('Wrong number of arguments.')
2831 project = store_read_project(wd)
2832 apiurl = store_read_apiurl(wd)
2834 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))
2837 @cmdln.option('-q', '--hide-legend', action='store_true',
2838 help='hide the legend')
2839 @cmdln.option('-c', '--csv', action='store_true',
2841 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2842 help='show only packages with buildstatus STATUS (see legend)')
2843 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2844 help='show only packages whose names match EXPR')
2847 def do_rprjresults(self, subcmd, opts, *args):
2848 print "Command rprjresults is obsolete. Please use 'osc prjresults'"
2852 @cmdln.option('-s', '--start', metavar='START',
2853 help='get log starting from the offset')
2854 def do_buildlog(self, subcmd, opts, *args):
2855 """${cmd_name}: Shows the build log of a package
2857 Shows the log file of the build of a package. Can be used to follow the
2858 log while it is being written.
2859 Needs to be called from within a package directory.
2861 The arguments REPOSITORY and ARCH are the first two columns in the 'osc
2862 results' output. If the buildlog url is used buildlog command has the
2863 same behavior as remotebuildlog.
2865 ${cmd_usage} [REPOSITORY ARCH | BUILDLOGURL]
2869 repository = arch = None
2871 if len(args) == 1 and args[0].startswith('http'):
2872 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2875 package = store_read_package(wd)
2876 project = store_read_project(wd)
2877 apiurl = store_read_apiurl(wd)
2881 offset = int(opts.start)
2883 if not repository or not arch:
2887 repository = args[0]
2890 print_buildlog(apiurl, project, package, repository, arch, offset)
2893 def print_repos(self):
2896 if is_package_dir(wd):
2899 elif is_project_dir(wd):
2904 print 'Valid arguments for this %s are:' % str
2906 self.do_repos(None, None)
2908 raise oscerr.WrongArgs('Missing arguments')
2911 @cmdln.alias('rbuildlog')
2912 @cmdln.option('-s', '--start', metavar='START',
2913 help='get log starting from the offset')
2914 def do_remotebuildlog(self, subcmd, opts, *args):
2915 """${cmd_name}: Shows the build log of a package
2917 Shows the log file of the build of a package. Can be used to follow the
2918 log while it is being written.
2921 osc remotebuildlog project package repository arch
2923 osc remotebuildlog project/package/repository/arch
2925 osc remotebuildlog buildlogurl
2928 if len(args) == 1 and args[0].startswith('http'):
2929 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2931 args = slash_split(args)
2932 apiurl = conf.config['apiurl']
2934 raise oscerr.WrongArgs('Too few arguments.')
2936 raise oscerr.WrongArgs('Too many arguments.')
2938 project, package, repository, arch = args
2942 offset = int(opts.start)
2944 print_buildlog(apiurl, project, package, repository, arch, offset)
2947 @cmdln.option('-s', '--start', metavar='START',
2948 help='get log starting from offset')
2949 def do_localbuildlog(self, subcmd, opts, *args):
2950 """${cmd_name}: Shows the build log of a local buildchroot
2953 osc lbl [REPOSITORY ARCH]
2954 osc lbl # show log of newest last local build
2958 if conf.config['build-type']:
2959 # FIXME: raise Exception instead
2960 print >>sys.stderr, 'Not implemented for VMs'
2964 package = store_read_package('.')
2966 files = glob.glob(os.path.join(os.getcwd(), store, "_buildinfo-*"))
2969 raise oscerr.WrongArgs('No buildconfig found, please specify repo and arch manually.')
2973 if os.stat(f).st_mtime > os.stat(cfg).st_mtime:
2975 root = ET.parse(cfg).getroot()
2976 project = root.get("project")
2977 repo = root.get("repository")
2978 arch = root.find("arch").text
2979 elif len(args) == 2:
2980 project = store_read_project('.')
2981 package = store_read_package('.')
2985 if is_package_dir(os.curdir):
2987 raise oscerr.WrongArgs('Wrong number of arguments.')
2989 buildroot = os.environ.get('OSC_BUILD_ROOT', conf.config['build-root'])
2990 buildroot = buildroot % {'project': project, 'package': package,
2991 'repo': repo, 'arch': arch}
2994 offset = int(opts.start)
2995 logfile = os.path.join(buildroot, '.build.log')
2996 if not os.path.isfile(logfile):
2997 raise oscerr.OscIOError(None, 'logfile \'%s\' does not exist' % logfile)
2998 f = open(logfile, 'r')
3000 data = f.read(BUFSIZE)
3002 sys.stdout.write(data)
3003 data = f.read(BUFSIZE)
3007 def do_triggerreason(self, subcmd, opts, *args):
3008 """${cmd_name}: Show reason why a package got triggered to build
3010 The server decides when a package needs to get rebuild, this command
3011 shows the detailed reason for a package. A brief reason is also stored
3012 in the jobhistory, which can be accessed via "osc jobhistory".
3014 Trigger reasons might be:
3015 - new build (never build yet or rebuild manually forced)
3016 - source change (eg. on updating sources)
3017 - meta change (packages which are used for building have changed)
3018 - rebuild count sync (In case that it is configured to sync release numbers)
3020 usage in package or project directory:
3021 osc reason REPOSITORY ARCH
3022 osc reason PROJECT PACKAGE REPOSITORY ARCH
3027 args = slash_split(args)
3028 project = package = repository = arch = None
3033 if len(args) == 2: # 2
3034 if is_package_dir('.'):
3035 package = store_read_package(wd)
3037 raise oscerr.WrongArgs('package is not specified.')
3038 project = store_read_project(wd)
3039 apiurl = store_read_apiurl(wd)
3040 repository = args[0]
3042 elif len(args) == 4:
3043 apiurl = conf.config['apiurl']
3046 repository = args[2]
3049 raise oscerr.WrongArgs('Too many arguments.')
3051 print apiurl, project, package, repository, arch
3052 xml = show_package_trigger_reason(apiurl, project, package, repository, arch)
3053 root = ET.fromstring(xml)
3054 reason = root.find('explain').text
3056 if reason == "meta change":
3057 print "changed keys:"
3058 for package in root.findall('packagechange'):
3059 print " ", package.get('change'), package.get('key')
3062 # FIXME: the new osc syntax should allow to specify multiple packages
3063 # FIXME: the command should optionally use buildinfo data to show all dependencies
3064 @cmdln.alias('whatdependson')
3065 def do_dependson(self, subcmd, opts, *args):
3066 """${cmd_name}: Show the build dependencies
3068 The command dependson and whatdependson can be used to find out what
3069 will be triggered when a certain package changes.
3070 This is no guarantee, since the new build might have changed dependencies.
3072 dependson shows the build dependencies inside of a project, valid for a
3073 given repository and architecture.
3074 NOTE: to see all binary packages, which can trigger a build you need to
3075 refer the buildinfo, since this command shows only the dependencies
3076 inside of a project.
3078 The arguments REPOSITORY and ARCH can be taken from the first two columns
3079 of the 'osc repos' output.
3081 usage in package or project directory:
3082 osc dependson REPOSITORY ARCH
3083 osc whatdependson REPOSITORY ARCH
3086 osc dependson PROJECT [PACKAGE] REPOSITORY ARCH
3087 osc whatdependson PROJECT [PACKAGE] REPOSITORY ARCH
3092 args = slash_split(args)
3093 project = packages = repository = arch = reverse = None