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()
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 for repo in get_repos_of_project(apiurl, project):
302 if repo.name == opts.repo:
304 elif opts.arch and not opts.repo:
305 for repo in get_repos_of_project(apiurl, project):
306 if repo.arch == opts.arch:
309 repos = get_repos_of_project(apiurl, project)
313 results.append((repo, get_binarylist(apiurl, project, repo.name, repo.arch, package=package, verbose=opts.verbose)))
315 for result in results:
318 print '%s/%s' % (result[0].name, result[0].arch)
323 print "%9d %s %-40s" % (f.size, shorttime(f.mtime), f.name)
329 elif not opts.binaries:
331 print '\n'.join(meta_get_project_list(conf.config['apiurl']))
335 if self.options.verbose:
336 print >>sys.stderr, 'Sorry, the --verbose option is not implemented for projects.'
338 raise oscerr.WrongOptions('Sorry, the --expand option is not implemented for projects.')
340 print '\n'.join(meta_get_packagelist(conf.config['apiurl'], project))
342 elif len(args) == 2 or len(args) == 3:
344 print_not_found = True
346 l = meta_get_filelist(conf.config['apiurl'],
349 verbose=opts.verbose,
351 revision=opts.revision)
352 link_seen = '_link' in l
354 out = [ '%s %7s %9d %s %s' % (i.md5, i.rev, i.size, shorttime(i.mtime), i.name) \
355 for i in l if not fname or fname == i.name ]
357 print_not_found = False
363 print_not_found = False
366 if opts.expand or opts.unexpand or not link_seen: break
367 m = show_files_meta(conf.config['apiurl'], project, package)
368 xml = ET.fromstring(''.join(m)).find('linkinfo')
369 project, package = xml.get('project'), xml.get('package')
370 print "# -> %s %s" % (project, package)
372 if fname and print_not_found:
373 print 'file \'%s\' does not exist' % fname
376 @cmdln.option('-f', '--force', action='store_true',
377 help='force generation of new patchinfo file')
378 @cmdln.option('--force-update', action='store_true',
379 help='drops away collected packages from an already built patch and let it collect again')
380 def do_patchinfo(self, subcmd, opts, *args):
381 """${cmd_name}: Generate and edit a patchinfo file.
383 A patchinfo file describes the packages for an update and the kind of
388 osc patchinfo PATCH_NAME
392 project_dir = localdir = os.getcwd()
393 if is_project_dir(localdir):
394 project = store_read_project(localdir)
395 apiurl = store_read_apiurl(localdir)
397 sys.exit('This command must be called in a checked out project.')
399 for p in meta_get_packagelist(apiurl, project):
400 if p.startswith("_patchinfo:"):
403 if opts.force or not patchinfo:
404 print "Creating initial patchinfo..."
405 query='cmd=createpatchinfo'
407 query += "&name=" + args[0]
408 url = makeurl(apiurl, ['source', project], query=query)
410 for p in meta_get_packagelist(apiurl, project):
411 if p.startswith("_patchinfo:"):
414 if not os.path.exists(project_dir + "/" + patchinfo):
415 checkout_package(apiurl, project, patchinfo, prj_dir=project_dir)
417 if sys.platform[:3] != 'win':
418 editor = os.getenv('EDITOR', default='vim')
420 editor = os.getenv('EDITOR', default='notepad')
421 subprocess.call('%s %s' % (editor, project_dir + "/" + patchinfo + "/_patchinfo"), shell=True)
424 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
425 help='affect only a given attribute')
426 @cmdln.option('--attribute-defaults', action='store_true',
427 help='include defined attribute defaults')
428 @cmdln.option('--attribute-project', action='store_true',
429 help='include project values, if missing in packages ')
430 @cmdln.option('-F', '--file', metavar='FILE',
431 help='read metadata from FILE, instead of opening an editor. '
432 '\'-\' denotes standard input. ')
433 @cmdln.option('-e', '--edit', action='store_true',
434 help='edit metadata')
435 @cmdln.option('-c', '--create', action='store_true',
436 help='create attribute without values')
437 @cmdln.option('-s', '--set', metavar='ATTRIBUTE_VALUES',
438 help='set attribute values')
439 @cmdln.option('--delete', action='store_true',
440 help='delete a pattern or attribute')
441 def do_meta(self, subcmd, opts, *args):
442 """${cmd_name}: Show meta information, or edit it
444 Show or edit build service metadata of type <prj|pkg|prjconf|user|pattern>.
446 This command displays metadata on buildservice objects like projects,
447 packages, or users. The type of metadata is specified by the word after
448 "meta", like e.g. "meta prj".
450 prj denotes metadata of a buildservice project.
451 prjconf denotes the (build) configuration of a project.
452 pkg denotes metadata of a buildservice package.
453 user denotes the metadata of a user.
454 pattern denotes installation patterns defined for a project.
456 To list patterns, use 'osc meta pattern PRJ'. An additional argument
457 will be the pattern file to view or edit.
459 With the --edit switch, the metadata can be edited. Per default, osc
460 opens the program specified by the environmental variable EDITOR with a
461 temporary file. Alternatively, content to be saved can be supplied via
462 the --file switch. If the argument is '-', input is taken from stdin:
463 osc meta prjconf home:user | sed ... | osc meta prjconf home:user -F -
465 When trying to edit a non-existing resource, it is created implicitly.
471 osc meta pkg PRJ PKG -e
472 osc meta attribute PRJ [PKG [SUBPACKAGE]] [--attribute ATTRIBUTE] [--create|--delete|--set [value_list]]
475 osc meta <prj|pkg|prjconf|user|pattern|attribute> ARGS...
476 osc meta <prj|pkg|prjconf|user|pattern|attribute> -e|--edit ARGS...
477 osc meta <prj|pkg|prjconf|user|pattern|attribute> -F|--file ARGS...
478 osc meta pattern --delete PRJ PATTERN
482 args = slash_split(args)
484 if not args or args[0] not in metatypes.keys():
485 raise oscerr.WrongArgs('Unknown meta type. Choose one of %s.' \
486 % ', '.join(metatypes))
492 min_args, max_args = 2, 2
493 elif cmd in ['pattern']:
494 min_args, max_args = 1, 2
495 elif cmd in ['attribute']:
496 min_args, max_args = 1, 3
498 min_args, max_args = 1, 1
499 if len(args) < min_args:
500 raise oscerr.WrongArgs('Too few arguments.')
501 if len(args) > max_args:
502 raise oscerr.WrongArgs('Too many arguments.')
509 project, package = args[0:2]
510 elif cmd == 'attribute':
516 if opts.attribute_project:
517 raise oscerr.WrongOptions('--attribute-project works only when also a package is given')
522 attributepath.append('source')
523 attributepath.append(project)
525 attributepath.append(package)
527 attributepath.append(subpackage)
528 attributepath.append('_attribute')
529 elif cmd == 'prjconf':
533 elif cmd == 'pattern':
539 # enforce pattern argument if needed
540 if opts.edit or opts.file:
541 raise oscerr.WrongArgs('A pattern file argument is required.')
544 if not opts.edit and not opts.file and not opts.delete and not opts.create and not opts.set:
546 sys.stdout.write(''.join(show_project_meta(conf.config['apiurl'], project)))
548 sys.stdout.write(''.join(show_package_meta(conf.config['apiurl'], project, package)))
549 elif cmd == 'attribute':
550 sys.stdout.write(''.join(show_attribute_meta(conf.config['apiurl'], project, package, subpackage, opts.attribute, opts.attribute_defaults, opts.attribute_project)))
551 elif cmd == 'prjconf':
552 sys.stdout.write(''.join(show_project_conf(conf.config['apiurl'], project)))
554 r = get_user_meta(conf.config['apiurl'], user)
556 sys.stdout.write(''.join(r))
557 elif cmd == 'pattern':
559 r = show_pattern_meta(conf.config['apiurl'], project, pattern)
561 sys.stdout.write(''.join(r))
563 r = show_pattern_metalist(conf.config['apiurl'], project)
565 sys.stdout.write('\n'.join(r) + '\n')
568 if opts.edit and not opts.file:
570 edit_meta(metatype='prj',
572 path_args=quote_plus(project),
575 'user': conf.config['user']}))
577 edit_meta(metatype='pkg',
579 path_args=(quote_plus(project), quote_plus(package)),
582 'user': conf.config['user']}))
583 elif cmd == 'prjconf':
584 edit_meta(metatype='prjconf',
586 path_args=quote_plus(project),
589 edit_meta(metatype='user',
591 path_args=(quote_plus(user)),
592 template_args=({'user': user}))
593 elif cmd == 'pattern':
594 edit_meta(metatype='pattern',
596 path_args=(project, pattern),
599 # create attribute entry
600 if (opts.create or opts.set) and cmd == 'attribute':
601 if not opts.attribute:
602 raise oscerr.WrongOptions('no attribute given to create')
605 opts.set = opts.set.replace('&', '&').replace('<', '<').replace('>', '>')
606 for i in opts.set.split(','):
607 values += '<value>%s</value>' % i
608 aname = opts.attribute.split(":")
609 d = '<attributes><attribute namespace=\'%s\' name=\'%s\' >%s</attribute></attributes>' % (aname[0], aname[1], values)
610 url = makeurl(conf.config['apiurl'], attributepath)
611 for data in streamfile(url, http_POST, data=d):
612 sys.stdout.write(data)
621 f = open(opts.file).read()
623 sys.exit('could not open file \'%s\'.' % opts.file)
626 edit_meta(metatype='prj',
629 path_args=quote_plus(project))
631 edit_meta(metatype='pkg',
634 path_args=(quote_plus(project), quote_plus(package)))
635 elif cmd == 'prjconf':
636 edit_meta(metatype='prjconf',
639 path_args=quote_plus(project))
641 edit_meta(metatype='user',
644 path_args=(quote_plus(user)))
645 elif cmd == 'pattern':
646 edit_meta(metatype='pattern',
649 path_args=(project, pattern))
654 path = metatypes[cmd]['path']
656 path = path % (project, pattern)
657 u = makeurl(conf.config['apiurl'], [path])
659 elif cmd == 'attribute':
660 if not opts.attribute:
661 raise oscerr.WrongOptions('no attribute given to create')
662 attributepath.append(opts.attribute)
663 u = makeurl(conf.config['apiurl'], attributepath)
664 for data in streamfile(u, http_DELETE):
665 sys.stdout.write(data)
667 raise oscerr.WrongOptions('The --delete switch is only for pattern metadata or attributes.')
670 @cmdln.option('-m', '--message', metavar='TEXT',
671 help='specify message TEXT')
672 @cmdln.option('-r', '--revision', metavar='REV',
673 help='for "create", specify a certain source revision ID (the md5 sum)')
674 @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
675 help='Superseding another request by this one')
676 @cmdln.option('--nodevelproject', action='store_true',
677 help='do not follow a defined devel project ' \
678 '(primary project where a package is developed)')
679 @cmdln.option('--cleanup', action='store_true',
680 help='remove package if submission gets accepted (default for home:<id>:branch projects)')
681 @cmdln.option('--no-cleanup', action='store_true',
682 help='never remove source package on accept, but update its content')
683 @cmdln.option('--no-update', action='store_true',
684 help='never touch source package on accept (will break source links)')
685 @cmdln.option('-d', '--diff', action='store_true',
686 help='show diff only instead of creating the actual request')
687 @cmdln.option('--yes', action='store_true',
688 help='proceed without asking.')
690 @cmdln.alias("submitreq")
691 @cmdln.alias("submitpac")
692 def do_submitrequest(self, subcmd, opts, *args):
693 """${cmd_name}: Create request to submit source into another Project
695 [See http://en.opensuse.org/Build_Service/Collaboration for information
698 See the "request" command for showing and modifing existing requests.
701 osc submitreq [OPTIONS]
702 osc submitreq [OPTIONS] DESTPRJ [DESTPKG]
703 osc submitreq [OPTIONS] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
707 src_update = conf.config['submitrequest_on_accept_action'] or None
708 # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
710 src_update = "cleanup"
711 elif opts.no_cleanup:
712 src_update = "update"
714 src_update = "noupdate"
716 args = slash_split(args)
718 # remove this block later again
719 oldcmds = ['create', 'list', 'log', 'show', 'decline', 'accept', 'delete', 'revoke']
720 if args and args[0] in oldcmds:
721 print "************************************************************************"
722 print "* WARNING: It looks that you are using this command with a *"
723 print "* deprecated syntax. *"
724 print "* Please run \"osc sr --help\" and \"osc rq --help\" *"
725 print "* to see the new syntax. *"
726 print "************************************************************************"
727 if args[0] == 'create':
733 raise oscerr.WrongArgs('Too many arguments.')
735 if len(args) > 0 and len(args) <= 2 and is_project_dir(os.getcwd()):
736 sys.exit('osc submitrequest from project directory is only working without target specs and for source linked files\n')
738 apiurl = conf.config['apiurl']
740 if len(args) == 0 and is_project_dir(os.getcwd()):
742 # submit requests for multiple packages are currently handled via multiple requests
743 # They could be also one request with multiple actions, but that avoids to accepts parts of it.
744 project = store_read_project(os.curdir)
745 apiurl = store_read_apiurl(os.curdir)
751 # loop via all packages for checking their state
752 for p in meta_get_packagelist(apiurl, project):
753 if p.startswith("_patchinfo:"):
756 # get _link info from server, that knows about the local state ...
757 u = makeurl(apiurl, ['source', project, p])
759 root = ET.parse(f).getroot()
760 linkinfo = root.find('linkinfo')
762 print "Package ", p, " is not a source link."
763 sys.exit("This is currently not supported.")
764 if linkinfo.get('error'):
765 print "Package ", p, " is a broken source link."
766 sys.exit("Please fix this first")
767 t = linkinfo.get('project')
769 if len(root.findall('entry')) > 1: # This is not really correct, but should work mostly
770 # Real fix is to ask the api if sources are modificated
771 # but there is no such call yet.
772 targetprojects.append(t)
774 print "Submitting package ", p
776 print " Skipping package ", p
778 print "Skipping package ", p, " since it is a source link pointing inside the project."
782 print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
783 print "\nEverything fine? Can we create the requests ? [y/n]"
784 if sys.stdin.read(1) != "y":
785 sys.exit("Aborted...")
787 # loop via all packages to do the action
789 result = create_submit_request(apiurl, project, p)
792 sys.exit("submit request creation failed")
793 sr_ids.append(result)
795 # create submit requests for all found patchinfos
799 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
802 for t in targetprojects:
803 s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \
804 (project, p, t, p, options_block)
808 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
809 (actionxml, cgi.escape(opts.message or ""))
810 u = makeurl(apiurl, ['request'], query='cmd=create')
811 f = http_POST(u, data=xml)
813 root = ET.parse(f).getroot()
814 sr_ids.append(root.get('id'))
816 print "Requests created: ",
819 sys.exit('Successfull finished')
822 # try using the working copy at hand
823 p = findpacs(os.curdir)[0]
824 src_project = p.prjname
827 if len(args) == 0 and p.islink():
828 dst_project = p.linkinfo.project
829 dst_package = p.linkinfo.package
831 dst_project = args[0]
833 dst_package = args[1]
835 dst_package = src_package
837 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
838 'Please provide it the target via commandline arguments.' % p.name)
840 modified = [i for i in p.filenamelist if p.status(i) != ' ' and p.status(i) != '?']
841 if len(modified) > 0:
842 print 'Your working copy has local modifications.'
843 repl = raw_input('Proceed without committing the local changes? (y|N) ')
847 # get the arguments from the commandline
848 src_project, src_package, dst_project = args[0:3]
850 dst_package = args[3]
852 dst_package = src_package
854 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
855 + self.get_cmd_help('request'))
857 if not opts.nodevelproject:
860 devloc = show_develproject(apiurl, dst_project, dst_package)
861 except urllib2.HTTPError:
862 print >>sys.stderr, """\
863 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
864 % (dst_project, dst_package)
868 dst_project != devloc and \
869 src_project != devloc:
871 A different project, %s, is defined as the place where development
872 of the package %s primarily takes place.
873 Please submit there instead, or use --nodevelproject to force direct submission.""" \
874 % (devloc, dst_package)
879 if opts.diff or not opts.message:
881 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
882 rdiff += server_diff(apiurl,
883 dst_project, dst_package, opts.revision,
884 src_project, src_package, None, True)
890 reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit')
891 user = conf.get_apiurl_usr(apiurl)
892 myreqs = [ i for i in reqs if i.state.who == user ]
895 print 'You already created the following submit request: %s.' % \
896 ', '.join([str(i.reqid) for i in myreqs ])
897 repl = raw_input('Supersede the old requests? (y/n/c) ')
898 if repl.lower() == 'c':
899 print >>sys.stderr, 'Aborting'
905 changes_re = re.compile(r'^--- .*\.changes ')
906 for line in rdiff.split('\n'):
907 if line.startswith('--- '):
908 if changes_re.match(line):
913 difflines.append(line)
914 opts.message = edit_message(footer=rdiff, template='\n'.join(parse_diff_for_commit_message('\n'.join(difflines))))
916 result = create_submit_request(apiurl,
917 src_project, src_package,
918 dst_project, dst_package,
919 opts.message, orev=opts.revision, src_update=src_update)
920 if repl.lower() == 'y':
922 change_request_state(apiurl, str(req.reqid), 'superseded',
923 'superseded by %s' % result, result)
926 r = change_request_state(conf.config['apiurl'],
927 opts.supersede, 'superseded', opts.message or '', result)
929 print 'created request id', result
932 @cmdln.option('-m', '--message', metavar='TEXT',
933 help='specify message TEXT')
935 @cmdln.alias("deletereq")
936 def do_deleterequest(self, subcmd, opts, *args):
937 """${cmd_name}: Create request to delete a package or project
941 osc deletereq [-m TEXT] PROJECT [PACKAGE]
945 args = slash_split(args)
948 raise oscerr.WrongArgs('Please specify at least a project.')
950 raise oscerr.WrongArgs('Too many arguments.')
952 apiurl = conf.config['apiurl']
960 opts.message = edit_message()
962 result = create_delete_request(apiurl, project, package, opts.message)
966 @cmdln.option('-m', '--message', metavar='TEXT',
967 help='specify message TEXT')
969 @cmdln.alias("changedevelreq")
970 def do_changedevelrequest(self, subcmd, opts, *args):
971 """${cmd_name}: Create request to change the devel package definition.
973 [See http://en.opensuse.org/Build_Service/Collaboration for information
976 See the "request" command for showing and modifing existing requests.
978 osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
982 raise oscerr.WrongArgs('Too many arguments.')
984 if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
986 devel_project = store_read_project(wd)
987 devel_package = package = store_read_package(wd)
988 apiurl = store_read_apiurl(wd)
989 project = conf.config['getpac_default_project']
992 raise oscerr.WrongArgs('Too few arguments.')
994 apiurl = conf.config['apiurl']
996 devel_project = args[2]
999 devel_package = package
1001 devel_package = args[3]
1003 if not opts.message:
1005 footer=textwrap.TextWrapper(width = 66).fill(
1006 'please explain why you like to change the devel project of %s/%s to %s/%s'
1007 % (project,package,devel_project,devel_package))
1008 opts.message = edit_message(footer)
1010 result = create_change_devel_request(apiurl,
1011 devel_project, devel_package,
1017 @cmdln.option('-d', '--diff', action='store_true',
1018 help='generate a diff')
1019 @cmdln.option('-u', '--unified', action='store_true',
1020 help='output the diff in the unified diff format')
1021 @cmdln.option('-m', '--message', metavar='TEXT',
1022 help='specify message TEXT')
1023 @cmdln.option('-t', '--type', metavar='TYPE',
1024 help='limit to requests which contain a given action type (submit/delete/change_devel)')
1025 @cmdln.option('-a', '--all', action='store_true',
1026 help='all states. Same as\'-s all\'')
1027 @cmdln.option('-s', '--state', default='', # default is 'all' if no args given, 'new' otherwise
1028 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]')
1029 @cmdln.option('-D', '--days', metavar='DAYS',
1030 help='only list requests in state "new" or changed in the last DAYS. [default=%(request_list_days)s]')
1031 @cmdln.option('-U', '--user', metavar='USER',
1032 help='same as -M, but for the specified USER')
1033 @cmdln.option('-b', '--brief', action='store_true', default=False,
1034 help='print output in list view as list subcommand')
1035 @cmdln.option('-M', '--mine', action='store_true',
1036 help='only show requests created by yourself')
1037 @cmdln.option('-B', '--bugowner', action='store_true',
1038 help='also show requests about packages where I am bugowner')
1039 @cmdln.option('-i', '--interactive', action='store_true',
1040 help='interactive review of request')
1041 @cmdln.option('--non-interactive', action='store_true',
1042 help='non-interactive review of request')
1043 @cmdln.option('--exclude-target-project', action='append',
1044 help='exclude target project from request list')
1045 @cmdln.option('--involved-projects', action='store_true',
1046 help='show all requests for project/packages where USER is involved')
1048 @cmdln.alias("review")
1049 def do_request(self, subcmd, opts, *args):
1050 """${cmd_name}: Show and modify requests
1052 [See http://en.opensuse.org/Build_Service/Collaboration for information
1055 This command shows and modifies existing requests. To create new requests
1056 you need to call one of the following:
1059 osc changedevelrequest
1060 To send low level requests to the buildservice API, use:
1063 This command has the following sub commands:
1065 "list" lists open requests attached to a project or package or person.
1066 Uses the project/package of the current directory if none of
1067 -M, -U USER, project/package are given.
1069 "log" will show the history of the given ID
1071 "show" will show the request itself, and generate a diff for review, if
1072 used with the --diff option. The keyword show can be omitted if the ID is numeric.
1074 "decline" will change the request state to "declined" and append a
1075 message that you specify with the --message option.
1077 "wipe" will permanently delete a request.
1079 "revoke" will set the request state to "revoked" and append a
1080 message that you specify with the --message option.
1082 "accept" will change the request state to "accepted" and will trigger
1083 the actual submit process. That would normally be a server-side copy of
1084 the source package to the target package.
1086 "checkout" will checkout the request's source package. This only works for "submit" requests.
1089 osc request list [-M] [-U USER] [-s state] [-D DAYS] [-t type] [-B] [PRJ [PKG]]
1091 osc request [show] [-d] [-b] ID
1092 osc request accept [-m TEXT] ID
1093 osc request decline [-m TEXT] ID
1094 osc request revoke [-m TEXT] ID
1096 osc request checkout/co ID
1097 osc review accept [-m TEXT] ID
1098 osc review decline [-m TEXT] ID
1102 args = slash_split(args)
1104 if opts.all and opts.state:
1105 raise oscerr.WrongOptions('Sorry, the options \'--all\' and \'--state\' ' \
1106 'are mutually exclusive.')
1107 if opts.mine and opts.user:
1108 raise oscerr.WrongOptions('Sorry, the options \'--user\' and \'--mine\' ' \
1109 'are mutually exclusive.')
1110 if opts.interactive and opts.non_interactive:
1111 raise oscerr.WrongOptions('Sorry, the options \'--interactive\' and ' \
1112 '\'--non-interactive\' are mutually exclusive')
1117 if opts.state == '':
1120 if opts.state == '':
1123 cmds = ['list', 'log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co', 'help']
1124 if not args or args[0] not in cmds:
1125 raise oscerr.WrongArgs('Unknown request action %s. Choose one of %s.' \
1126 % (args[0],', '.join(cmds)))
1132 return self.do_help(['help', 'request'])
1135 min_args, max_args = 1, 1
1136 elif cmd in ['list']:
1137 min_args, max_args = 0, 2
1139 min_args, max_args = 1, 1
1140 if len(args) < min_args:
1141 raise oscerr.WrongArgs('Too few arguments.')
1142 if len(args) > max_args:
1143 raise oscerr.WrongArgs('Too many arguments.')
1145 apiurl = conf.config['apiurl']
1152 elif not opts.mine and not opts.user:
1154 project = store_read_project(os.curdir)
1155 apiurl = store_read_apiurl(os.curdir)
1156 package = store_read_package(os.curdir)
1157 except oscerr.NoWorkingCopy:
1162 elif cmd in ['log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co']:
1167 states = ('new', 'accepted', 'revoked', 'declined')
1168 state_list = opts.state.split(',')
1169 if opts.state == 'all':
1170 state_list = ['all']
1172 for s in state_list:
1174 raise oscerr.WrongArgs('Unknown state \'%s\', try one of %s' % (s, ','.join(states)))
1177 who = conf.get_apiurl_usr(apiurl)
1181 state_list = ['all']
1183 ## FIXME -B not implemented!
1185 if (self.options.debug):
1186 print 'list: option --bugowner ignored: not impl.'
1188 if opts.involved_projects:
1189 who = who or conf.get_apiurl_usr(apiurl)
1190 results = get_user_projpkgs_request_list(apiurl, who, req_state=state_list,
1191 req_type=opts.type, exclude_projects=opts.exclude_target_project or [])
1193 results = get_request_list(apiurl, project, package, who,
1194 state_list, opts.type, opts.exclude_target_project or [])
1195 results.sort(reverse=True)
1197 days = opts.days or conf.config['request_list_days']
1204 since = time.strftime('%Y-%m-%dT%H:%M:%S',time.localtime(time.time()-days*24*3600))
1207 ## bs has received 2009-09-20 a new xquery compare() function
1208 ## which allows us to limit the list inside of get_request_list
1209 ## That would be much faster for coolo. But counting the remainder
1210 ## would not be possible with current xquery implementation.
1211 ## Workaround: fetch all, and filter on client side.
1213 ## FIXME: date filtering should become implemented on server side
1214 for result in results:
1215 if days == 0 or result.state.when > since or result.state.name == 'new':
1216 print result.list_view()
1220 print "There are %d requests older than %s days.\n" % (skipped, days)
1223 for l in get_request_log(conf.config['apiurl'], reqid):
1228 r = get_request(conf.config['apiurl'], reqid)
1231 elif (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive:
1232 return request_interactive_review(conf.config['apiurl'], r)
1235 # fixme: will inevitably fail if the given target doesn't exist
1236 if opts.diff and r.actions[0].type != 'submit':
1237 raise oscerr.WrongOptions('\'--diff\' is not possible for request type: \'%s\'' % r.actions[0].type)
1240 print server_diff(conf.config['apiurl'],
1241 r.actions[0].dst_project, r.actions[0].dst_package, None,
1242 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified)
1243 except urllib2.HTTPError, e:
1244 e.osc_msg = 'Diff not possible'
1248 elif cmd == 'checkout' or cmd == 'co':
1249 r = get_request(conf.config['apiurl'], reqid)
1250 submits = [ i for i in r.actions if i.type == 'submit' ]
1251 if not len(submits):
1252 raise oscerr.WrongArgs('\'checkout\' only works for \'submit\' requests')
1253 checkout_package(conf.config['apiurl'], submits[0].src_project, submits[0].src_package, \
1254 submits[0].src_rev, expand_link=True, prj_dir=submits[0].src_project)
1257 if not opts.message:
1258 opts.message = edit_message()
1259 state_map = {'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked'}
1260 # Change review state only
1261 if subcmd == 'review':
1262 if cmd in ['accept', 'decline']:
1263 r = change_review_state(conf.config['apiurl'],
1264 reqid, state_map[cmd], conf.config['user'], '', opts.message or '')
1266 # Change state of entire request
1267 elif cmd in ['accept', 'decline', 'wipe', 'revoke']:
1268 r = change_request_state(conf.config['apiurl'],
1269 reqid, state_map[cmd], opts.message or '')
1272 # editmeta and its aliases are all depracated
1273 @cmdln.alias("editprj")
1274 @cmdln.alias("createprj")
1275 @cmdln.alias("editpac")
1276 @cmdln.alias("createpac")
1277 @cmdln.alias("edituser")
1278 @cmdln.alias("usermeta")
1280 def do_editmeta(self, subcmd, opts, *args):
1283 Obsolete command to edit metadata. Use 'meta' now.
1285 See the help output of 'meta'.
1289 print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
1290 print >>sys.stderr, 'See \'osc help meta\'.'
1291 #self.do_help([None, 'meta'])
1295 @cmdln.option('-r', '--revision', metavar='rev',
1296 help='use the specified revision.')
1297 @cmdln.option('-u', '--unset', action='store_true',
1298 help='remove revision in link, it will point always to latest revision')
1299 def do_setlinkrev(self, subcmd, opts, *args):
1300 """${cmd_name}: Updates a revision number in a source link.
1302 This command adds or updates a specified revision number in a source link.
1303 The current revision of the source is used, if no revision number is specified.
1307 osc setlinkrev PROJECT [PACKAGE]
1311 args = slash_split(args)
1312 apiurl = conf.config['apiurl']
1315 p = findpacs(os.curdir)[0]
1320 sys.exit('Local directory is no checked out source link package, aborting')
1321 elif len(args) == 2:
1324 elif len(args) == 1:
1327 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1328 + self.get_cmd_help('setlinkrev'))
1331 packages = [ package ]
1333 packages = meta_get_packagelist(apiurl, project)
1336 print "setting revision for package", p
1340 rev, dummy = parseRevisionOption(opts.revision)
1341 set_link_rev(apiurl, project, p, rev)
1344 def do_linktobranch(self, subcmd, opts, *args):
1345 """${cmd_name}: Convert a package containing a classic link with patch to a branch
1347 This command tells the server to convert a _link with or without a project.diff
1348 to a branch. This is a full copy with a _link file pointing to the branched place.
1351 osc linktobranch # can be used in checked out package
1352 osc linktobranch PROJECT PACKAGE
1356 args = slash_split(args)
1359 project = store_read_project(wd)
1360 package = store_read_package(wd)
1361 apiurl = store_read_apiurl(wd)
1362 update_local_dir = True
1364 raise oscerr.WrongArgs('Too few arguments (required none or two)')
1366 raise oscerr.WrongArgs('Too many arguments (required none or two)')
1368 apiurl = conf.config['apiurl']
1371 update_local_dir = False
1374 link_to_branch(apiurl, project, package)
1375 if update_local_dir:
1377 pac.update(rev=pac.latest_rev())
1380 @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'],
1381 help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.')
1382 @cmdln.option('-c', '--current', action='store_true',
1383 help='link fixed against current revision.')
1384 @cmdln.option('-r', '--revision', metavar='rev',
1385 help='link the specified revision.')
1386 @cmdln.option('-f', '--force', action='store_true',
1387 help='overwrite an existing link file if it is there.')
1388 @cmdln.option('-d', '--disable-publish', action='store_true',
1389 help='disable publishing of the linked package')
1390 def do_linkpac(self, subcmd, opts, *args):
1391 """${cmd_name}: "Link" a package to another package
1393 A linked package is a clone of another package, but plus local
1394 modifications. It can be cross-project.
1396 The DESTPAC name is optional; the source packages' name will be used if
1399 Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
1401 To add a patch, add the patch as file and add it to the _link file.
1402 You can also specify text which will be inserted at the top of the spec file.
1404 See the examples in the _link file.
1407 osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1411 args = slash_split(args)
1413 if not args or len(args) < 3:
1414 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1415 + self.get_cmd_help('linkpac'))
1417 rev, dummy = parseRevisionOption(opts.revision)
1419 src_project = args[0]
1420 src_package = args[1]
1421 dst_project = args[2]
1423 dst_package = args[3]
1425 dst_package = src_package
1427 if src_project == dst_project and src_package == dst_package:
1428 raise oscerr.WrongArgs('Error: source and destination are the same.')
1430 if src_project == dst_project and not opts.cicount:
1431 # in this case, the user usually wants to build different spec
1432 # files from the same source
1433 opts.cicount = "copy"
1436 rev = show_upstream_rev(conf.config['apiurl'], src_project, src_package)
1438 if rev and not checkRevision(src_project, src_package, rev):
1439 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
1442 link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish)
1444 @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]',
1445 help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]')
1446 @cmdln.option('-d', '--disable-publish', action='store_true',
1447 help='disable publishing of the aggregated package')
1448 def do_aggregatepac(self, subcmd, opts, *args):
1449 """${cmd_name}: "Aggregate" a package to another package
1451 Aggregation of a package means that the build results (binaries) of a
1452 package are basically copied into another project.
1453 This can be used to make packages available from building that are
1454 needed in a project but available only in a different project. Note
1455 that this is done at the expense of disk space. See
1456 http://en.opensuse.org/Build_Service/Tips_and_Tricks#_link_and__aggregate
1457 for more information.
1459 The DESTPAC name is optional; the source packages' name will be used if
1463 osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1467 args = slash_split(args)
1469 if not args or len(args) < 3:
1470 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1471 + self.get_cmd_help('aggregatepac'))
1473 src_project = args[0]
1474 src_package = args[1]
1475 dst_project = args[2]
1477 dst_package = args[3]
1479 dst_package = src_package
1481 if src_project == dst_project and src_package == dst_package:
1482 raise oscerr.WrongArgs('Error: source and destination are the same.')
1486 for pair in opts.map_repo.split(','):
1487 src_tgt = pair.split('=')
1488 if len(src_tgt) != 2:
1489 raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo)
1490 repo_map[src_tgt[0]] = src_tgt[1]
1492 aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish)
1495 @cmdln.option('-c', '--client-side-copy', action='store_true',
1496 help='do a (slower) client-side copy')
1497 @cmdln.option('-k', '--keep-maintainers', action='store_true',
1498 help='keep original maintainers. Default is remove all and replace with the one calling the script.')
1499 @cmdln.option('-d', '--keep-develproject', action='store_true',
1500 help='keep develproject tag in the package metadata')
1501 @cmdln.option('-r', '--revision', metavar='rev',
1502 help='link the specified revision.')
1503 @cmdln.option('-t', '--to-apiurl', metavar='URL',
1504 help='URL of destination api server. Default is the source api server.')
1505 @cmdln.option('-m', '--message', metavar='TEXT',
1506 help='specify message TEXT')
1507 @cmdln.option('-e', '--expand', action='store_true',
1508 help='if the source package is a link then copy the expanded version of the link')
1509 def do_copypac(self, subcmd, opts, *args):
1510 """${cmd_name}: Copy a package
1512 A way to copy package to somewhere else.
1514 It can be done across buildservice instances, if the -t option is used.
1515 In that case, a client-side copy is implied.
1517 Using --client-side-copy always involves downloading all files, and
1518 uploading them to the target.
1520 The DESTPAC name is optional; the source packages' name will be used if
1524 osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1528 args = slash_split(args)
1530 if not args or len(args) < 3:
1531 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1532 + self.get_cmd_help('copypac'))
1534 src_project = args[0]
1535 src_package = args[1]
1536 dst_project = args[2]
1538 dst_package = args[3]
1540 dst_package = src_package
1542 src_apiurl = conf.config['apiurl']
1544 dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl)
1546 dst_apiurl = src_apiurl
1548 if src_project == dst_project and \
1549 src_package == dst_package and \
1550 src_apiurl == dst_apiurl:
1551 raise oscerr.WrongArgs('Source and destination are the same.')
1553 if src_apiurl != dst_apiurl:
1554 opts.client_side_copy = True
1556 rev, dummy = parseRevisionOption(opts.revision)
1559 comment = opts.message
1562 rev = show_upstream_rev(src_apiurl, src_project, src_package)
1563 comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev )
1565 r = copy_pac(src_apiurl, src_project, src_package,
1566 dst_apiurl, dst_project, dst_package,
1567 client_side_copy=opts.client_side_copy,
1568 keep_maintainers=opts.keep_maintainers,
1569 keep_develproject=opts.keep_develproject,
1576 @cmdln.option('-c', '--checkout', action='store_true',
1577 help='Checkout branched package afterwards ' \
1578 '(\'osc bco\' is a shorthand for this option)' )
1579 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
1580 help='Use this attribute to find affected packages (default is OBS:Maintained)')
1581 @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE',
1582 help='Use this attribute to find update projects (default is OBS:UpdateProject) ')
1583 def do_mbranch(self, subcmd, opts, *args):
1584 """${cmd_name}: Multiple branch of a package
1586 [See http://en.opensuse.org/Build_Service/Concepts/Maintenance for information
1589 This command is used for creating multiple links of defined version of a package
1590 in one project. This is esp. used for maintenance updates.
1592 The branched package will live in
1593 home:USERNAME:branches:ATTRIBUTE:PACKAGE
1594 if nothing else specified.
1597 osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ]
1600 args = slash_split(args)
1603 maintained_attribute = conf.config['maintained_attribute']
1604 maintained_update_project_attribute = conf.config['maintained_update_project_attribute']
1606 if not len(args) or len(args) > 2:
1607 raise oscerr.WrongArgs('Wrong number of arguments.')
1613 r = attribute_branch_pkg(conf.config['apiurl'], maintained_attribute, maintained_update_project_attribute, \
1617 print >>sys.stderr, 'ERROR: Attribute branch call came not back with a project.'
1620 print "Project " + r + " created."
1623 init_project_dir(conf.config['apiurl'], r, r)
1624 print statfrmt('A', r)
1627 for package in meta_get_packagelist(conf.config['apiurl'], r):
1629 checkout_package(conf.config['apiurl'], r, package, expand_link = True, prj_dir = r)
1631 print >>sys.stderr, 'Error while checkout package:\n', package
1633 if conf.config['verbose']:
1634 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1637 @cmdln.alias('branchco')
1639 @cmdln.alias('getpac')
1640 @cmdln.option('--nodevelproject', action='store_true',
1641 help='do not follow a defined devel project ' \
1642 '(primary project where a package is developed)')
1643 @cmdln.option('-c', '--checkout', action='store_true',
1644 help='Checkout branched package afterwards ' \
1645 '(\'osc bco\' is a shorthand for this option)' )
1646 @cmdln.option('-r', '--revision', metavar='rev',
1647 help='branch against a specific revision')
1648 @cmdln.option('-m', '--message', metavar='TEXT',
1649 help='specify message TEXT')
1650 def do_branch(self, subcmd, opts, *args):
1651 """${cmd_name}: Branch a package
1653 [See http://en.opensuse.org/Build_Service/Collaboration for information
1656 Create a source link from a package of an existing project to a new
1657 subproject of the requesters home project (home:branches:)
1659 The branched package will live in
1660 home:USERNAME:branches:PROJECT/PACKAGE
1661 if nothing else specified.
1663 With getpac or bco, the branched package will come from
1664 %(getpac_default_project)s
1665 if nothing else specified.
1668 osc branch SOURCEPROJECT SOURCEPACKAGE
1669 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT
1670 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE
1671 osc getpac SOURCEPACKAGE
1676 if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': opts.checkout = True
1677 args = slash_split(args)
1678 tproject = tpackage = None
1680 if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1:
1681 print >>sys.stderr, 'defaulting to %s/%s' % (conf.config['getpac_default_project'], args[0])
1682 # python has no args.unshift ???
1683 args = [ conf.config['getpac_default_project'] , args[0] ]
1685 if len(args) < 2 or len(args) > 4:
1686 raise oscerr.WrongArgs('Wrong number of arguments.')
1687 expected = 'home:%s:branches:%s' % (conf.config['user'], args[0])
1689 expected = tproject = args[2]
1693 if not opts.message:
1694 footer='please specify the purpose of your branch'
1695 template='This package was branched from %s in order to ...\n' % args[0]
1696 opts.message = edit_message(footer, template)
1698 exists, targetprj, targetpkg, srcprj, srcpkg = \
1699 branch_pkg(conf.config['apiurl'], args[0], args[1],
1700 nodevelproject=opts.nodevelproject, rev=opts.revision,
1701 target_project=tproject, target_package=tpackage,
1702 return_existing=opts.checkout, msg=opts.message or '')
1704 print >>sys.stderr, 'Using existing branch project: %s' % targetprj
1707 if not exists and (srcprj is not None and srcprj != args[0] or \
1708 srcprj is None and targetprj != expected):
1709 devloc = srcprj or targetprj
1710 if not srcprj and 'branches:' in targetprj:
1711 devloc = targetprj.split('branches:')[1]
1712 print '\nNote: The branch has been created of a different project,\n' \
1714 ' which is the primary location of where development for\n' \
1715 ' that package takes place.\n' \
1716 ' That\'s also where you would normally make changes against.\n' \
1717 ' A direct branch of the specified package can be forced\n' \
1718 ' with the --nodevelproject option.\n' % devloc
1720 package = tpackage or args[1]
1722 checkout_package(conf.config['apiurl'], targetprj, package,
1723 expand_link=True, prj_dir=targetprj)
1724 if conf.config['verbose']:
1725 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1728 if conf.get_configParser().get('general', 'apiurl') != conf.config['apiurl']:
1729 apiopt = '-A %s ' % conf.config['apiurl']
1730 print 'A working copy of the branched package can be checked out with:\n\n' \
1732 % (apiopt, targetprj, package)
1733 print_request_list(conf.config['apiurl'], args[0], args[1])
1735 print_request_list(conf.config['apiurl'], devloc, args[1])
1739 @cmdln.option('-f', '--force', action='store_true',
1740 help='deletes a package or an empty project')
1741 def do_rdelete(self, subcmd, opts, *args):
1742 """${cmd_name}: Delete a project or packages on the server.
1744 As a safety measure, project must be empty (i.e., you need to delete all
1745 packages first). If you are sure that you want to remove this project and all
1746 its packages use \'--force\' switch.
1749 osc rdelete -f PROJECT
1750 osc rdelete PROJECT PACKAGE [PACKAGE ...]
1755 args = slash_split(args)
1757 raise oscerr.WrongArgs('Missing argument.')
1763 # careful: if pkg is an empty string, the package delete request results
1764 # into a project delete request - which works recursively...
1766 delete_package(conf.config['apiurl'], prj, pkg)
1767 elif len(meta_get_packagelist(conf.config['apiurl'], prj)) >= 1 and not opts.force:
1768 print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
1769 'If you are sure that you want to remove this project and all its ' \
1770 'packages use the \'--force\' switch'
1773 delete_project(conf.config['apiurl'], prj)
1776 def do_deletepac(self, subcmd, opts, *args):
1777 print """${cmd_name} is obsolete !
1780 osc delete for checked out packages or projects
1782 osc rdelete for server side operations."""
1787 @cmdln.option('-f', '--force', action='store_true',
1788 help='deletes a project and its packages')
1789 def do_deleteprj(self, subcmd, opts, project):
1790 """${cmd_name} is obsolete !
1797 @cmdln.alias('metafromspec')
1798 @cmdln.option('', '--specfile', metavar='FILE',
1799 help='Path to specfile. (if you pass more than working copy this option is ignored)')
1800 def do_updatepacmetafromspec(self, subcmd, opts, *args):
1801 """${cmd_name}: Update package meta information from a specfile
1803 ARG, if specified, is a package working copy.
1809 args = parseargs(args)
1810 if opts.specfile and len(args) == 1:
1811 specfile = opts.specfile
1814 pacs = findpacs(args)
1816 p.read_meta_from_spec(specfile)
1817 p.update_package_meta()
1821 @cmdln.option('-c', '--change', metavar='rev',
1822 help='the change made by revision rev (like -r rev-1:rev).'
1823 'If rev is negative this is like -r rev:rev-1.')
1824 @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
1825 help='If rev1 is specified it will compare your working copy against '
1826 'the revision (rev1) on the server. '
1827 'If rev1 and rev2 are specified it will compare rev1 against rev2 '
1828 '(NOTE: changes in your working copy are ignored in this case)')
1829 @cmdln.option('-p', '--plain', action='store_true',
1830 help='output the diff in plain (not unified) diff format')
1831 def do_diff(self, subcmd, opts, *args):
1832 """${cmd_name}: Generates a diff
1834 Generates a diff, comparing local changes against the repository
1837 ARG, specified, is a filename to include in the diff.
1843 args = parseargs(args)
1844 pacs = findpacs(args)
1848 rev = int(opts.change)
1858 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1861 rev1, rev2 = parseRevisionOption(opts.revision)
1865 diff += ''.join(make_diff(pac, rev1))
1867 diff += server_diff(pac.apiurl, pac.prjname, pac.name, rev1,
1868 pac.prjname, pac.name, rev2, not opts.plain)
1873 @cmdln.option('--oldprj', metavar='OLDPRJ',
1874 help='project to compare against'
1875 ' (deprecated, use 3 argument form)')
1876 @cmdln.option('--oldpkg', metavar='OLDPKG',
1877 help='package to compare against'
1878 ' (deprecated, use 3 argument form)')
1879 @cmdln.option('-r', '--revision', metavar='N[:M]',
1880 help='revision id, where N = old revision and M = new revision')
1881 @cmdln.option('-p', '--plain', action='store_true',
1882 help='output the diff in plain (not unified) diff format')
1883 @cmdln.option('-c', '--change', metavar='rev',
1884 help='the change made by revision rev (like -r rev-1:rev). '
1885 'If rev is negative this is like -r rev:rev-1.')
1886 def do_rdiff(self, subcmd, opts, *args):
1887 """${cmd_name}: Server-side "pretty" diff of two packages
1889 Compares two packages (three or four arguments) or shows the
1890 changes of a specified revision of a package (two arguments)
1892 If no revision is specified the latest revision is used.
1894 Note that this command doesn't return a normal diff (which could be
1895 applied as patch), but a "pretty" diff, which also compares the content
1900 osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC]
1901 osc ${cmd_name} PROJECT PACKAGE
1905 args = slash_split(args)
1916 new_project = args[0]
1917 new_package = args[1]
1919 old_project = opts.oldprj
1921 old_package = opts.oldpkg
1922 elif len(args) == 3 or len(args) == 4:
1923 if opts.oldprj or opts.oldpkg:
1924 raise oscerr.WrongArgs('--oldpkg and --oldprj are only valid with two arguments')
1925 old_project = args[0]
1926 new_package = old_package = args[1]
1927 new_project = args[2]
1929 new_package = args[3]
1931 raise oscerr.WrongArgs('Wrong number of arguments')
1936 rev = int(opts.change)
1946 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1950 rev1, rev2 = parseRevisionOption(opts.revision)
1952 rdiff = server_diff(conf.config['apiurl'],
1953 old_project, old_package, rev1,
1954 new_project, new_package, rev2, not opts.plain)
1960 def do_install(self, subcmd, opts, *args):
1961 """${cmd_name}: install a package after build via zypper in -r
1963 Not implemented yet. Use osc repourls,
1964 select the url you best like (standard),
1965 chop off after the last /, this should work with zypper.
1972 args = slash_split(args)
1973 args = expand_proj_pack(args)
1976 ## if there is only one argument, and it ends in .ymp
1977 ## then fetch it, Parse XML to get the first
1978 ## metapackage.group.repositories.repository.url
1979 ## and construct zypper cmd's for all
1980 ## metapackage.group.software.item.name
1982 ## if args[0] is already an url, the use it as is.
1984 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])
1985 print self.do_install.__doc__
1986 print "Example: \n" + cmd
1989 def do_repourls(self, subcmd, opts, *args):
1990 """${cmd_name}: Shows URLs of .repo files
1992 Shows URLs on which to access the project .repos files (yum-style
1993 metadata) on download.opensuse.org.
1996 osc repourls [PROJECT]
2001 apiurl = conf.config['apiurl']
2005 elif len(args) == 0:
2006 project = store_read_project('.')
2007 apiurl = store_read_apiurl('.')
2009 raise oscerr.WrongArgs('Wrong number of arguments')
2011 # XXX: API should somehow tell that
2012 url_tmpl = 'http://download.opensuse.org/repositories/%s/%s/%s.repo'
2013 repos = get_repositories_of_project(apiurl, project)
2015 print url_tmpl % (project.replace(':', ':/'), repo, project)
2018 @cmdln.option('-r', '--revision', metavar='rev',
2019 help='checkout the specified revision. '
2020 'NOTE: if you checkout the complete project '
2021 'this option is ignored!')
2022 @cmdln.option('-e', '--expand-link', action='store_true',
2023 help='if a package is a link, check out the expanded '
2024 'sources (no-op, since this became the default)')
2025 @cmdln.option('-u', '--unexpand-link', action='store_true',
2026 help='if a package is a link, check out the _link file ' \
2027 'instead of the expanded sources')
2028 @cmdln.option('-c', '--current-dir', action='store_true',
2029 help='place PACKAGE folder in the current directory' \
2030 'instead of a PROJECT/PACKAGE directory')
2031 @cmdln.option('-s', '--source-service-files', action='store_true',
2032 help='server side generated files of source services' \
2033 'gets downloaded as well' )
2035 def do_checkout(self, subcmd, opts, *args):
2036 """${cmd_name}: Check out content from the repository
2038 Check out content from the repository server, creating a local working
2041 When checking out a single package, the option --revision can be used
2042 to specify a revision of the package to be checked out.
2044 When a package is a source link, then it will be checked out in
2045 expanded form. If --unexpand-link option is used, the checkout will
2046 instead produce the raw _link file plus patches.
2049 osc co PROJECT [PACKAGE] [FILE]
2050 osc co PROJECT # entire project
2051 osc co PROJECT PACKAGE # a package
2052 osc co PROJECT PACKAGE FILE # single file -> to current dir
2054 while inside a project directory:
2055 osc co PACKAGE # check out PACKAGE from project
2060 if opts.unexpand_link:
2064 if opts.source_service_files:
2065 service_files = True
2067 service_files = False
2069 args = slash_split(args)
2070 project = package = filename = None
2071 apiurl = conf.config['apiurl']
2073 project = project_dir = args[0]
2079 if args and len(args) == 1:
2080 localdir = os.getcwd()
2081 if is_project_dir(localdir):
2082 project = store_read_project(localdir)
2083 project_dir = localdir
2085 apiurl = store_read_apiurl(localdir)
2087 rev, dummy = parseRevisionOption(opts.revision)
2091 if rev and rev != "latest" and not checkRevision(project, package, rev):
2092 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2096 get_source_file(apiurl, project, package, filename, revision=rev, progress_obj=self.download_progress)
2099 if opts.current_dir:
2101 checkout_package(apiurl, project, package, rev, expand_link=expand_link, \
2102 prj_dir=project_dir, service_files=service_files, progress_obj=self.download_progress)
2103 print_request_list(apiurl, project, package)
2107 if sys.platform[:3] == 'win':
2108 prj_dir = prj_dir.replace(':', ';')
2109 if os.path.exists(prj_dir):
2110 sys.exit('osc: project \'%s\' already exists' % project)
2112 # check if the project does exist (show_project_meta will throw an exception)
2113 show_project_meta(apiurl, project)
2115 init_project_dir(apiurl, prj_dir, project)
2116 print statfrmt('A', prj_dir)
2119 for package in meta_get_packagelist(apiurl, project):
2121 checkout_package(apiurl, project, package, expand_link = expand_link, \
2122 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress)
2123 except oscerr.LinkExpandError, e:
2124 print >>sys.stderr, 'Link cannot be expanded:\n', e
2125 print >>sys.stderr, 'Use "osc repairlink" for fixing merge conflicts:\n'
2126 # check out in unexpanded form at least
2127 checkout_package(apiurl, project, package, expand_link = False, \
2128 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress)
2129 print_request_list(apiurl, project)
2132 raise oscerr.WrongArgs('Missing argument.\n\n' \
2133 + self.get_cmd_help('checkout'))
2136 @cmdln.option('-q', '--quiet', action='store_true',
2137 help='print as little as possible')
2138 @cmdln.option('-v', '--verbose', action='store_true',
2139 help='print extra information')
2141 def do_status(self, subcmd, opts, *args):
2142 """${cmd_name}: Show status of files in working copy
2144 Show the status of files in a local working copy, indicating whether
2145 files have been changed locally, deleted, added, ...
2147 The first column in the output specifies the status and is one of the
2148 following characters:
2149 ' ' no modifications
2154 '?' item is not under version control
2155 '!' item is missing (removed by non-osc command) or incomplete
2160 osc st file1 file2 ...
2163 osc status [OPTS] [PATH...]
2167 args = parseargs(args)
2169 # storage for single Package() objects
2171 # storage for a project dir ( { prj_instance : [ package objects ] } )
2174 # when 'status' is run inside a project dir, it should
2175 # stat all packages existing in the wc
2176 if is_project_dir(arg):
2177 prj = Project(arg, False)
2179 if conf.config['do_package_tracking']:
2181 for pac in prj.pacs_have:
2182 # we cannot create package objects if the dir does not exist
2183 if not pac in prj.pacs_broken:
2184 prjpacs[prj].append(os.path.join(arg, pac))
2186 pacpaths += [arg + '/' + n for n in prj.pacs_have]
2187 elif is_package_dir(arg):
2188 pacpaths.append(arg)
2189 elif os.path.isfile(arg):
2190 pacpaths.append(arg)
2192 msg = '\'%s\' is neither a project or a package directory' % arg
2193 raise oscerr.NoWorkingCopy, msg
2195 # process single packages
2196 lines = getStatus(findpacs(pacpaths), None, opts.verbose, opts.quiet)
2197 # process project dirs
2198 for prj, pacs in prjpacs.iteritems():
2199 lines += getStatus(findpacs(pacs), prj, opts.verbose, opts.quiet)
2201 print '\n'.join(lines)
2204 def do_add(self, subcmd, opts, *args):
2205 """${cmd_name}: Mark files to be added upon the next commit
2208 osc add FILE [FILE...]
2212 raise oscerr.WrongArgs('Missing argument.\n\n' \
2213 + self.get_cmd_help('add'))
2215 filenames = parseargs(args)
2219 def do_mkpac(self, subcmd, opts, *args):
2220 """${cmd_name}: Create a new package under version control
2223 osc mkpac new_package
2226 if not conf.config['do_package_tracking']:
2227 print >>sys.stderr, "to use this feature you have to enable \'do_package_tracking\' " \
2228 "in the [general] section in the configuration file"
2232 raise oscerr.WrongArgs('Wrong number of arguments.')
2234 createPackageDir(args[0])
2236 @cmdln.option('-r', '--recursive', action='store_true',
2237 help='If CWD is a project dir then scan all package dirs as well')
2239 def do_addremove(self, subcmd, opts, *args):
2240 """${cmd_name}: Adds new files, removes disappeared files
2242 Adds all files new in the local copy, and removes all disappeared files.
2244 ARG, if specified, is a package working copy.
2250 args = parseargs(args)
2252 for arg in arg_list:
2253 if is_project_dir(arg) and conf.config['do_package_tracking']:
2254 prj = Project(arg, False)
2255 for pac in prj.pacs_unvers:
2256 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2257 if os.path.isdir(pac_dir):
2258 addFiles([pac_dir], prj)
2259 for pac in prj.pacs_broken:
2260 if prj.get_state(pac) != 'D':
2261 prj.set_state(pac, 'D')
2262 print statfrmt('D', getTransActPath(os.path.join(prj.dir, pac)))
2264 for pac in prj.pacs_have:
2265 state = prj.get_state(pac)
2266 if state != None and state != 'D':
2267 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2268 args.append(pac_dir)
2270 prj.write_packages()
2271 elif is_project_dir(arg):
2272 print >>sys.stderr, 'osc: addremove is not supported in a project dir unless ' \
2273 '\'do_package_tracking\' is enabled in the configuration file'
2276 pacs = findpacs(args)
2278 p.todo = p.filenamelist + p.filenamelist_unvers
2280 for filename in p.todo:
2281 if os.path.isdir(filename):
2283 # ignore foo.rXX, foo.mine for files which are in 'C' state
2284 if os.path.splitext(filename)[0] in p.in_conflict:
2286 state = p.status(filename)
2289 # TODO: should ignore typical backup files suffix ~ or .orig
2291 print statfrmt('A', getTransActPath(os.path.join(p.dir, filename)))
2293 p.put_on_deletelist(filename)
2294 p.write_deletelist()
2295 os.unlink(os.path.join(p.storedir, filename))
2296 print statfrmt('D', getTransActPath(os.path.join(p.dir, filename)))
2301 @cmdln.alias('checkin')
2302 @cmdln.option('-m', '--message', metavar='TEXT',
2303 help='specify log message TEXT')
2304 @cmdln.option('-F', '--file', metavar='FILE',
2305 help='read log message from FILE')
2306 @cmdln.option('-f', '--force', default=False, action="store_true",
2307 help='force commit - do not tests a file list')
2308 def do_commit(self, subcmd, opts, *args):
2309 """${cmd_name}: Upload content to the repository server
2311 Upload content which is changed in your working copy, to the repository
2314 Optionally checks the state of a working copy, if found a file with
2315 unknown state, it requests an user input:
2316 * skip - don't change anything, just move to another file
2317 * remove - remove a file from dir
2318 * edit file list - edit filelist using EDITOR
2319 * commit - don't check anything and commit package
2320 * abort - abort commit - this is default value
2321 This can be supressed by check_filelist config item, or -f/--force
2322 command line option.
2325 osc ci # current dir
2327 osc ci file1 file2 ...
2333 args = parseargs(args)
2340 msg = open(opts.file).read()
2342 sys.exit('could not open file \'%s\'.' % opts.file)
2345 for arg in arg_list:
2346 if conf.config['do_package_tracking'] and is_project_dir(arg):
2347 Project(arg).commit(msg=msg)
2349 msg = edit_message()
2352 pacs = findpacs(args)
2354 if conf.config['check_filelist'] and not opts.force:
2355 check_filelist_before_commit(pacs)
2358 template = store_read_file(os.path.abspath('.'), '_commit_msg')
2359 # open editor for commit message
2360 # but first, produce status and diff to append to the template
2364 changed = getStatus([pac], quiet=True)
2367 diffs += ['\nDiff for working copy: %s' % pac.dir]
2368 diffs += make_diff(pac, 0)
2369 lines.extend(get_commit_message_template(pac))
2370 if template == None:
2371 template='\n'.join(lines)
2372 # if footer is empty, there is nothing to commit, and no edit needed.
2374 msg = edit_message(footer='\n'.join(footer), template=template)
2377 store_write_string(os.path.abspath('.'), '_commit_msg', msg)
2379 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2381 if conf.config['do_package_tracking'] and len(pacs) > 0:
2385 # it is possible to commit packages from different projects at the same
2386 # time: iterate over all pacs and put each pac to the right project in the dict
2388 path = os.path.normpath(os.path.join(pac.dir, os.pardir))
2389 if is_project_dir(path):
2390 pac_path = os.path.basename(os.path.normpath(pac.absdir))
2391 prj_paths.setdefault(path, []).append(pac_path)
2392 files[pac_path] = pac.todo
2394 single_paths.append(pac.dir)
2395 for prj, packages in prj_paths.iteritems():
2396 Project(prj).commit(tuple(packages), msg, files)
2397 for pac in single_paths:
2398 Package(pac).commit(msg)
2403 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2405 @cmdln.option('-r', '--revision', metavar='REV',
2406 help='update to specified revision (this option will be ignored '
2407 'if you are going to update the complete project or more than '
2409 @cmdln.option('-u', '--unexpand-link', action='store_true',
2410 help='if a package is an expanded link, update to the raw _link file')
2411 @cmdln.option('-e', '--expand-link', action='store_true',
2412 help='if a package is a link, update to the expanded sources')
2413 @cmdln.option('-s', '--source-service-files', action='store_true',
2414 help='Use server side generated sources instead of local generation.' )
2416 def do_update(self, subcmd, opts, *args):
2417 """${cmd_name}: Update a working copy
2422 If the current working directory is a package, update it.
2423 If the directory is a project directory, update all contained
2424 packages, AND check out newly added packages.
2426 To update only checked out packages, without checking out new
2427 ones, you might want to use "osc up *" from within the project
2431 Update the packages specified by the path argument(s)
2433 When --expand-link is used with source link packages, the expanded
2434 sources will be checked out. Without this option, the _link file and
2435 patches will be checked out. The option --unexpand-link can be used to
2436 switch back to the "raw" source with a _link file plus patch(es).
2442 if (opts.expand_link and opts.unexpand_link) \
2443 or (opts.expand_link and opts.revision) \
2444 or (opts.unexpand_link and opts.revision):
2445 raise oscerr.WrongOptions('Sorry, the options --expand-link, --unexpand-link and '
2446 '--revision are mutually exclusive.')
2448 if opts.source_service_files: service_files = True
2449 else: service_files = False
2451 args = parseargs(args)
2454 for arg in arg_list:
2455 if is_project_dir(arg):
2456 prj = Project(arg, progress_obj=self.download_progress)
2458 if conf.config['do_package_tracking']:
2459 prj.update(expand_link=opts.expand_link,
2460 unexpand_link=opts.unexpand_link)
2463 # if not tracking package, and 'update' is run inside a project dir,
2464 # it should do the following:
2465 # (a) update all packages
2466 args += prj.pacs_have
2467 # (b) fetch new packages
2468 prj.checkout_missing_pacs(expand_link = not opts.unexpand_link)
2470 print_request_list(prj.apiurl, prj.name)
2473 pacs = findpacs(args, progress_obj=self.download_progress)
2475 if opts.revision and len(args) == 1:
2476 rev, dummy = parseRevisionOption(opts.revision)
2477 if not checkRevision(pacs[0].prjname, pacs[0].name, rev, pacs[0].apiurl):
2478 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2485 print 'Updating %s' % p.name
2487 # FIXME: ugly workaround for #399247
2488 if opts.expand_link or opts.unexpand_link:
2489 if [ i for i in p.filenamelist+p.filenamelist_unvers if p.status(i) != ' ' and p.status(i) != '?']:
2490 print >>sys.stderr, 'osc: cannot expand/unexpand because your working ' \
2491 'copy has local modifications.\nPlease revert/commit them ' \
2496 if opts.expand_link and p.islink() and not p.isexpanded():
2497 if p.haslinkerror():
2499 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev)
2501 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev, linkrev="base")
2504 rev = p.linkinfo.xsrcmd5
2505 print 'Expanding to rev', rev
2506 elif opts.unexpand_link and p.islink() and p.isexpanded():
2507 print 'Unexpanding to rev', p.linkinfo.lsrcmd5
2508 rev = p.linkinfo.lsrcmd5
2509 elif p.islink() and p.isexpanded():
2510 rev = p.latest_rev()
2512 p.update(rev, service_files)
2513 if opts.unexpand_link:
2516 print_request_list(p.apiurl, p.prjname, p.name)
2519 @cmdln.option('-f', '--force', action='store_true',
2520 help='forces removal of entire package and its files')
2523 @cmdln.alias('remove')
2524 def do_delete(self, subcmd, opts, *args):
2525 """${cmd_name}: Mark files or package directories to be deleted upon the next 'checkin'
2528 cd .../PROJECT/PACKAGE
2529 osc delete FILE [...]
2531 osc delete PACKAGE [...]
2533 This command works on check out copies. Use "rdelete" for working on server
2534 side only. This is needed for removing the entire project.
2536 As a safety measure, projects must be empty (i.e., you need to delete all
2539 If you are sure that you want to remove a package and all
2540 its files use \'--force\' switch. Sometimes this also works without --force.
2546 raise oscerr.WrongArgs('Missing argument.\n\n' \
2547 + self.get_cmd_help('delete'))
2549 args = parseargs(args)
2550 # check if args contains a package which was removed by
2551 # a non-osc command and mark it with the 'D'-state
2554 if not os.path.exists(i):
2555 prj_dir, pac_dir = getPrjPacPaths(i)
2556 if is_project_dir(prj_dir):
2557 prj = Project(prj_dir, False)
2558 if i in prj.pacs_broken:
2559 if prj.get_state(i) != 'A':
2560 prj.set_state(pac_dir, 'D')
2562 prj.del_package_node(i)
2563 print statfrmt('D', getTransActPath(i))
2565 prj.write_packages()
2566 pacs = findpacs(args)
2570 prj_dir, pac_dir = getPrjPacPaths(p.absdir)
2571 if is_project_dir(prj_dir):
2572 if conf.config['do_package_tracking']:
2573 prj = Project(prj_dir, False)
2574 prj.delPackage(p, opts.force)
2576 print "WARNING: package tracking is disabled, operation skipped !"
2578 pathn = getTransActPath(p.dir)
2579 for filename in p.todo:
2580 ret, state = p.delete_file(filename, opts.force)
2582 print statfrmt('D', os.path.join(pathn, filename))
2585 sys.exit('\'%s\' is not under version control' % filename)
2586 elif state in ['A', 'M'] and not opts.force:
2587 sys.exit('\'%s\' has local modifications (use --force to remove this file)' % filename)
2590 def do_resolved(self, subcmd, opts, *args):
2591 """${cmd_name}: Remove 'conflicted' state on working copy files
2593 If an upstream change can't be merged automatically, a file is put into
2594 in 'conflicted' ('C') state. Within the file, conflicts are marked with
2595 special <<<<<<< as well as ======== and >>>>>>> lines.
2597 After manually resolving all conflicting parts, use this command to
2598 remove the 'conflicted' state.
2600 Note: this subcommand does not semantically resolve conflicts or
2601 remove conflict markers; it merely removes the conflict-related
2602 artifact files and allows PATH to be committed again.
2605 osc resolved FILE [FILE...]
2610 raise oscerr.WrongArgs('Missing argument.\n\n' \
2611 + self.get_cmd_help('resolved'))
2613 args = parseargs(args)
2614 pacs = findpacs(args)
2617 for filename in p.todo:
2618 print 'Resolved conflicted state of "%s"' % filename
2619 p.clear_from_conflictlist(filename)
2622 @cmdln.alias('platforms')
2623 def do_repositories(self, subcmd, opts, *args):
2624 """${cmd_name}: Shows available repositories
2628 Shows all available repositories/build targets
2630 2. osc repositories <project>
2631 Shows the configured repositories/build targets of a project
2639 print '\n'.join(get_repositories_of_project(conf.config['apiurl'], project))
2641 print '\n'.join(get_repositories(conf.config['apiurl']))
2645 def do_results_meta(self, subcmd, opts, *args):
2646 print "Command results_meta is obsolete. Please use: osc results --xml"
2650 @cmdln.option('-l', '--last-build', action='store_true',
2651 help='show last build results (succeeded/failed/unknown)')
2652 @cmdln.option('-r', '--repo', action='append', default = [],
2653 help='Show results only for specified repo(s)')
2654 @cmdln.option('-a', '--arch', action='append', default = [],
2655 help='Show results only for specified architecture(s)')
2656 @cmdln.option('', '--xml', action='store_true',
2657 help='generate output in XML (former results_meta)')
2658 def do_rresults(self, subcmd, opts, *args):
2659 print "Command rresults is obsolete. Running 'osc results' instead"
2660 self.do_results('results', opts, *args)
2664 @cmdln.option('-f', '--force', action='store_true', default=False,
2665 help="Don't ask and delete files")
2666 def do_rremove(self, subcmd, opts, project, package, *files):
2667 """${cmd_name}: Remove source files from selected package
2674 if not '/' in project:
2675 raise oscerr.WrongArgs("Missing operand, type osc help rremove for help")
2678 project, package = project.split('/')
2682 resp = raw_input("rm: remove source file `%s' from `%s/%s'? (yY|nN) " % (file, project, package))
2683 if resp not in ('y', 'Y'):
2686 delete_files(conf.config['apiurl'], project, package, (file, ))
2687 except urllib2.HTTPError, e:
2689 print >>sys.stderr, e
2691 if e.code in [ 400, 403, 404, 500 ]:
2692 if '<summary>' in body:
2693 msg = body.split('<summary>')[1]
2694 msg = msg.split('</summary>')[0]
2695 print >>sys.stderr, msg
2700 @cmdln.option('-l', '--last-build', action='store_true',
2701 help='show last build results (succeeded/failed/unknown)')
2702 @cmdln.option('-r', '--repo', action='append', default = [],
2703 help='Show results only for specified repo(s)')
2704 @cmdln.option('-a', '--arch', action='append', default = [],
2705 help='Show results only for specified architecture(s)')
2706 @cmdln.option('', '--xml', action='store_true',
2707 help='generate output in XML (former results_meta)')
2708 def do_results(self, subcmd, opts, *args):
2709 """${cmd_name}: Shows the build results of a package
2712 osc results (inside working copy)
2713 osc results remote_project remote_package
2718 args = slash_split(args)
2720 apiurl = conf.config['apiurl']
2723 if is_project_dir(wd):
2727 opts.hide_legend = None
2728 opts.name_filter = None
2729 opts.status_filter = None
2730 opts.vertical = None
2731 self.do_prjresults('prjresults', opts, *args)
2734 project = store_read_project(wd)
2735 package = store_read_package(wd)
2736 apiurl = store_read_apiurl(wd)
2738 raise oscerr.WrongArgs('Too few arguments (required none or two)')
2740 raise oscerr.WrongArgs('Too many arguments (required none or two)')
2749 func = show_results_meta
2752 print delim.join(func(apiurl, project, package, opts.last_build, opts.repo, opts.arch))
2754 # WARNING: this function is also called by do_results. You need to set a default there
2755 # as well when adding a new option!
2756 @cmdln.option('-q', '--hide-legend', action='store_true',
2757 help='hide the legend')
2758 @cmdln.option('-c', '--csv', action='store_true',
2760 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2761 help='show only packages with buildstatus STATUS (see legend)')
2762 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2763 help='show only packages whose names match EXPR')
2764 @cmdln.option('-a', '--arch', metavar='ARCH',
2765 help='show results only for specified architecture(s)')
2766 @cmdln.option('-r', '--repo', metavar='REPO',
2767 help='show results only for specified repo(s)')
2768 @cmdln.option('-V', '--vertical', action='store_true',
2769 help='list packages vertically instead horizontally')
2771 def do_prjresults(self, subcmd, opts, *args):
2772 """${cmd_name}: Shows project-wide build results
2775 osc prjresults (inside working copy)
2776 osc prjresults PROJECT
2782 apiurl = conf.config['apiurl']
2786 raise oscerr.WrongArgs('Wrong number of arguments.')
2789 project = store_read_project(wd)
2790 apiurl = store_read_apiurl(wd)
2792 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))
2795 @cmdln.option('-q', '--hide-legend', action='store_true',
2796 help='hide the legend')
2797 @cmdln.option('-c', '--csv', action='store_true',
2799 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2800 help='show only packages with buildstatus STATUS (see legend)')
2801 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2802 help='show only packages whose names match EXPR')
2805 def do_rprjresults(self, subcmd, opts, *args):
2806 print "Command rprjresults is obsolete. Please use 'osc prjresults'"
2810 @cmdln.option('-s', '--start', metavar='START',
2811 help='get log starting from the offset')
2812 def do_buildlog(self, subcmd, opts, *args):
2813 """${cmd_name}: Shows the build log of a package
2815 Shows the log file of the build of a package. Can be used to follow the
2816 log while it is being written.
2817 Needs to be called from within a package directory.
2819 The arguments REPOSITORY and ARCH are the first two columns in the 'osc
2820 results' output. If the buildlog url is used buildlog command has the
2821 same behavior as remotebuildlog.
2823 ${cmd_usage} [REPOSITORY ARCH | BUILDLOGURL]
2827 repository = arch = None
2829 if len(args) == 1 and args[0].startswith('http'):
2830 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2833 package = store_read_package(wd)
2834 project = store_read_project(wd)
2835 apiurl = store_read_apiurl(wd)
2839 offset = int(opts.start)
2841 if not repository or not arch:
2845 repository = args[0]
2848 print_buildlog(apiurl, project, package, repository, arch, offset)
2851 def print_repos(self):
2854 if is_package_dir(wd):
2857 elif is_project_dir(wd):
2862 print 'Valid arguments for this %s are:' % str
2864 self.do_repos(None, None)
2866 raise oscerr.WrongArgs('Missing arguments')
2869 @cmdln.alias('rbuildlog')
2870 @cmdln.option('-s', '--start', metavar='START',
2871 help='get log starting from the offset')
2872 def do_remotebuildlog(self, subcmd, opts, *args):
2873 """${cmd_name}: Shows the build log of a package
2875 Shows the log file of the build of a package. Can be used to follow the
2876 log while it is being written.
2879 osc remotebuildlog project package repository arch
2881 osc remotebuildlog project/package/repository/arch
2883 osc remotebuildlog buildlogurl
2886 if len(args) == 1 and args[0].startswith('http'):
2887 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2889 args = slash_split(args)
2890 apiurl = conf.config['apiurl']
2892 raise oscerr.WrongArgs('Too few arguments.')
2894 raise oscerr.WrongArgs('Too many arguments.')
2896 project, package, repository, arch = args
2900 offset = int(opts.start)
2902 print_buildlog(apiurl, project, package, repository, arch, offset)
2905 @cmdln.option('-s', '--start', metavar='START',
2906 help='get log starting from offset')
2907 def do_localbuildlog(self, subcmd, opts, *args):
2908 """${cmd_name}: Shows the build log of a local buildchroot
2911 osc lbl [REPOSITORY ARCH]
2912 osc lbl # show log of newest last local build
2916 if conf.config['build-type']:
2917 # FIXME: raise Exception instead
2918 print >>sys.stderr, 'Not implemented for VMs'
2922 package = store_read_package('.')
2924 files = glob.glob(os.path.join(os.getcwd(), store, "_buildinfo-*"))
2927 raise oscerr.WrongArgs('No buildconfig found, please specify repo and arch manually.')
2931 if os.stat(f).st_mtime > os.stat(cfg).st_mtime:
2933 root = ET.parse(cfg).getroot()
2934 project = root.get("project")
2935 repo = root.get("repository")
2936 arch = root.find("arch").text
2937 elif len(args) == 2:
2938 project = store_read_project('.')
2939 package = store_read_package('.')
2943 if is_package_dir(os.curdir):
2945 raise oscerr.WrongArgs('Wrong number of arguments.')
2947 buildroot = os.environ.get('OSC_BUILD_ROOT', conf.config['build-root'])
2948 buildroot = buildroot % {'project': project, 'package': package,
2949 'repo': repo, 'arch': arch}
2952 offset = int(opts.start)
2953 logfile = os.path.join(buildroot, '.build.log')
2954 if not os.path.isfile(logfile):
2955 raise oscerr.OscIOError(None, 'logfile \'%s\' does not exist' % logfile)
2956 f = open(logfile, 'r')
2958 data = f.read(BUFSIZE)
2960 sys.stdout.write(data)
2961 data = f.read(BUFSIZE)
2965 def do_triggerreason(self, subcmd, opts, *args):
2966 """${cmd_name}: Show reason why a package got triggered to build
2968 The server decides when a package needs to get rebuild, this command
2969 shows the detailed reason for a package. A brief reason is also stored
2970 in the jobhistory, which can be accessed via "osc jobhistory".
2972 Trigger reasons might be:
2973 - new build (never build yet or rebuild manually forced)
2974 - source change (eg. on updating sources)
2975 - meta change (packages which are used for building have changed)
2976 - rebuild count sync (In case that it is configured to sync release numbers)
2978 usage in package or project directory:
2979 osc reason REPOSITORY ARCH
2980 osc reason PROJECT PACKAGE REPOSITORY ARCH
2985 args = slash_split(args)
2986 project = package = repository = arch = None
2991 if len(args) == 2: # 2
2992 if is_package_dir('.'):
2993 package = store_read_package(wd)
2995 raise oscerr.WrongArgs('package is not specified.')
2996 project = store_read_project(wd)
2997 apiurl = store_read_apiurl(wd)
2998 repository = args[0]
3000 elif len(args) == 4:
3001 apiurl = conf.config['apiurl']
3004 repository = args[2]
3007 raise oscerr.WrongArgs('Too many arguments.')
3009 print apiurl, project, package, repository, arch
3010 xml = show_package_trigger_reason(apiurl, project, package, repository, arch)
3011 root = ET.fromstring(xml)
3012 reason = root.find('explain').text
3014 if reason == "meta change":
3015 print "changed keys:"
3016 for package in root.findall('packagechange'):
3017 print " ", package.get('change'), package.get('key')
3020 # FIXME: the new osc syntax should allow to specify multiple packages
3021 # FIXME: the command should optionally use buildinfo data to show all dependencies
3022 @cmdln.alias('whatdependson')
3023 def do_dependson(self, subcmd, opts, *args):
3024 """${cmd_name}: Show the build dependencies
3026 The command dependson and whatdependson can be used to find out what
3027 will be triggered when a certain package changes.
3028 This is no guarantee, since the new build might have changed dependencies.
3030 dependson shows the build dependencies inside of a project, valid for a
3031 given repository and architecture.
3032 NOTE: to see all binary packages, which can trigger a build you need to
3033 refer the buildinfo, since this command shows only the dependencies
3034 inside of a project.
3036 The arguments REPOSITORY and ARCH can be taken from the first two columns
3037 of the 'osc repos' output.
3039 usage in package or project directory:
3040 osc dependson REPOSITORY ARCH
3041 osc whatdependson REPOSITORY ARCH
3044 osc dependson PROJECT [PACKAGE] REPOSITORY ARCH
3045 osc whatdependson PROJECT [PACKAGE] REPOSITORY ARCH
3050 args = slash_split(args)
3051 project = packages = repository = arch = reverse = None
3053 if len(args) < 2 and (is_package_dir('.') or is_project_dir('.')):
3057 raise oscerr.WrongArgs('Too many arguments.')
3059 if len(args) < 3: # 2
3060 if is_package_dir('.'):
3061 packages = [store_read_package(wd)]
3062 elif not is_project_dir('.'):
3063 raise oscerr.WrongArgs('Project and package is not specified.')
3064 project = store_read_project(wd)
3065 apiurl = store_read_apiurl(wd)
3066 repository = args[0]
3070 apiurl = conf.config['apiurl']
3072 repository = args[1]
3076 apiurl = conf.config['apiurl']
3078 packages = [args[1]]
3079 repository = args[2]
3082 if subcmd == 'whatdependson':
3085 xml = get_dependson(apiurl, project, repository, arch, packages, reverse)
3087 root = ET.fromstring(xml)
3088 for package in root.findall('package'):
3089 print package.get('name'), ":"
3090 for dep in package.findall('pkgdep'):
3094 @cmdln.option('-x', '--extra-pkgs', metavar='PAC', action='append',
3095 help='Add this package when computing the buildinfo')
3096 def do_buildinfo(self, subcmd, opts, *args):
3097 """${cmd_name}: Shows the build info
3099 Shows the build "info" which is used in building a package.
3100 This command is mostly used internally by the 'build' subcommand.
3101 It needs to be called from within a package directory.