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 if sys.platform[:3] != 'win':
420 editor = os.getenv('EDITOR', default='vim')
422 editor = os.getenv('EDITOR', default='notepad')
423 subprocess.call('%s %s' % (editor, project_dir + "/" + patchinfo + "/_patchinfo"), shell=True)
426 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
427 help='affect only a given attribute')
428 @cmdln.option('--attribute-defaults', action='store_true',
429 help='include defined attribute defaults')
430 @cmdln.option('--attribute-project', action='store_true',
431 help='include project values, if missing in packages ')
432 @cmdln.option('-F', '--file', metavar='FILE',
433 help='read metadata from FILE, instead of opening an editor. '
434 '\'-\' denotes standard input. ')
435 @cmdln.option('-e', '--edit', action='store_true',
436 help='edit metadata')
437 @cmdln.option('-c', '--create', action='store_true',
438 help='create attribute without values')
439 @cmdln.option('-s', '--set', metavar='ATTRIBUTE_VALUES',
440 help='set attribute values')
441 @cmdln.option('--delete', action='store_true',
442 help='delete a pattern or attribute')
443 def do_meta(self, subcmd, opts, *args):
444 """${cmd_name}: Show meta information, or edit it
446 Show or edit build service metadata of type <prj|pkg|prjconf|user|pattern>.
448 This command displays metadata on buildservice objects like projects,
449 packages, or users. The type of metadata is specified by the word after
450 "meta", like e.g. "meta prj".
452 prj denotes metadata of a buildservice project.
453 prjconf denotes the (build) configuration of a project.
454 pkg denotes metadata of a buildservice package.
455 user denotes the metadata of a user.
456 pattern denotes installation patterns defined for a project.
458 To list patterns, use 'osc meta pattern PRJ'. An additional argument
459 will be the pattern file to view or edit.
461 With the --edit switch, the metadata can be edited. Per default, osc
462 opens the program specified by the environmental variable EDITOR with a
463 temporary file. Alternatively, content to be saved can be supplied via
464 the --file switch. If the argument is '-', input is taken from stdin:
465 osc meta prjconf home:user | sed ... | osc meta prjconf home:user -F -
467 When trying to edit a non-existing resource, it is created implicitly.
473 osc meta pkg PRJ PKG -e
474 osc meta attribute PRJ [PKG [SUBPACKAGE]] [--attribute ATTRIBUTE] [--create|--delete|--set [value_list]]
477 osc meta <prj|pkg|prjconf|user|pattern|attribute> ARGS...
478 osc meta <prj|pkg|prjconf|user|pattern|attribute> -e|--edit ARGS...
479 osc meta <prj|pkg|prjconf|user|pattern|attribute> -F|--file ARGS...
480 osc meta pattern --delete PRJ PATTERN
484 args = slash_split(args)
486 if not args or args[0] not in metatypes.keys():
487 raise oscerr.WrongArgs('Unknown meta type. Choose one of %s.' \
488 % ', '.join(metatypes))
494 min_args, max_args = 0, 2
495 elif cmd in ['pattern']:
496 min_args, max_args = 1, 2
497 elif cmd in ['attribute']:
498 min_args, max_args = 1, 3
499 elif cmd in ['prj', 'prjconf']:
500 min_args, max_args = 0, 1
502 min_args, max_args = 1, 1
504 if len(args) < min_args:
505 raise oscerr.WrongArgs('Too few arguments.')
506 if len(args) > max_args:
507 raise oscerr.WrongArgs('Too many arguments.')
511 if cmd in ['pkg', 'prj', 'prjconf' ]:
513 project = store_read_project(os.curdir)
519 package = store_read_package(os.curdir)
523 elif cmd == 'attribute':
529 if opts.attribute_project:
530 raise oscerr.WrongOptions('--attribute-project works only when also a package is given')
535 attributepath.append('source')
536 attributepath.append(project)
538 attributepath.append(package)
540 attributepath.append(subpackage)
541 attributepath.append('_attribute')
544 elif cmd == 'pattern':
550 # enforce pattern argument if needed
551 if opts.edit or opts.file:
552 raise oscerr.WrongArgs('A pattern file argument is required.')
555 if not opts.edit and not opts.file and not opts.delete and not opts.create and not opts.set:
557 sys.stdout.write(''.join(show_project_meta(conf.config['apiurl'], project)))
559 sys.stdout.write(''.join(show_package_meta(conf.config['apiurl'], project, package)))
560 elif cmd == 'attribute':
561 sys.stdout.write(''.join(show_attribute_meta(conf.config['apiurl'], project, package, subpackage, opts.attribute, opts.attribute_defaults, opts.attribute_project)))
562 elif cmd == 'prjconf':
563 sys.stdout.write(''.join(show_project_conf(conf.config['apiurl'], project)))
565 r = get_user_meta(conf.config['apiurl'], user)
567 sys.stdout.write(''.join(r))
568 elif cmd == 'pattern':
570 r = show_pattern_meta(conf.config['apiurl'], project, pattern)
572 sys.stdout.write(''.join(r))
574 r = show_pattern_metalist(conf.config['apiurl'], project)
576 sys.stdout.write('\n'.join(r) + '\n')
579 if opts.edit and not opts.file:
581 edit_meta(metatype='prj',
583 path_args=quote_plus(project),
586 'user': conf.config['user']}))
588 edit_meta(metatype='pkg',
590 path_args=(quote_plus(project), quote_plus(package)),
593 'user': conf.config['user']}))
594 elif cmd == 'prjconf':
595 edit_meta(metatype='prjconf',
597 path_args=quote_plus(project),
600 edit_meta(metatype='user',
602 path_args=(quote_plus(user)),
603 template_args=({'user': user}))
604 elif cmd == 'pattern':
605 edit_meta(metatype='pattern',
607 path_args=(project, pattern),
610 # create attribute entry
611 if (opts.create or opts.set) and cmd == 'attribute':
612 if not opts.attribute:
613 raise oscerr.WrongOptions('no attribute given to create')
616 opts.set = opts.set.replace('&', '&').replace('<', '<').replace('>', '>')
617 for i in opts.set.split(','):
618 values += '<value>%s</value>' % i
619 aname = opts.attribute.split(":")
620 d = '<attributes><attribute namespace=\'%s\' name=\'%s\' >%s</attribute></attributes>' % (aname[0], aname[1], values)
621 url = makeurl(conf.config['apiurl'], attributepath)
622 for data in streamfile(url, http_POST, data=d):
623 sys.stdout.write(data)
632 f = open(opts.file).read()
634 sys.exit('could not open file \'%s\'.' % opts.file)
637 edit_meta(metatype='prj',
640 path_args=quote_plus(project))
642 edit_meta(metatype='pkg',
645 path_args=(quote_plus(project), quote_plus(package)))
646 elif cmd == 'prjconf':
647 edit_meta(metatype='prjconf',
650 path_args=quote_plus(project))
652 edit_meta(metatype='user',
655 path_args=(quote_plus(user)))
656 elif cmd == 'pattern':
657 edit_meta(metatype='pattern',
660 path_args=(project, pattern))
665 path = metatypes[cmd]['path']
667 path = path % (project, pattern)
668 u = makeurl(conf.config['apiurl'], [path])
670 elif cmd == 'attribute':
671 if not opts.attribute:
672 raise oscerr.WrongOptions('no attribute given to create')
673 attributepath.append(opts.attribute)
674 u = makeurl(conf.config['apiurl'], attributepath)
675 for data in streamfile(u, http_DELETE):
676 sys.stdout.write(data)
678 raise oscerr.WrongOptions('The --delete switch is only for pattern metadata or attributes.')
681 @cmdln.option('-m', '--message', metavar='TEXT',
682 help='specify message TEXT')
683 @cmdln.option('-r', '--revision', metavar='REV',
684 help='for "create", specify a certain source revision ID (the md5 sum)')
685 @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
686 help='Superseding another request by this one')
687 @cmdln.option('--nodevelproject', action='store_true',
688 help='do not follow a defined devel project ' \
689 '(primary project where a package is developed)')
690 @cmdln.option('--cleanup', action='store_true',
691 help='remove package if submission gets accepted (default for home:<id>:branch projects)')
692 @cmdln.option('--no-cleanup', action='store_true',
693 help='never remove source package on accept, but update its content')
694 @cmdln.option('--no-update', action='store_true',
695 help='never touch source package on accept (will break source links)')
696 @cmdln.option('-d', '--diff', action='store_true',
697 help='show diff only instead of creating the actual request')
698 @cmdln.option('--yes', action='store_true',
699 help='proceed without asking.')
701 @cmdln.alias("submitreq")
702 @cmdln.alias("submitpac")
703 def do_submitrequest(self, subcmd, opts, *args):
704 """${cmd_name}: Create request to submit source into another Project
706 [See http://en.opensuse.org/Build_Service/Collaboration for information
709 See the "request" command for showing and modifing existing requests.
712 osc submitreq [OPTIONS]
713 osc submitreq [OPTIONS] DESTPRJ [DESTPKG]
714 osc submitreq [OPTIONS] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
718 src_update = conf.config['submitrequest_on_accept_action'] or None
719 # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
721 src_update = "cleanup"
722 elif opts.no_cleanup:
723 src_update = "update"
725 src_update = "noupdate"
727 args = slash_split(args)
729 # remove this block later again
730 oldcmds = ['create', 'list', 'log', 'show', 'decline', 'accept', 'delete', 'revoke']
731 if args and args[0] in oldcmds:
732 print "************************************************************************"
733 print "* WARNING: It looks that you are using this command with a *"
734 print "* deprecated syntax. *"
735 print "* Please run \"osc sr --help\" and \"osc rq --help\" *"
736 print "* to see the new syntax. *"
737 print "************************************************************************"
738 if args[0] == 'create':
744 raise oscerr.WrongArgs('Too many arguments.')
746 if len(args) > 0 and len(args) <= 2 and is_project_dir(os.getcwd()):
747 sys.exit('osc submitrequest from project directory is only working without target specs and for source linked files\n')
749 apiurl = conf.config['apiurl']
751 if len(args) == 0 and is_project_dir(os.getcwd()):
753 # submit requests for multiple packages are currently handled via multiple requests
754 # They could be also one request with multiple actions, but that avoids to accepts parts of it.
755 project = store_read_project(os.curdir)
756 apiurl = store_read_apiurl(os.curdir)
762 # loop via all packages for checking their state
763 for p in meta_get_packagelist(apiurl, project):
764 if p.startswith("_patchinfo:"):
767 # get _link info from server, that knows about the local state ...
768 u = makeurl(apiurl, ['source', project, p])
770 root = ET.parse(f).getroot()
771 linkinfo = root.find('linkinfo')
773 print "Package ", p, " is not a source link."
774 sys.exit("This is currently not supported.")
775 if linkinfo.get('error'):
776 print "Package ", p, " is a broken source link."
777 sys.exit("Please fix this first")
778 t = linkinfo.get('project')
780 if len(root.findall('entry')) > 1: # This is not really correct, but should work mostly
781 # Real fix is to ask the api if sources are modificated
782 # but there is no such call yet.
783 targetprojects.append(t)
785 print "Submitting package ", p
787 print " Skipping package ", p
789 print "Skipping package ", p, " since it is a source link pointing inside the project."
793 print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
794 print "\nEverything fine? Can we create the requests ? [y/n]"
795 if sys.stdin.read(1) != "y":
796 print >>sys.stderr, 'Aborted...'
797 raise oscerr.UserAbort()
799 # loop via all packages to do the action
801 result = create_submit_request(apiurl, project, p)
804 sys.exit("submit request creation failed")
805 sr_ids.append(result)
807 # create submit requests for all found patchinfos
811 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
814 for t in targetprojects:
815 s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \
816 (project, p, t, p, options_block)
820 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
821 (actionxml, cgi.escape(opts.message or ""))
822 u = makeurl(apiurl, ['request'], query='cmd=create')
823 f = http_POST(u, data=xml)
825 root = ET.parse(f).getroot()
826 sr_ids.append(root.get('id'))
828 print "Requests created: ",
831 sys.exit('Successfull finished')
834 # try using the working copy at hand
835 p = findpacs(os.curdir)[0]
836 src_project = p.prjname
839 if len(args) == 0 and p.islink():
840 dst_project = p.linkinfo.project
841 dst_package = p.linkinfo.package
843 dst_project = args[0]
845 dst_package = args[1]
847 dst_package = src_package
849 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
850 'Please provide it the target via commandline arguments.' % p.name)
852 modified = [i for i in p.filenamelist if p.status(i) != ' ' and p.status(i) != '?']
853 if len(modified) > 0:
854 print 'Your working copy has local modifications.'
855 repl = raw_input('Proceed without committing the local changes? (y|N) ')
857 raise oscerr.UserAbort()
859 # get the arguments from the commandline
860 src_project, src_package, dst_project = args[0:3]
862 dst_package = args[3]
864 dst_package = src_package
866 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
867 + self.get_cmd_help('request'))
869 if not opts.nodevelproject:
872 devloc = show_develproject(apiurl, dst_project, dst_package)
873 except urllib2.HTTPError:
874 print >>sys.stderr, """\
875 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
876 % (dst_project, dst_package)
880 dst_project != devloc and \
881 src_project != devloc:
883 A different project, %s, is defined as the place where development
884 of the package %s primarily takes place.
885 Please submit there instead, or use --nodevelproject to force direct submission.""" \
886 % (devloc, dst_package)
891 if opts.diff or not opts.message:
893 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
894 rdiff += server_diff(apiurl,
895 dst_project, dst_package, opts.revision,
896 src_project, src_package, None, True)
902 reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit')
903 user = conf.get_apiurl_usr(apiurl)
904 myreqs = [ i for i in reqs if i.state.who == user ]
907 print 'You already created the following submit request: %s.' % \
908 ', '.join([str(i.reqid) for i in myreqs ])
909 repl = raw_input('Supersede the old requests? (y/n/c) ')
910 if repl.lower() == 'c':
911 print >>sys.stderr, 'Aborting'
912 raise oscerr.UserAbort()
917 changes_re = re.compile(r'^--- .*\.changes ')
918 for line in rdiff.split('\n'):
919 if line.startswith('--- '):
920 if changes_re.match(line):
925 difflines.append(line)
926 opts.message = edit_message(footer=rdiff, template='\n'.join(parse_diff_for_commit_message('\n'.join(difflines))))
928 result = create_submit_request(apiurl,
929 src_project, src_package,
930 dst_project, dst_package,
931 opts.message, orev=opts.revision, src_update=src_update)
932 if repl.lower() == 'y':
934 change_request_state(apiurl, str(req.reqid), 'superseded',
935 'superseded by %s' % result, result)
938 r = change_request_state(conf.config['apiurl'],
939 opts.supersede, 'superseded', opts.message or '', result)
941 print 'created request id', result
944 @cmdln.option('-m', '--message', metavar='TEXT',
945 help='specify message TEXT')
947 @cmdln.alias("deletereq")
948 def do_deleterequest(self, subcmd, opts, *args):
949 """${cmd_name}: Create request to delete a package or project
953 osc deletereq [-m TEXT] PROJECT [PACKAGE]
957 args = slash_split(args)
960 raise oscerr.WrongArgs('Please specify at least a project.')
962 raise oscerr.WrongArgs('Too many arguments.')
964 apiurl = conf.config['apiurl']
972 opts.message = edit_message()
974 result = create_delete_request(apiurl, project, package, opts.message)
978 @cmdln.option('-m', '--message', metavar='TEXT',
979 help='specify message TEXT')
981 @cmdln.alias("changedevelreq")
982 def do_changedevelrequest(self, subcmd, opts, *args):
983 """${cmd_name}: Create request to change the devel package definition.
985 [See http://en.opensuse.org/Build_Service/Collaboration for information
988 See the "request" command for showing and modifing existing requests.
990 osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
994 raise oscerr.WrongArgs('Too many arguments.')
996 if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
998 devel_project = store_read_project(wd)
999 devel_package = package = store_read_package(wd)
1000 apiurl = store_read_apiurl(wd)
1001 project = conf.config['getpac_default_project']
1004 raise oscerr.WrongArgs('Too few arguments.')
1006 apiurl = conf.config['apiurl']
1008 devel_project = args[2]
1011 devel_package = package
1013 devel_package = args[3]
1015 if not opts.message:
1017 footer=textwrap.TextWrapper(width = 66).fill(
1018 'please explain why you like to change the devel project of %s/%s to %s/%s'
1019 % (project,package,devel_project,devel_package))
1020 opts.message = edit_message(footer)
1022 result = create_change_devel_request(apiurl,
1023 devel_project, devel_package,
1029 @cmdln.option('-d', '--diff', action='store_true',
1030 help='generate a diff')
1031 @cmdln.option('-u', '--unified', action='store_true',
1032 help='output the diff in the unified diff format')
1033 @cmdln.option('-m', '--message', metavar='TEXT',
1034 help='specify message TEXT')
1035 @cmdln.option('-t', '--type', metavar='TYPE',
1036 help='limit to requests which contain a given action type (submit/delete/change_devel)')
1037 @cmdln.option('-a', '--all', action='store_true',
1038 help='all states. Same as\'-s all\'')
1039 @cmdln.option('-s', '--state', default='', # default is 'all' if no args given, 'new' otherwise
1040 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]')
1041 @cmdln.option('-D', '--days', metavar='DAYS',
1042 help='only list requests in state "new" or changed in the last DAYS. [default=%(request_list_days)s]')
1043 @cmdln.option('-U', '--user', metavar='USER',
1044 help='same as -M, but for the specified USER')
1045 @cmdln.option('-b', '--brief', action='store_true', default=False,
1046 help='print output in list view as list subcommand')
1047 @cmdln.option('-M', '--mine', action='store_true',
1048 help='only show requests created by yourself')
1049 @cmdln.option('-B', '--bugowner', action='store_true',
1050 help='also show requests about packages where I am bugowner')
1051 @cmdln.option('-i', '--interactive', action='store_true',
1052 help='interactive review of request')
1053 @cmdln.option('--non-interactive', action='store_true',
1054 help='non-interactive review of request')
1055 @cmdln.option('--exclude-target-project', action='append',
1056 help='exclude target project from request list')
1057 @cmdln.option('--involved-projects', action='store_true',
1058 help='show all requests for project/packages where USER is involved')
1060 @cmdln.alias("review")
1061 def do_request(self, subcmd, opts, *args):
1062 """${cmd_name}: Show and modify requests
1064 [See http://en.opensuse.org/Build_Service/Collaboration for information
1067 This command shows and modifies existing requests. To create new requests
1068 you need to call one of the following:
1071 osc changedevelrequest
1072 To send low level requests to the buildservice API, use:
1075 This command has the following sub commands:
1077 "list" lists open requests attached to a project or package or person.
1078 Uses the project/package of the current directory if none of
1079 -M, -U USER, project/package are given.
1081 "log" will show the history of the given ID
1083 "show" will show the request itself, and generate a diff for review, if
1084 used with the --diff option. The keyword show can be omitted if the ID is numeric.
1086 "decline" will change the request state to "declined" and append a
1087 message that you specify with the --message option.
1089 "wipe" will permanently delete a request.
1091 "revoke" will set the request state to "revoked" and append a
1092 message that you specify with the --message option.
1094 "accept" will change the request state to "accepted" and will trigger
1095 the actual submit process. That would normally be a server-side copy of
1096 the source package to the target package.
1098 "checkout" will checkout the request's source package. This only works for "submit" requests.
1101 osc request list [-M] [-U USER] [-s state] [-D DAYS] [-t type] [-B] [PRJ [PKG]]
1103 osc request [show] [-d] [-b] ID
1104 osc request accept [-m TEXT] ID
1105 osc request decline [-m TEXT] ID
1106 osc request revoke [-m TEXT] ID
1108 osc request checkout/co ID
1109 osc review accept [-m TEXT] ID
1110 osc review decline [-m TEXT] ID
1114 args = slash_split(args)
1116 if opts.all and opts.state:
1117 raise oscerr.WrongOptions('Sorry, the options \'--all\' and \'--state\' ' \
1118 'are mutually exclusive.')
1119 if opts.mine and opts.user:
1120 raise oscerr.WrongOptions('Sorry, the options \'--user\' and \'--mine\' ' \
1121 'are mutually exclusive.')
1122 if opts.interactive and opts.non_interactive:
1123 raise oscerr.WrongOptions('Sorry, the options \'--interactive\' and ' \
1124 '\'--non-interactive\' are mutually exclusive')
1129 if opts.state == '':
1132 if opts.state == '':
1135 cmds = ['list', 'log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co', 'help']
1136 if not args or args[0] not in cmds:
1137 raise oscerr.WrongArgs('Unknown request action %s. Choose one of %s.' \
1138 % (args[0],', '.join(cmds)))
1144 return self.do_help(['help', 'request'])
1147 min_args, max_args = 1, 1
1148 elif cmd in ['list']:
1149 min_args, max_args = 0, 2
1151 min_args, max_args = 1, 1
1152 if len(args) < min_args:
1153 raise oscerr.WrongArgs('Too few arguments.')
1154 if len(args) > max_args:
1155 raise oscerr.WrongArgs('Too many arguments.')
1157 apiurl = conf.config['apiurl']
1164 elif not opts.mine and not opts.user:
1166 project = store_read_project(os.curdir)
1167 apiurl = store_read_apiurl(os.curdir)
1168 package = store_read_package(os.curdir)
1169 except oscerr.NoWorkingCopy:
1174 elif cmd in ['log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co']:
1179 states = ('new', 'accepted', 'revoked', 'declined')
1180 state_list = opts.state.split(',')
1181 if opts.state == 'all':
1182 state_list = ['all']
1184 for s in state_list:
1186 raise oscerr.WrongArgs('Unknown state \'%s\', try one of %s' % (s, ','.join(states)))
1189 who = conf.get_apiurl_usr(apiurl)
1193 state_list = ['all']
1195 ## FIXME -B not implemented!
1197 if (self.options.debug):
1198 print 'list: option --bugowner ignored: not impl.'
1200 if opts.involved_projects:
1201 who = who or conf.get_apiurl_usr(apiurl)
1202 results = get_user_projpkgs_request_list(apiurl, who, req_state=state_list,
1203 req_type=opts.type, exclude_projects=opts.exclude_target_project or [])
1205 results = get_request_list(apiurl, project, package, who,
1206 state_list, opts.type, opts.exclude_target_project or [])
1207 results.sort(reverse=True)
1209 days = opts.days or conf.config['request_list_days']
1216 since = time.strftime('%Y-%m-%dT%H:%M:%S',time.localtime(time.time()-days*24*3600))
1219 ## bs has received 2009-09-20 a new xquery compare() function
1220 ## which allows us to limit the list inside of get_request_list
1221 ## That would be much faster for coolo. But counting the remainder
1222 ## would not be possible with current xquery implementation.
1223 ## Workaround: fetch all, and filter on client side.
1225 ## FIXME: date filtering should become implemented on server side
1226 for result in results:
1227 if days == 0 or result.state.when > since or result.state.name == 'new':
1228 print result.list_view()
1232 print "There are %d requests older than %s days.\n" % (skipped, days)
1235 for l in get_request_log(conf.config['apiurl'], reqid):
1240 r = get_request(conf.config['apiurl'], reqid)
1243 elif (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive:
1244 return request_interactive_review(conf.config['apiurl'], r)
1247 # fixme: will inevitably fail if the given target doesn't exist
1248 if opts.diff and r.actions[0].type != 'submit':
1249 raise oscerr.WrongOptions('\'--diff\' is not possible for request type: \'%s\'' % r.actions[0].type)
1252 print server_diff(conf.config['apiurl'],
1253 r.actions[0].dst_project, r.actions[0].dst_package, None,
1254 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, True)
1255 except urllib2.HTTPError, e:
1257 e.osc_msg = 'Diff not possible'
1259 # backward compatiblity: only a recent api/backend supports the missingok parameter
1261 print server_diff(conf.config['apiurl'],
1262 r.actions[0].dst_project, r.actions[0].dst_package, None,
1263 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified, False)
1264 except urllib2.HTTPError, e:
1265 e.osc_msg = 'Diff not possible'
1269 elif cmd == 'checkout' or cmd == 'co':
1270 r = get_request(conf.config['apiurl'], reqid)
1271 submits = [ i for i in r.actions if i.type == 'submit' ]
1272 if not len(submits):
1273 raise oscerr.WrongArgs('\'checkout\' only works for \'submit\' requests')
1274 checkout_package(conf.config['apiurl'], submits[0].src_project, submits[0].src_package, \
1275 submits[0].src_rev, expand_link=True, prj_dir=submits[0].src_project)
1278 if not opts.message:
1279 opts.message = edit_message()
1280 state_map = {'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked'}
1281 # Change review state only
1282 if subcmd == 'review':
1283 if cmd in ['accept', 'decline']:
1284 r = change_review_state(conf.config['apiurl'],
1285 reqid, state_map[cmd], conf.config['user'], '', opts.message or '')
1287 # Change state of entire request
1288 elif cmd in ['accept', 'decline', 'wipe', 'revoke']:
1289 r = change_request_state(conf.config['apiurl'],
1290 reqid, state_map[cmd], opts.message or '')
1293 # editmeta and its aliases are all depracated
1294 @cmdln.alias("editprj")
1295 @cmdln.alias("createprj")
1296 @cmdln.alias("editpac")
1297 @cmdln.alias("createpac")
1298 @cmdln.alias("edituser")
1299 @cmdln.alias("usermeta")
1301 def do_editmeta(self, subcmd, opts, *args):
1304 Obsolete command to edit metadata. Use 'meta' now.
1306 See the help output of 'meta'.
1310 print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
1311 print >>sys.stderr, 'See \'osc help meta\'.'
1312 #self.do_help([None, 'meta'])
1316 @cmdln.option('-r', '--revision', metavar='rev',
1317 help='use the specified revision.')
1318 @cmdln.option('-u', '--unset', action='store_true',
1319 help='remove revision in link, it will point always to latest revision')
1320 def do_setlinkrev(self, subcmd, opts, *args):
1321 """${cmd_name}: Updates a revision number in a source link.
1323 This command adds or updates a specified revision number in a source link.
1324 The current revision of the source is used, if no revision number is specified.
1328 osc setlinkrev PROJECT [PACKAGE]
1332 args = slash_split(args)
1333 apiurl = conf.config['apiurl']
1336 p = findpacs(os.curdir)[0]
1341 sys.exit('Local directory is no checked out source link package, aborting')
1342 elif len(args) == 2:
1345 elif len(args) == 1:
1348 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1349 + self.get_cmd_help('setlinkrev'))
1352 packages = [ package ]
1354 packages = meta_get_packagelist(apiurl, project)
1357 print "setting revision for package", p
1361 rev, dummy = parseRevisionOption(opts.revision)
1362 set_link_rev(apiurl, project, p, rev)
1365 def do_linktobranch(self, subcmd, opts, *args):
1366 """${cmd_name}: Convert a package containing a classic link with patch to a branch
1368 This command tells the server to convert a _link with or without a project.diff
1369 to a branch. This is a full copy with a _link file pointing to the branched place.
1372 osc linktobranch # can be used in checked out package
1373 osc linktobranch PROJECT PACKAGE
1377 args = slash_split(args)
1380 project = store_read_project(wd)
1381 package = store_read_package(wd)
1382 apiurl = store_read_apiurl(wd)
1383 update_local_dir = True
1385 raise oscerr.WrongArgs('Too few arguments (required none or two)')
1387 raise oscerr.WrongArgs('Too many arguments (required none or two)')
1389 apiurl = conf.config['apiurl']
1392 update_local_dir = False
1395 link_to_branch(apiurl, project, package)
1396 if update_local_dir:
1398 pac.update(rev=pac.latest_rev())
1401 @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'],
1402 help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.')
1403 @cmdln.option('-c', '--current', action='store_true',
1404 help='link fixed against current revision.')
1405 @cmdln.option('-r', '--revision', metavar='rev',
1406 help='link the specified revision.')
1407 @cmdln.option('-f', '--force', action='store_true',
1408 help='overwrite an existing link file if it is there.')
1409 @cmdln.option('-d', '--disable-publish', action='store_true',
1410 help='disable publishing of the linked package')
1411 def do_linkpac(self, subcmd, opts, *args):
1412 """${cmd_name}: "Link" a package to another package
1414 A linked package is a clone of another package, but plus local
1415 modifications. It can be cross-project.
1417 The DESTPAC name is optional; the source packages' name will be used if
1420 Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
1422 To add a patch, add the patch as file and add it to the _link file.
1423 You can also specify text which will be inserted at the top of the spec file.
1425 See the examples in the _link file.
1428 osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1432 args = slash_split(args)
1434 if not args or len(args) < 3:
1435 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1436 + self.get_cmd_help('linkpac'))
1438 rev, dummy = parseRevisionOption(opts.revision)
1440 src_project = args[0]
1441 src_package = args[1]
1442 dst_project = args[2]
1444 dst_package = args[3]
1446 dst_package = src_package
1448 if src_project == dst_project and src_package == dst_package:
1449 raise oscerr.WrongArgs('Error: source and destination are the same.')
1451 if src_project == dst_project and not opts.cicount:
1452 # in this case, the user usually wants to build different spec
1453 # files from the same source
1454 opts.cicount = "copy"
1457 rev = show_upstream_rev(conf.config['apiurl'], src_project, src_package)
1459 if rev and not checkRevision(src_project, src_package, rev):
1460 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
1463 link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish)
1465 @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]',
1466 help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]')
1467 @cmdln.option('-d', '--disable-publish', action='store_true',
1468 help='disable publishing of the aggregated package')
1469 def do_aggregatepac(self, subcmd, opts, *args):
1470 """${cmd_name}: "Aggregate" a package to another package
1472 Aggregation of a package means that the build results (binaries) of a
1473 package are basically copied into another project.
1474 This can be used to make packages available from building that are
1475 needed in a project but available only in a different project. Note
1476 that this is done at the expense of disk space. See
1477 http://en.opensuse.org/Build_Service/Tips_and_Tricks#_link_and__aggregate
1478 for more information.
1480 The DESTPAC name is optional; the source packages' name will be used if
1484 osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1488 args = slash_split(args)
1490 if not args or len(args) < 3:
1491 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1492 + self.get_cmd_help('aggregatepac'))
1494 src_project = args[0]
1495 src_package = args[1]
1496 dst_project = args[2]
1498 dst_package = args[3]
1500 dst_package = src_package
1502 if src_project == dst_project and src_package == dst_package:
1503 raise oscerr.WrongArgs('Error: source and destination are the same.')
1507 for pair in opts.map_repo.split(','):
1508 src_tgt = pair.split('=')
1509 if len(src_tgt) != 2:
1510 raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo)
1511 repo_map[src_tgt[0]] = src_tgt[1]
1513 aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish)
1516 @cmdln.option('-c', '--client-side-copy', action='store_true',
1517 help='do a (slower) client-side copy')
1518 @cmdln.option('-k', '--keep-maintainers', action='store_true',
1519 help='keep original maintainers. Default is remove all and replace with the one calling the script.')
1520 @cmdln.option('-d', '--keep-develproject', action='store_true',
1521 help='keep develproject tag in the package metadata')
1522 @cmdln.option('-r', '--revision', metavar='rev',
1523 help='link the specified revision.')
1524 @cmdln.option('-t', '--to-apiurl', metavar='URL',
1525 help='URL of destination api server. Default is the source api server.')
1526 @cmdln.option('-m', '--message', metavar='TEXT',
1527 help='specify message TEXT')
1528 @cmdln.option('-e', '--expand', action='store_true',
1529 help='if the source package is a link then copy the expanded version of the link')
1530 def do_copypac(self, subcmd, opts, *args):
1531 """${cmd_name}: Copy a package
1533 A way to copy package to somewhere else.
1535 It can be done across buildservice instances, if the -t option is used.
1536 In that case, a client-side copy is implied.
1538 Using --client-side-copy always involves downloading all files, and
1539 uploading them to the target.
1541 The DESTPAC name is optional; the source packages' name will be used if
1545 osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1549 args = slash_split(args)
1551 if not args or len(args) < 3:
1552 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1553 + self.get_cmd_help('copypac'))
1555 src_project = args[0]
1556 src_package = args[1]
1557 dst_project = args[2]
1559 dst_package = args[3]
1561 dst_package = src_package
1563 src_apiurl = conf.config['apiurl']
1565 dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl)
1567 dst_apiurl = src_apiurl
1569 if src_project == dst_project and \
1570 src_package == dst_package and \
1571 src_apiurl == dst_apiurl:
1572 raise oscerr.WrongArgs('Source and destination are the same.')
1574 if src_apiurl != dst_apiurl:
1575 opts.client_side_copy = True
1577 rev, dummy = parseRevisionOption(opts.revision)
1580 comment = opts.message
1583 rev = show_upstream_rev(src_apiurl, src_project, src_package)
1584 comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev )
1586 r = copy_pac(src_apiurl, src_project, src_package,
1587 dst_apiurl, dst_project, dst_package,
1588 client_side_copy=opts.client_side_copy,
1589 keep_maintainers=opts.keep_maintainers,
1590 keep_develproject=opts.keep_develproject,
1597 @cmdln.option('-c', '--checkout', action='store_true',
1598 help='Checkout branched package afterwards ' \
1599 '(\'osc bco\' is a shorthand for this option)' )
1600 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
1601 help='Use this attribute to find affected packages (default is OBS:Maintained)')
1602 @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE',
1603 help='Use this attribute to find update projects (default is OBS:UpdateProject) ')
1604 def do_mbranch(self, subcmd, opts, *args):
1605 """${cmd_name}: Multiple branch of a package
1607 [See http://en.opensuse.org/Build_Service/Concepts/Maintenance for information
1610 This command is used for creating multiple links of defined version of a package
1611 in one project. This is esp. used for maintenance updates.
1613 The branched package will live in
1614 home:USERNAME:branches:ATTRIBUTE:PACKAGE
1615 if nothing else specified.
1618 osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ]
1621 args = slash_split(args)
1624 maintained_attribute = conf.config['maintained_attribute']
1625 maintained_update_project_attribute = conf.config['maintained_update_project_attribute']
1627 if not len(args) or len(args) > 2:
1628 raise oscerr.WrongArgs('Wrong number of arguments.')
1634 r = attribute_branch_pkg(conf.config['apiurl'], maintained_attribute, maintained_update_project_attribute, \
1638 print >>sys.stderr, 'ERROR: Attribute branch call came not back with a project.'
1641 print "Project " + r + " created."
1644 init_project_dir(conf.config['apiurl'], r, r)
1645 print statfrmt('A', r)
1648 for package in meta_get_packagelist(conf.config['apiurl'], r):
1650 checkout_package(conf.config['apiurl'], r, package, expand_link = True, prj_dir = r)
1652 print >>sys.stderr, 'Error while checkout package:\n', package
1654 if conf.config['verbose']:
1655 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1658 @cmdln.alias('branchco')
1660 @cmdln.alias('getpac')
1661 @cmdln.option('--nodevelproject', action='store_true',
1662 help='do not follow a defined devel project ' \
1663 '(primary project where a package is developed)')
1664 @cmdln.option('-c', '--checkout', action='store_true',
1665 help='Checkout branched package afterwards ' \
1666 '(\'osc bco\' is a shorthand for this option)' )
1667 @cmdln.option('-r', '--revision', metavar='rev',
1668 help='branch against a specific revision')
1669 @cmdln.option('-m', '--message', metavar='TEXT',
1670 help='specify message TEXT')
1671 def do_branch(self, subcmd, opts, *args):
1672 """${cmd_name}: Branch a package
1674 [See http://en.opensuse.org/Build_Service/Collaboration for information
1677 Create a source link from a package of an existing project to a new
1678 subproject of the requesters home project (home:branches:)
1680 The branched package will live in
1681 home:USERNAME:branches:PROJECT/PACKAGE
1682 if nothing else specified.
1684 With getpac or bco, the branched package will come from
1685 %(getpac_default_project)s
1686 if nothing else specified.
1690 osc branch SOURCEPROJECT SOURCEPACKAGE
1691 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT
1692 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE
1693 osc getpac SOURCEPACKAGE
1698 if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': opts.checkout = True
1699 args = slash_split(args)
1700 tproject = tpackage = None
1702 if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1:
1703 print >>sys.stderr, 'defaulting to %s/%s' % (conf.config['getpac_default_project'], args[0])
1704 # python has no args.unshift ???
1705 args = [ conf.config['getpac_default_project'] , args[0] ]
1707 if len(args) == 0 and is_package_dir('.'):
1708 args = (store_read_project('.'), store_read_package('.'))
1710 if len(args) < 2 or len(args) > 4:
1711 raise oscerr.WrongArgs('Wrong number of arguments.')
1713 expected = 'home:%s:branches:%s' % (conf.config['user'], args[0])
1715 expected = tproject = args[2]
1719 if not opts.message:
1720 footer='please specify the purpose of your branch'
1721 template='This package was branched from %s in order to ...\n' % args[0]
1722 opts.message = edit_message(footer, template)
1724 exists, targetprj, targetpkg, srcprj, srcpkg = \
1725 branch_pkg(conf.config['apiurl'], args[0], args[1],
1726 nodevelproject=opts.nodevelproject, rev=opts.revision,
1727 target_project=tproject, target_package=tpackage,
1728 return_existing=opts.checkout, msg=opts.message or '')
1730 print >>sys.stderr, 'Using existing branch project: %s' % targetprj
1733 if not exists and (srcprj is not None and srcprj != args[0] or \
1734 srcprj is None and targetprj != expected):
1735 devloc = srcprj or targetprj
1736 if not srcprj and 'branches:' in targetprj:
1737 devloc = targetprj.split('branches:')[1]
1738 print '\nNote: The branch has been created of a different project,\n' \
1740 ' which is the primary location of where development for\n' \
1741 ' that package takes place.\n' \
1742 ' That\'s also where you would normally make changes against.\n' \
1743 ' A direct branch of the specified package can be forced\n' \
1744 ' with the --nodevelproject option.\n' % devloc
1746 package = tpackage or args[1]
1748 checkout_package(conf.config['apiurl'], targetprj, package,
1749 expand_link=True, prj_dir=targetprj)
1750 if conf.config['verbose']:
1751 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1754 if conf.get_configParser().get('general', 'apiurl') != conf.config['apiurl']:
1755 apiopt = '-A %s ' % conf.config['apiurl']
1756 print 'A working copy of the branched package can be checked out with:\n\n' \
1758 % (apiopt, targetprj, package)
1759 print_request_list(conf.config['apiurl'], args[0], args[1])
1761 print_request_list(conf.config['apiurl'], devloc, args[1])
1765 @cmdln.option('-f', '--force', action='store_true',
1766 help='deletes a package or an empty project')
1767 def do_rdelete(self, subcmd, opts, *args):
1768 """${cmd_name}: Delete a project or packages on the server.
1770 As a safety measure, project must be empty (i.e., you need to delete all
1771 packages first). If you are sure that you want to remove this project and all
1772 its packages use \'--force\' switch.
1775 osc rdelete -f PROJECT
1776 osc rdelete PROJECT PACKAGE [PACKAGE ...]
1781 args = slash_split(args)
1783 raise oscerr.WrongArgs('Missing argument.')
1789 # careful: if pkg is an empty string, the package delete request results
1790 # into a project delete request - which works recursively...
1792 delete_package(conf.config['apiurl'], prj, pkg)
1793 elif len(meta_get_packagelist(conf.config['apiurl'], prj)) >= 1 and not opts.force:
1794 print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
1795 'If you are sure that you want to remove this project and all its ' \
1796 'packages use the \'--force\' switch'
1799 delete_project(conf.config['apiurl'], prj)
1802 def do_deletepac(self, subcmd, opts, *args):
1803 print """${cmd_name} is obsolete !
1806 osc delete for checked out packages or projects
1808 osc rdelete for server side operations."""
1813 @cmdln.option('-f', '--force', action='store_true',
1814 help='deletes a project and its packages')
1815 def do_deleteprj(self, subcmd, opts, project):
1816 """${cmd_name} is obsolete !
1823 @cmdln.alias('metafromspec')
1824 @cmdln.option('', '--specfile', metavar='FILE',
1825 help='Path to specfile. (if you pass more than working copy this option is ignored)')
1826 def do_updatepacmetafromspec(self, subcmd, opts, *args):
1827 """${cmd_name}: Update package meta information from a specfile
1829 ARG, if specified, is a package working copy.
1835 args = parseargs(args)
1836 if opts.specfile and len(args) == 1:
1837 specfile = opts.specfile
1840 pacs = findpacs(args)
1842 p.read_meta_from_spec(specfile)
1843 p.update_package_meta()
1847 @cmdln.option('-c', '--change', metavar='rev',
1848 help='the change made by revision rev (like -r rev-1:rev).'
1849 'If rev is negative this is like -r rev:rev-1.')
1850 @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
1851 help='If rev1 is specified it will compare your working copy against '
1852 'the revision (rev1) on the server. '
1853 'If rev1 and rev2 are specified it will compare rev1 against rev2 '
1854 '(NOTE: changes in your working copy are ignored in this case)')
1855 @cmdln.option('-p', '--plain', action='store_true',
1856 help='output the diff in plain (not unified) diff format')
1857 @cmdln.option('--missingok', action='store_true',
1858 help='do not fail if the source or target project/package does not exist on the server')
1859 def do_diff(self, subcmd, opts, *args):
1860 """${cmd_name}: Generates a diff
1862 Generates a diff, comparing local changes against the repository
1865 ARG, specified, is a filename to include in the diff.
1871 args = parseargs(args)
1872 pacs = findpacs(args)
1876 rev = int(opts.change)
1886 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1889 rev1, rev2 = parseRevisionOption(opts.revision)
1893 diff += ''.join(make_diff(pac, rev1))
1895 diff += server_diff(pac.apiurl, pac.prjname, pac.name, rev1,
1896 pac.prjname, pac.name, rev2, not opts.plain, opts.missingok)
1901 @cmdln.option('--oldprj', metavar='OLDPRJ',
1902 help='project to compare against'
1903 ' (deprecated, use 3 argument form)')
1904 @cmdln.option('--oldpkg', metavar='OLDPKG',
1905 help='package to compare against'
1906 ' (deprecated, use 3 argument form)')
1907 @cmdln.option('-r', '--revision', metavar='N[:M]',
1908 help='revision id, where N = old revision and M = new revision')
1909 @cmdln.option('-p', '--plain', action='store_true',
1910 help='output the diff in plain (not unified) diff format')
1911 @cmdln.option('-c', '--change', metavar='rev',
1912 help='the change made by revision rev (like -r rev-1:rev). '
1913 'If rev is negative this is like -r rev:rev-1.')
1914 @cmdln.option('--missingok', action='store_true',
1915 help='do not fail if the source or target project/package does not exist on the server')
1916 def do_rdiff(self, subcmd, opts, *args):
1917 """${cmd_name}: Server-side "pretty" diff of two packages
1919 Compares two packages (three or four arguments) or shows the
1920 changes of a specified revision of a package (two arguments)
1922 If no revision is specified the latest revision is used.
1924 Note that this command doesn't return a normal diff (which could be
1925 applied as patch), but a "pretty" diff, which also compares the content
1930 osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC]
1931 osc ${cmd_name} PROJECT PACKAGE
1935 args = slash_split(args)
1946 new_project = args[0]
1947 new_package = args[1]
1949 old_project = opts.oldprj
1951 old_package = opts.oldpkg
1952 elif len(args) == 3 or len(args) == 4:
1953 if opts.oldprj or opts.oldpkg:
1954 raise oscerr.WrongArgs('--oldpkg and --oldprj are only valid with two arguments')
1955 old_project = args[0]
1956 new_package = old_package = args[1]
1957 new_project = args[2]
1959 new_package = args[3]
1961 raise oscerr.WrongArgs('Wrong number of arguments')
1966 rev = int(opts.change)
1976 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1980 rev1, rev2 = parseRevisionOption(opts.revision)
1982 rdiff = server_diff(conf.config['apiurl'],
1983 old_project, old_package, rev1,
1984 new_project, new_package, rev2, not opts.plain, opts.missingok)
1989 def do_install(self, subcmd, opts, *args):
1990 """${cmd_name}: install a package after build via zypper in -r
1992 Not implemented yet. Use osc repourls,
1993 select the url you best like (standard),
1994 chop off after the last /, this should work with zypper.
2001 args = slash_split(args)
2002 args = expand_proj_pack(args)
2005 ## if there is only one argument, and it ends in .ymp
2006 ## then fetch it, Parse XML to get the first
2007 ## metapackage.group.repositories.repository.url
2008 ## and construct zypper cmd's for all
2009 ## metapackage.group.software.item.name
2011 ## if args[0] is already an url, the use it as is.
2013 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])
2014 print self.do_install.__doc__
2015 print "Example: \n" + cmd
2018 def do_repourls(self, subcmd, opts, *args):
2019 """${cmd_name}: Shows URLs of .repo files
2021 Shows URLs on which to access the project .repos files (yum-style
2022 metadata) on download.opensuse.org.
2025 osc repourls [PROJECT]
2030 apiurl = conf.config['apiurl']
2034 elif len(args) == 0:
2035 project = store_read_project('.')
2036 apiurl = store_read_apiurl('.')
2038 raise oscerr.WrongArgs('Wrong number of arguments')
2040 # XXX: API should somehow tell that
2041 url_tmpl = 'http://download.opensuse.org/repositories/%s/%s/%s.repo'
2042 repos = get_repositories_of_project(apiurl, project)
2044 print url_tmpl % (project.replace(':', ':/'), repo, project)
2047 @cmdln.option('-r', '--revision', metavar='rev',
2048 help='checkout the specified revision. '
2049 'NOTE: if you checkout the complete project '
2050 'this option is ignored!')
2051 @cmdln.option('-e', '--expand-link', action='store_true',
2052 help='if a package is a link, check out the expanded '
2053 'sources (no-op, since this became the default)')
2054 @cmdln.option('-u', '--unexpand-link', action='store_true',
2055 help='if a package is a link, check out the _link file ' \
2056 'instead of the expanded sources')
2057 @cmdln.option('-c', '--current-dir', action='store_true',
2058 help='place PACKAGE folder in the current directory' \
2059 'instead of a PROJECT/PACKAGE directory')
2060 @cmdln.option('-s', '--source-service-files', action='store_true',
2061 help='server side generated files of source services' \
2062 'gets downloaded as well' )
2063 @cmdln.option('-l', '--limit-size', metavar='limit_size',
2064 help='Skip all files with a given size')
2066 def do_checkout(self, subcmd, opts, *args):
2067 """${cmd_name}: Check out content from the repository
2069 Check out content from the repository server, creating a local working
2072 When checking out a single package, the option --revision can be used
2073 to specify a revision of the package to be checked out.
2075 When a package is a source link, then it will be checked out in
2076 expanded form. If --unexpand-link option is used, the checkout will
2077 instead produce the raw _link file plus patches.
2080 osc co PROJECT [PACKAGE] [FILE]
2081 osc co PROJECT # entire project
2082 osc co PROJECT PACKAGE # a package
2083 osc co PROJECT PACKAGE FILE # single file -> to current dir
2085 while inside a project directory:
2086 osc co PACKAGE # check out PACKAGE from project
2091 if opts.unexpand_link:
2095 if opts.source_service_files:
2096 service_files = True
2098 service_files = False
2100 args = slash_split(args)
2101 project = package = filename = None
2102 apiurl = conf.config['apiurl']
2104 project = project_dir = args[0]
2110 if args and len(args) == 1:
2111 localdir = os.getcwd()
2112 if is_project_dir(localdir):
2113 project = store_read_project(localdir)
2114 project_dir = localdir
2116 apiurl = store_read_apiurl(localdir)
2118 rev, dummy = parseRevisionOption(opts.revision)
2122 if rev and rev != "latest" and not checkRevision(project, package, rev):
2123 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2127 get_source_file(apiurl, project, package, filename, revision=rev, progress_obj=self.download_progress)
2130 if opts.current_dir:
2132 checkout_package(apiurl, project, package, rev, expand_link=expand_link, \
2133 prj_dir=project_dir, service_files=service_files, progress_obj=self.download_progress, limit_size=opts.limit_size)
2134 print_request_list(apiurl, project, package)
2138 if sys.platform[:3] == 'win':
2139 prj_dir = prj_dir.replace(':', ';')
2140 if os.path.exists(prj_dir):
2141 sys.exit('osc: project \'%s\' already exists' % project)
2143 # check if the project does exist (show_project_meta will throw an exception)
2144 show_project_meta(apiurl, project)
2146 init_project_dir(apiurl, prj_dir, project)
2147 print statfrmt('A', prj_dir)
2150 for package in meta_get_packagelist(apiurl, project):
2152 checkout_package(apiurl, project, package, expand_link = expand_link, \
2153 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress, limit_size=opts.limit_size)
2154 except oscerr.LinkExpandError, e:
2155 print >>sys.stderr, 'Link cannot be expanded:\n', e
2156 print >>sys.stderr, 'Use "osc repairlink" for fixing merge conflicts:\n'
2157 # check out in unexpanded form at least
2158 checkout_package(apiurl, project, package, expand_link = False, \
2159 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress, limit_size=opts.limit_size)
2160 print_request_list(apiurl, project)
2163 raise oscerr.WrongArgs('Missing argument.\n\n' \
2164 + self.get_cmd_help('checkout'))
2167 @cmdln.option('-q', '--quiet', action='store_true',
2168 help='print as little as possible')
2169 @cmdln.option('-v', '--verbose', action='store_true',
2170 help='print extra information')
2172 def do_status(self, subcmd, opts, *args):
2173 """${cmd_name}: Show status of files in working copy
2175 Show the status of files in a local working copy, indicating whether
2176 files have been changed locally, deleted, added, ...
2178 The first column in the output specifies the status and is one of the
2179 following characters:
2180 ' ' no modifications
2185 '?' item is not under version control
2186 '!' item is missing (removed by non-osc command) or incomplete
2191 osc st file1 file2 ...
2194 osc status [OPTS] [PATH...]
2198 args = parseargs(args)
2200 # storage for single Package() objects
2202 # storage for a project dir ( { prj_instance : [ package objects ] } )
2205 # when 'status' is run inside a project dir, it should
2206 # stat all packages existing in the wc
2207 if is_project_dir(arg):
2208 prj = Project(arg, False)
2210 if conf.config['do_package_tracking']:
2212 for pac in prj.pacs_have:
2213 # we cannot create package objects if the dir does not exist
2214 if not pac in prj.pacs_broken:
2215 prjpacs[prj].append(os.path.join(arg, pac))
2217 pacpaths += [arg + '/' + n for n in prj.pacs_have]
2218 elif is_package_dir(arg):
2219 pacpaths.append(arg)
2220 elif os.path.isfile(arg):
2221 pacpaths.append(arg)
2223 msg = '\'%s\' is neither a project or a package directory' % arg
2224 raise oscerr.NoWorkingCopy, msg
2226 # process single packages
2227 lines = getStatus(findpacs(pacpaths), None, opts.verbose, opts.quiet)
2228 # process project dirs
2229 for prj, pacs in prjpacs.iteritems():
2230 lines += getStatus(findpacs(pacs), prj, opts.verbose, opts.quiet)
2232 print '\n'.join(lines)
2235 def do_add(self, subcmd, opts, *args):
2236 """${cmd_name}: Mark files to be added upon the next commit
2239 osc add FILE [FILE...]
2243 raise oscerr.WrongArgs('Missing argument.\n\n' \
2244 + self.get_cmd_help('add'))
2246 filenames = parseargs(args)
2250 def do_mkpac(self, subcmd, opts, *args):
2251 """${cmd_name}: Create a new package under version control
2254 osc mkpac new_package
2257 if not conf.config['do_package_tracking']:
2258 print >>sys.stderr, "to use this feature you have to enable \'do_package_tracking\' " \
2259 "in the [general] section in the configuration file"
2263 raise oscerr.WrongArgs('Wrong number of arguments.')
2265 createPackageDir(args[0])
2267 @cmdln.option('-r', '--recursive', action='store_true',
2268 help='If CWD is a project dir then scan all package dirs as well')
2270 def do_addremove(self, subcmd, opts, *args):
2271 """${cmd_name}: Adds new files, removes disappeared files
2273 Adds all files new in the local copy, and removes all disappeared files.
2275 ARG, if specified, is a package working copy.
2281 args = parseargs(args)
2283 for arg in arg_list:
2284 if is_project_dir(arg) and conf.config['do_package_tracking']:
2285 prj = Project(arg, False)
2286 for pac in prj.pacs_unvers:
2287 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2288 if os.path.isdir(pac_dir):
2289 addFiles([pac_dir], prj)
2290 for pac in prj.pacs_broken:
2291 if prj.get_state(pac) != 'D':
2292 prj.set_state(pac, 'D')
2293 print statfrmt('D', getTransActPath(os.path.join(prj.dir, pac)))
2295 for pac in prj.pacs_have:
2296 state = prj.get_state(pac)
2297 if state != None and state != 'D':
2298 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2299 args.append(pac_dir)
2301 prj.write_packages()
2302 elif is_project_dir(arg):
2303 print >>sys.stderr, 'osc: addremove is not supported in a project dir unless ' \
2304 '\'do_package_tracking\' is enabled in the configuration file'
2307 pacs = findpacs(args)
2309 p.todo = p.filenamelist + p.filenamelist_unvers
2311 for filename in p.todo:
2312 if os.path.isdir(filename):
2314 # ignore foo.rXX, foo.mine for files which are in 'C' state
2315 if os.path.splitext(filename)[0] in p.in_conflict:
2317 state = p.status(filename)
2320 # TODO: should ignore typical backup files suffix ~ or .orig
2322 print statfrmt('A', getTransActPath(os.path.join(p.dir, filename)))
2324 p.put_on_deletelist(filename)
2325 p.write_deletelist()
2326 os.unlink(os.path.join(p.storedir, filename))
2327 print statfrmt('D', getTransActPath(os.path.join(p.dir, filename)))
2332 @cmdln.alias('checkin')
2333 @cmdln.option('-m', '--message', metavar='TEXT',
2334 help='specify log message TEXT')
2335 @cmdln.option('-F', '--file', metavar='FILE',
2336 help='read log message from FILE')
2337 @cmdln.option('-f', '--force', default=False, action="store_true",
2338 help='force commit - do not tests a file list')
2339 def do_commit(self, subcmd, opts, *args):
2340 """${cmd_name}: Upload content to the repository server
2342 Upload content which is changed in your working copy, to the repository
2345 Optionally checks the state of a working copy, if found a file with
2346 unknown state, it requests an user input:
2347 * skip - don't change anything, just move to another file
2348 * remove - remove a file from dir
2349 * edit file list - edit filelist using EDITOR
2350 * commit - don't check anything and commit package
2351 * abort - abort commit - this is default value
2352 This can be supressed by check_filelist config item, or -f/--force
2353 command line option.
2356 osc ci # current dir
2358 osc ci file1 file2 ...
2364 args = parseargs(args)
2371 msg = open(opts.file).read()
2373 sys.exit('could not open file \'%s\'.' % opts.file)
2376 for arg in arg_list:
2377 if conf.config['do_package_tracking'] and is_project_dir(arg):
2378 Project(arg).commit(msg=msg)
2380 msg = edit_message()
2383 pacs = findpacs(args)
2385 if conf.config['check_filelist'] and not opts.force:
2386 check_filelist_before_commit(pacs)
2389 template = store_read_file(os.path.abspath('.'), '_commit_msg')
2390 # open editor for commit message
2391 # but first, produce status and diff to append to the template
2395 changed = getStatus([pac], quiet=True)
2398 diffs += ['\nDiff for working copy: %s' % pac.dir]
2399 diffs += make_diff(pac, 0)
2400 lines.extend(get_commit_message_template(pac))
2401 if template == None:
2402 template='\n'.join(lines)
2403 # if footer is empty, there is nothing to commit, and no edit needed.
2405 msg = edit_message(footer='\n'.join(footer), template=template)
2408 store_write_string(os.path.abspath('.'), '_commit_msg', msg)
2410 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2412 if conf.config['do_package_tracking'] and len(pacs) > 0:
2416 # it is possible to commit packages from different projects at the same
2417 # time: iterate over all pacs and put each pac to the right project in the dict
2419 path = os.path.normpath(os.path.join(pac.dir, os.pardir))
2420 if is_project_dir(path):
2421 pac_path = os.path.basename(os.path.normpath(pac.absdir))
2422 prj_paths.setdefault(path, []).append(pac_path)
2423 files[pac_path] = pac.todo
2425 single_paths.append(pac.dir)
2426 for prj, packages in prj_paths.iteritems():
2427 Project(prj).commit(tuple(packages), msg, files)
2428 for pac in single_paths:
2429 Package(pac).commit(msg)
2434 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2436 @cmdln.option('-r', '--revision', metavar='REV',
2437 help='update to specified revision (this option will be ignored '
2438 'if you are going to update the complete project or more than '
2440 @cmdln.option('-u', '--unexpand-link', action='store_true',
2441 help='if a package is an expanded link, update to the raw _link file')
2442 @cmdln.option('-e', '--expand-link', action='store_true',
2443 help='if a package is a link, update to the expanded sources')
2444 @cmdln.option('-s', '--source-service-files', action='store_true',
2445 help='Use server side generated sources instead of local generation.' )
2446 @cmdln.option('-l', '--limit-size', metavar='limit_size',
2447 help='Skip all files with a given size')
2449 def do_update(self, subcmd, opts, *args):
2450 """${cmd_name}: Update a working copy
2455 If the current working directory is a package, update it.
2456 If the directory is a project directory, update all contained
2457 packages, AND check out newly added packages.
2459 To update only checked out packages, without checking out new
2460 ones, you might want to use "osc up *" from within the project
2464 Update the packages specified by the path argument(s)
2466 When --expand-link is used with source link packages, the expanded
2467 sources will be checked out. Without this option, the _link file and
2468 patches will be checked out. The option --unexpand-link can be used to
2469 switch back to the "raw" source with a _link file plus patch(es).
2475 if (opts.expand_link and opts.unexpand_link) \
2476 or (opts.expand_link and opts.revision) \
2477 or (opts.unexpand_link and opts.revision):
2478 raise oscerr.WrongOptions('Sorry, the options --expand-link, --unexpand-link and '
2479 '--revision are mutually exclusive.')
2481 if opts.source_service_files: service_files = True
2482 else: service_files = False
2484 args = parseargs(args)
2487 for arg in arg_list:
2488 if is_project_dir(arg):
2489 prj = Project(arg, progress_obj=self.download_progress)
2491 if conf.config['do_package_tracking']:
2492 prj.update(expand_link=opts.expand_link,
2493 unexpand_link=opts.unexpand_link)
2496 # if not tracking package, and 'update' is run inside a project dir,
2497 # it should do the following:
2498 # (a) update all packages
2499 args += prj.pacs_have
2500 # (b) fetch new packages
2501 prj.checkout_missing_pacs(expand_link = not opts.unexpand_link)
2503 print_request_list(prj.apiurl, prj.name)
2506 pacs = findpacs(args, progress_obj=self.download_progress)
2508 if opts.revision and len(args) == 1:
2509 rev, dummy = parseRevisionOption(opts.revision)
2510 if not checkRevision(pacs[0].prjname, pacs[0].name, rev, pacs[0].apiurl):
2511 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2518 print 'Updating %s' % p.name
2520 # FIXME: ugly workaround for #399247
2521 if opts.expand_link or opts.unexpand_link:
2522 if [ i for i in p.filenamelist+p.filenamelist_unvers if p.status(i) != ' ' and p.status(i) != '?']:
2523 print >>sys.stderr, 'osc: cannot expand/unexpand because your working ' \
2524 'copy has local modifications.\nPlease revert/commit them ' \
2529 if opts.expand_link and p.islink() and not p.isexpanded():
2530 if p.haslinkerror():
2532 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev)
2534 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev, linkrev="base")
2537 rev = p.linkinfo.xsrcmd5
2538 print 'Expanding to rev', rev
2539 elif opts.unexpand_link and p.islink() and p.isexpanded():
2540 print 'Unexpanding to rev', p.linkinfo.lsrcmd5
2541 rev = p.linkinfo.lsrcmd5
2542 elif p.islink() and p.isexpanded():
2543 rev = p.latest_rev()
2545 p.update(rev, service_files, opts.limit_size)
2546 if opts.unexpand_link:
2549 print_request_list(p.apiurl, p.prjname, p.name)
2552 @cmdln.option('-f', '--force', action='store_true',
2553 help='forces removal of entire package and its files')
2556 @cmdln.alias('remove')
2557 def do_delete(self, subcmd, opts, *args):
2558 """${cmd_name}: Mark files or package directories to be deleted upon the next 'checkin'
2561 cd .../PROJECT/PACKAGE
2562 osc delete FILE [...]
2564 osc delete PACKAGE [...]
2566 This command works on check out copies. Use "rdelete" for working on server
2567 side only. This is needed for removing the entire project.
2569 As a safety measure, projects must be empty (i.e., you need to delete all
2572 If you are sure that you want to remove a package and all
2573 its files use \'--force\' switch. Sometimes this also works without --force.
2579 raise oscerr.WrongArgs('Missing argument.\n\n' \
2580 + self.get_cmd_help('delete'))
2582 args = parseargs(args)
2583 # check if args contains a package which was removed by
2584 # a non-osc command and mark it with the 'D'-state
2587 if not os.path.exists(i):
2588 prj_dir, pac_dir = getPrjPacPaths(i)
2589 if is_project_dir(prj_dir):
2590 prj = Project(prj_dir, False)
2591 if i in prj.pacs_broken:
2592 if prj.get_state(i) != 'A':
2593 prj.set_state(pac_dir, 'D')
2595 prj.del_package_node(i)
2596 print statfrmt('D', getTransActPath(i))
2598 prj.write_packages()
2599 pacs = findpacs(args)
2603 prj_dir, pac_dir = getPrjPacPaths(p.absdir)
2604 if is_project_dir(prj_dir):
2605 if conf.config['do_package_tracking']:
2606 prj = Project(prj_dir, False)
2607 prj.delPackage(p, opts.force)
2609 print "WARNING: package tracking is disabled, operation skipped !"
2611 pathn = getTransActPath(p.dir)
2612 for filename in p.todo:
2613 ret, state = p.delete_file(filename, opts.force)
2615 print statfrmt('D', os.path.join(pathn, filename))
2618 sys.exit('\'%s\' is not under version control' % filename)
2619 elif state in ['A', 'M'] and not opts.force:
2620 sys.exit('\'%s\' has local modifications (use --force to remove this file)' % filename)
2623 def do_resolved(self, subcmd, opts, *args):
2624 """${cmd_name}: Remove 'conflicted' state on working copy files
2626 If an upstream change can't be merged automatically, a file is put into
2627 in 'conflicted' ('C') state. Within the file, conflicts are marked with
2628 special <<<<<<< as well as ======== and >>>>>>> lines.
2630 After manually resolving all conflicting parts, use this command to
2631 remove the 'conflicted' state.
2633 Note: this subcommand does not semantically resolve conflicts or
2634 remove conflict markers; it merely removes the conflict-related
2635 artifact files and allows PATH to be committed again.
2638 osc resolved FILE [FILE...]
2643 raise oscerr.WrongArgs('Missing argument.\n\n' \
2644 + self.get_cmd_help('resolved'))
2646 args = parseargs(args)
2647 pacs = findpacs(args)
2650 for filename in p.todo:
2651 print 'Resolved conflicted state of "%s"' % filename
2652 p.clear_from_conflictlist(filename)
2655 @cmdln.alias('platforms')
2656 def do_repositories(self, subcmd, opts, *args):
2657 """${cmd_name}: Shows available repositories
2661 Shows all available repositories/build targets
2663 2. osc repositories <project>
2664 Shows the configured repositories/build targets of a project
2672 print '\n'.join(get_repositories_of_project(conf.config['apiurl'], project))
2674 print '\n'.join(get_repositories(conf.config['apiurl']))
2678 def do_results_meta(self, subcmd, opts, *args):
2679 print "Command results_meta is obsolete. Please use: osc results --xml"
2683 @cmdln.option('-l', '--last-build', action='store_true',
2684 help='show last build results (succeeded/failed/unknown)')
2685 @cmdln.option('-r', '--repo', action='append', default = [],
2686 help='Show results only for specified repo(s)')
2687 @cmdln.option('-a', '--arch', action='append', default = [],
2688 help='Show results only for specified architecture(s)')
2689 @cmdln.option('', '--xml', action='store_true',
2690 help='generate output in XML (former results_meta)')
2691 def do_rresults(self, subcmd, opts, *args):
2692 print "Command rresults is obsolete. Running 'osc results' instead"
2693 self.do_results('results', opts, *args)
2697 @cmdln.option('-f', '--force', action='store_true', default=False,
2698 help="Don't ask and delete files")
2699 def do_rremove(self, subcmd, opts, project, package, *files):
2700 """${cmd_name}: Remove source files from selected package
2707 if not '/' in project:
2708 raise oscerr.WrongArgs("Missing operand, type osc help rremove for help")
2711 project, package = project.split('/')
2715 resp = raw_input("rm: remove source file `%s' from `%s/%s'? (yY|nN) " % (file, project, package))
2716 if resp not in ('y', 'Y'):
2719 delete_files(conf.config['apiurl'], project, package, (file, ))
2720 except urllib2.HTTPError, e:
2722 print >>sys.stderr, e
2724 if e.code in [ 400, 403, 404, 500 ]:
2725 if '<summary>' in body:
2726 msg = body.split('<summary>')[1]
2727 msg = msg.split('</summary>')[0]
2728 print >>sys.stderr, msg
2733 @cmdln.option('-l', '--last-build', action='store_true',
2734 help='show last build results (succeeded/failed/unknown)')
2735 @cmdln.option('-r', '--repo', action='append', default = [],
2736 help='Show results only for specified repo(s)')
2737 @cmdln.option('-a', '--arch', action='append', default = [],
2738 help='Show results only for specified architecture(s)')
2739 @cmdln.option('', '--xml', action='store_true', default=False,
2740 help='generate output in XML (former results_meta)')
2741 @cmdln.option('', '--csv', action='store_true', default=False,
2742 help='generate output in CSV format')
2743 @cmdln.option('', '--format', default='%(repository)s|%(arch)s|%(state)s|%(dirty)s|%(code)s|%(details)s',
2744 help='format string for csv output')
2745 def do_results(self, subcmd, opts, *args):
2746 """${cmd_name}: Shows the build results of a package
2749 osc results (inside working copy)
2750 osc results remote_project remote_package
2755 args = slash_split(args)
2757 apiurl = conf.config['apiurl']
2760 if is_project_dir(wd):
2764 opts.hide_legend = None
2765 opts.name_filter = None
2766 opts.status_filter = None
2767 opts.vertical = None
2768 self.do_prjresults('prjresults', opts, *args)
2771 project = store_read_project(wd)
2772 package = store_read_package(wd)
2773 apiurl = store_read_apiurl(wd)
2775 raise oscerr.WrongArgs('Too few arguments (required none or two)')
2777 raise oscerr.WrongArgs('Too many arguments (required none or two)')
2782 if opts.xml and opts.csv:
2783 raise oscerr.WrongOptions("--xml and --csv are mutual exclusive")
2786 func = show_results_meta
2790 return format_results(get_package_results(*args), opts.format)
2797 print delim.join(func(apiurl, project, package, opts.last_build, opts.repo, opts.arch))
2799 # WARNING: this function is also called by do_results. You need to set a default there
2800 # as well when adding a new option!
2801 @cmdln.option('-q', '--hide-legend', action='store_true',
2802 help='hide the legend')
2803 @cmdln.option('-c', '--csv', action='store_true',
2805 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2806 help='show only packages with buildstatus STATUS (see legend)')
2807 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2808 help='show only packages whose names match EXPR')
2809 @cmdln.option('-a', '--arch', metavar='ARCH',
2810 help='show results only for specified architecture(s)')
2811 @cmdln.option('-r', '--repo', metavar='REPO',
2812 help='show results only for specified repo(s)')
2813 @cmdln.option('-V', '--vertical', action='store_true',
2814 help='list packages vertically instead horizontally')
2816 def do_prjresults(self, subcmd, opts, *args):
2817 """${cmd_name}: Shows project-wide build results
2820 osc prjresults (inside working copy)
2821 osc prjresults PROJECT
2827 apiurl = conf.config['apiurl']
2831 raise oscerr.WrongArgs('Wrong number of arguments.')
2834 project = store_read_project(wd)
2835 apiurl = store_read_apiurl(wd)
2837 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))
2840 @cmdln.option('-q', '--hide-legend', action='store_true',
2841 help='hide the legend')
2842 @cmdln.option('-c', '--csv', action='store_true',
2844 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2845 help='show only packages with buildstatus STATUS (see legend)')
2846 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2847 help='show only packages whose names match EXPR')
2850 def do_rprjresults(self, subcmd, opts, *args):
2851 print "Command rprjresults is obsolete. Please use 'osc prjresults'"
2855 @cmdln.option('-s', '--start', metavar='START',
2856 help='get log starting from the offset')
2857 def do_buildlog(self, subcmd, opts, *args):
2858 """${cmd_name}: Shows the build log of a package
2860 Shows the log file of the build of a package. Can be used to follow the
2861 log while it is being written.
2862 Needs to be called from within a package directory.
2864 The arguments REPOSITORY and ARCH are the first two columns in the 'osc
2865 results' output. If the buildlog url is used buildlog command has the
2866 same behavior as remotebuildlog.
2868 ${cmd_usage} [REPOSITORY ARCH | BUILDLOGURL]
2872 repository = arch = None
2874 if len(args) == 1 and args[0].startswith('http'):
2875 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2878 package = store_read_package(wd)
2879 project = store_read_project(wd)
2880 apiurl = store_read_apiurl(wd)
2884 offset = int(opts.start)
2886 if not repository or not arch:
2890 repository = args[0]
2893 print_buildlog(apiurl, project, package, repository, arch, offset)
2896 def print_repos(self):
2899 if is_package_dir(wd):
2902 elif is_project_dir(wd):
2907 print 'Valid arguments for this %s are:' % str
2909 self.do_repos(None, None)
2911 raise oscerr.WrongArgs('Missing arguments')
2914 @cmdln.alias('rbuildlog')
2915 @cmdln.option('-s', '--start', metavar='START',
2916 help='get log starting from the offset')
2917 def do_remotebuildlog(self, subcmd, opts, *args):
2918 """${cmd_name}: Shows the build log of a package
2920 Shows the log file of the build of a package. Can be used to follow the
2921 log while it is being written.
2924 osc remotebuildlog project package repository arch
2926 osc remotebuildlog project/package/repository/arch
2928 osc remotebuildlog buildlogurl
2931 if len(args) == 1 and args[0].startswith('http'):
2932 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2934 args = slash_split(args)
2935 apiurl = conf.config['apiurl']
2937 raise oscerr.WrongArgs('Too few arguments.')
2939 raise oscerr.WrongArgs('Too many arguments.')
2941 project, package, repository, arch = args
2945 offset = int(opts.start)
2947 print_buildlog(apiurl, project, package, repository, arch, offset)
2950 @cmdln.option('-s', '--start', metavar='START',
2951 help='get log starting from offset')
2952 def do_localbuildlog(self, subcmd, opts, *args):
2953 """${cmd_name}: Shows the build log of a local buildchroot
2956 osc lbl [REPOSITORY ARCH]
2957 osc lbl # show log of newest last local build
2961 if conf.config['build-type']:
2962 # FIXME: raise Exception instead
2963 print >>sys.stderr, 'Not implemented for VMs'
2967 package = store_read_package('.')
2969 files = glob.glob(os.path.join(os.getcwd(), store, "_buildinfo-*"))
2972 raise oscerr.WrongArgs('No buildconfig found, please specify repo and arch manually.')
2976 if os.stat(f).st_mtime > os.stat(cfg).st_mtime:
2978 root = ET.parse(cfg).getroot()
2979 project = root.get("project")
2980 repo = root.get("repository")
2981 arch = root.find("arch").text
2982 elif len(args) == 2:
2983 project = store_read_project('.')
2984 package = store_read_package('.')
2988 if is_package_dir(os.curdir):
2990 raise oscerr.WrongArgs('Wrong number of arguments.')
2992 buildroot = os.environ.get('OSC_BUILD_ROOT', conf.config['build-root'])
2993 buildroot = buildroot % {'project': project, 'package': package,
2994 'repo': repo, 'arch': arch}
2997 offset = int(opts.start)
2998 logfile = os.path.join(buildroot, '.build.log')
2999 if not os.path.isfile(logfile):
3000 raise oscerr.OscIOError(None, 'logfile \'%s\' does not exist' % logfile)
3001 f = open(logfile, 'r')
3003 data = f.read(BUFSIZE)
3005 sys.stdout.write(data)
3006 data = f.read(BUFSIZE)
3010 def do_triggerreason(self, subcmd, opts, *args):
3011 """${cmd_name}: Show reason why a package got triggered to build
3013 The server decides when a package needs to get rebuild, this command
3014 shows the detailed reason for a package. A brief reason is also stored
3015 in the jobhistory, which can be accessed via "osc jobhistory".
3017 Trigger reasons might be:
3018 - new build (never build yet or rebuild manually forced)
3019 - source change (eg. on updating sources)
3020 - meta change (packages which are used for building have changed)
3021 - rebuild count sync (In case that it is configured to sync release numbers)
3023 usage in package or project directory:
3024 osc reason REPOSITORY ARCH
3025 osc reason PROJECT PACKAGE REPOSITORY ARCH
3030 args = slash_split(args)
3031 project = package = repository = arch = None
3036 if len(args) == 2: # 2
3037 if is_package_dir('.'):
3038 package = store_read_package(wd)
3040 raise oscerr.WrongArgs('package is not specified.')
3041 project = store_read_project(wd)
3042 apiurl = store_read_apiurl(wd)
3043 repository = args[0]
3045 elif len(args) == 4:
3046 apiurl = conf.config['apiurl']
3049 repository = args[2]
3052 raise oscerr.WrongArgs('Too many arguments.')
3054 print apiurl, project, package, repository, arch
3055 xml = show_package_trigger_reason(apiurl, project, package, repository, arch)
3056 root = ET.fromstring(xml)
3057 reason = root.find('explain').text
3059 if reason == "meta change":
3060 print "changed keys:"
3061 for package in root.findall('packagechange'):
3062 print " ", package.get('change'), package.get('key')
3065 # FIXME: the new osc syntax should allow to specify multiple packages
3066 # FIXME: the command should optionally use buildinfo data to show all dependencies
3067 @cmdln.alias('whatdependson')
3068 def do_dependson(self, subcmd, opts, *args):
3069 """${cmd_name}: Show the build dependencies
3071 The command dependson and whatdependson can be used to find out what
3072 will be triggered when a certain package changes.
3073 This is no guarantee, since the new build might have changed dependencies.
3075 dependson shows the build dependencies inside of a project, valid for a
3076 given repository and architecture.
3077 NOTE: to see all binary packages, which can trigger a build you need to
3078 refer the buildinfo, since this command shows only the dependencies
3079 inside of a project.
3081 The arguments REPOSITORY and ARCH can be taken from the first two columns
3082 of the 'osc repos' output.
3084 usage in package or project directory:
3085 osc dependson REPOSITORY ARCH
3086 osc whatdependson REPOSITORY ARCH
3089 osc dependson PROJECT [PACKAGE] REPOSITORY ARCH
3090 osc whatdependson PROJECT [PACKAGE] REPOSITORY ARCH