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 link_seen == 0: break
367 m = show_files_meta(conf.config['apiurl'], project, package)
368 xml = ET.fromstring(''.join(m)).find('linkinfo')
369 print "# -> %s %s" % (xml.get('project'), xml.get('package'))
371 if fname and print_not_found:
372 print 'file \'%s\' does not exist' % fname
375 @cmdln.option('-f', '--force', action='store_true',
376 help='force generation of new patchinfo file')
377 @cmdln.option('--force-update', action='store_true',
378 help='drops away collected packages from an already built patch and let it collect again')
379 def do_patchinfo(self, subcmd, opts, *args):
380 """${cmd_name}: Generate and edit a patchinfo file.
382 A patchinfo file describes the packages for an update and the kind of
387 osc patchinfo PATCH_NAME
391 project_dir = localdir = os.getcwd()
392 if is_project_dir(localdir):
393 project = store_read_project(localdir)
394 apiurl = store_read_apiurl(localdir)
396 sys.exit('This command must be called in a checked out project.')
398 for p in meta_get_packagelist(apiurl, project):
399 if p.startswith("_patchinfo:"):
402 if opts.force or not patchinfo:
403 print "Creating initial patchinfo..."
404 query='cmd=createpatchinfo'
406 query += "&name=" + args[0]
407 url = makeurl(apiurl, ['source', project], query=query)
409 for p in meta_get_packagelist(apiurl, project):
410 if p.startswith("_patchinfo:"):
413 if not os.path.exists(project_dir + "/" + patchinfo):
414 checkout_package(apiurl, project, patchinfo, prj_dir=project_dir)
416 if sys.platform[:3] != 'win':
417 editor = os.getenv('EDITOR', default='vim')
419 editor = os.getenv('EDITOR', default='notepad')
420 subprocess.call('%s %s' % (editor, project_dir + "/" + patchinfo + "/_patchinfo"), shell=True)
423 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
424 help='affect only a given attribute')
425 @cmdln.option('--attribute-defaults', action='store_true',
426 help='include defined attribute defaults')
427 @cmdln.option('--attribute-project', action='store_true',
428 help='include project values, if missing in packages ')
429 @cmdln.option('-F', '--file', metavar='FILE',
430 help='read metadata from FILE, instead of opening an editor. '
431 '\'-\' denotes standard input. ')
432 @cmdln.option('-e', '--edit', action='store_true',
433 help='edit metadata')
434 @cmdln.option('-c', '--create', action='store_true',
435 help='create attribute without values')
436 @cmdln.option('-s', '--set', metavar='ATTRIBUTE_VALUES',
437 help='set attribute values')
438 @cmdln.option('--delete', action='store_true',
439 help='delete a pattern or attribute')
440 def do_meta(self, subcmd, opts, *args):
441 """${cmd_name}: Show meta information, or edit it
443 Show or edit build service metadata of type <prj|pkg|prjconf|user|pattern>.
445 This command displays metadata on buildservice objects like projects,
446 packages, or users. The type of metadata is specified by the word after
447 "meta", like e.g. "meta prj".
449 prj denotes metadata of a buildservice project.
450 prjconf denotes the (build) configuration of a project.
451 pkg denotes metadata of a buildservice package.
452 user denotes the metadata of a user.
453 pattern denotes installation patterns defined for a project.
455 To list patterns, use 'osc meta pattern PRJ'. An additional argument
456 will be the pattern file to view or edit.
458 With the --edit switch, the metadata can be edited. Per default, osc
459 opens the program specified by the environmental variable EDITOR with a
460 temporary file. Alternatively, content to be saved can be supplied via
461 the --file switch. If the argument is '-', input is taken from stdin:
462 osc meta prjconf home:user | sed ... | osc meta prjconf home:user -F -
464 When trying to edit a non-existing resource, it is created implicitly.
470 osc meta pkg PRJ PKG -e
471 osc meta attribute PRJ [PKG [SUBPACKAGE]] [--attribute ATTRIBUTE] [--create|--delete|--set [value_list]]
474 osc meta <prj|pkg|prjconf|user|pattern|attribute> ARGS...
475 osc meta <prj|pkg|prjconf|user|pattern|attribute> -e|--edit ARGS...
476 osc meta <prj|pkg|prjconf|user|pattern|attribute> -F|--file ARGS...
477 osc meta pattern --delete PRJ PATTERN
481 args = slash_split(args)
483 if not args or args[0] not in metatypes.keys():
484 raise oscerr.WrongArgs('Unknown meta type. Choose one of %s.' \
485 % ', '.join(metatypes))
491 min_args, max_args = 2, 2
492 elif cmd in ['pattern']:
493 min_args, max_args = 1, 2
494 elif cmd in ['attribute']:
495 min_args, max_args = 1, 3
497 min_args, max_args = 1, 1
498 if len(args) < min_args:
499 raise oscerr.WrongArgs('Too few arguments.')
500 if len(args) > max_args:
501 raise oscerr.WrongArgs('Too many arguments.')
508 project, package = args[0:2]
509 elif cmd == 'attribute':
515 if opts.attribute_project:
516 raise oscerr.WrongOptions('--attribute-project works only when also a package is given')
521 attributepath.append('source')
522 attributepath.append(project)
524 attributepath.append(package)
526 attributepath.append(subpackage)
527 attributepath.append('_attribute')
528 elif cmd == 'prjconf':
532 elif cmd == 'pattern':
538 # enforce pattern argument if needed
539 if opts.edit or opts.file:
540 raise oscerr.WrongArgs('A pattern file argument is required.')
543 if not opts.edit and not opts.file and not opts.delete and not opts.create and not opts.set:
545 sys.stdout.write(''.join(show_project_meta(conf.config['apiurl'], project)))
547 sys.stdout.write(''.join(show_package_meta(conf.config['apiurl'], project, package)))
548 elif cmd == 'attribute':
549 sys.stdout.write(''.join(show_attribute_meta(conf.config['apiurl'], project, package, subpackage, opts.attribute, opts.attribute_defaults, opts.attribute_project)))
550 elif cmd == 'prjconf':
551 sys.stdout.write(''.join(show_project_conf(conf.config['apiurl'], project)))
553 r = get_user_meta(conf.config['apiurl'], user)
555 sys.stdout.write(''.join(r))
556 elif cmd == 'pattern':
558 r = show_pattern_meta(conf.config['apiurl'], project, pattern)
560 sys.stdout.write(''.join(r))
562 r = show_pattern_metalist(conf.config['apiurl'], project)
564 sys.stdout.write('\n'.join(r) + '\n')
567 if opts.edit and not opts.file:
569 edit_meta(metatype='prj',
571 path_args=quote_plus(project),
574 'user': conf.config['user']}))
576 edit_meta(metatype='pkg',
578 path_args=(quote_plus(project), quote_plus(package)),
581 'user': conf.config['user']}))
582 elif cmd == 'prjconf':
583 edit_meta(metatype='prjconf',
585 path_args=quote_plus(project),
588 edit_meta(metatype='user',
590 path_args=(quote_plus(user)),
591 template_args=({'user': user}))
592 elif cmd == 'pattern':
593 edit_meta(metatype='pattern',
595 path_args=(project, pattern),
598 # create attribute entry
599 if (opts.create or opts.set) and cmd == 'attribute':
600 if not opts.attribute:
601 raise oscerr.WrongOptions('no attribute given to create')
604 opts.set = opts.set.replace('&', '&').replace('<', '<').replace('>', '>')
605 for i in opts.set.split(','):
606 values += '<value>%s</value>' % i
607 aname = opts.attribute.split(":")
608 d = '<attributes><attribute namespace=\'%s\' name=\'%s\' >%s</attribute></attributes>' % (aname[0], aname[1], values)
609 url = makeurl(conf.config['apiurl'], attributepath)
610 for data in streamfile(url, http_POST, data=d):
611 sys.stdout.write(data)
620 f = open(opts.file).read()
622 sys.exit('could not open file \'%s\'.' % opts.file)
625 edit_meta(metatype='prj',
628 path_args=quote_plus(project))
630 edit_meta(metatype='pkg',
633 path_args=(quote_plus(project), quote_plus(package)))
634 elif cmd == 'prjconf':
635 edit_meta(metatype='prjconf',
638 path_args=quote_plus(project))
640 edit_meta(metatype='user',
643 path_args=(quote_plus(user)))
644 elif cmd == 'pattern':
645 edit_meta(metatype='pattern',
648 path_args=(project, pattern))
653 path = metatypes[cmd]['path']
655 path = path % (project, pattern)
656 u = makeurl(conf.config['apiurl'], [path])
658 elif cmd == 'attribute':
659 if not opts.attribute:
660 raise oscerr.WrongOptions('no attribute given to create')
661 attributepath.append(opts.attribute)
662 u = makeurl(conf.config['apiurl'], attributepath)
663 for data in streamfile(u, http_DELETE):
664 sys.stdout.write(data)
666 raise oscerr.WrongOptions('The --delete switch is only for pattern metadata or attributes.')
669 @cmdln.option('-m', '--message', metavar='TEXT',
670 help='specify message TEXT')
671 @cmdln.option('-r', '--revision', metavar='REV',
672 help='for "create", specify a certain source revision ID (the md5 sum)')
673 @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
674 help='Superseding another request by this one')
675 @cmdln.option('--nodevelproject', action='store_true',
676 help='do not follow a defined devel project ' \
677 '(primary project where a package is developed)')
678 @cmdln.option('--cleanup', action='store_true',
679 help='remove package if submission gets accepted (default for home:<id>:branch projects)')
680 @cmdln.option('--no-cleanup', action='store_true',
681 help='never remove source package on accept, but update its content')
682 @cmdln.option('--no-update', action='store_true',
683 help='never touch source package on accept (will break source links)')
684 @cmdln.option('-d', '--diff', action='store_true',
685 help='show diff only instead of creating the actual request')
686 @cmdln.option('--yes', action='store_true',
687 help='proceed without asking.')
689 @cmdln.alias("submitreq")
690 @cmdln.alias("submitpac")
691 def do_submitrequest(self, subcmd, opts, *args):
692 """${cmd_name}: Create request to submit source into another Project
694 [See http://en.opensuse.org/Build_Service/Collaboration for information
697 See the "request" command for showing and modifing existing requests.
700 osc submitreq [OPTIONS]
701 osc submitreq [OPTIONS] DESTPRJ [DESTPKG]
702 osc submitreq [OPTIONS] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
706 src_update = conf.config['submitrequest_on_accept_action'] or None
707 # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
709 src_update = "cleanup"
710 elif opts.no_cleanup:
711 src_update = "update"
713 src_update = "noupdate"
715 args = slash_split(args)
717 # remove this block later again
718 oldcmds = ['create', 'list', 'log', 'show', 'decline', 'accept', 'delete', 'revoke']
719 if args and args[0] in oldcmds:
720 print "************************************************************************"
721 print "* WARNING: It looks that you are using this command with a *"
722 print "* deprecated syntax. *"
723 print "* Please run \"osc sr --help\" and \"osc rq --help\" *"
724 print "* to see the new syntax. *"
725 print "************************************************************************"
726 if args[0] == 'create':
732 raise oscerr.WrongArgs('Too many arguments.')
734 if len(args) > 0 and len(args) <= 2 and is_project_dir(os.getcwd()):
735 sys.exit('osc submitrequest from project directory is only working without target specs and for source linked files\n')
737 apiurl = conf.config['apiurl']
739 if len(args) == 0 and is_project_dir(os.getcwd()):
741 # submit requests for multiple packages are currently handled via multiple requests
742 # They could be also one request with multiple actions, but that avoids to accepts parts of it.
743 project = store_read_project(os.curdir)
744 apiurl = store_read_apiurl(os.curdir)
750 # loop via all packages for checking their state
751 for p in meta_get_packagelist(apiurl, project):
752 if p.startswith("_patchinfo:"):
755 # get _link info from server, that knows about the local state ...
756 u = makeurl(apiurl, ['source', project, p])
758 root = ET.parse(f).getroot()
759 linkinfo = root.find('linkinfo')
761 print "Package ", p, " is not a source link."
762 sys.exit("This is currently not supported.")
763 if linkinfo.get('error'):
764 print "Package ", p, " is a broken source link."
765 sys.exit("Please fix this first")
766 t = linkinfo.get('project')
768 if len(root.findall('entry')) > 1: # This is not really correct, but should work mostly
769 # Real fix is to ask the api if sources are modificated
770 # but there is no such call yet.
771 targetprojects.append(t)
773 print "Submitting package ", p
775 print " Skipping package ", p
777 print "Skipping package ", p, " since it is a source link pointing inside the project."
781 print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
782 print "\nEverything fine? Can we create the requests ? [y/n]"
783 if sys.stdin.read(1) != "y":
784 sys.exit("Aborted...")
786 # loop via all packages to do the action
788 result = create_submit_request(apiurl, project, p)
791 sys.exit("submit request creation failed")
792 sr_ids.append(result)
794 # create submit requests for all found patchinfos
798 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
801 for t in targetprojects:
802 s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \
803 (project, p, t, p, options_block)
807 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
808 (actionxml, cgi.escape(opts.message or ""))
809 u = makeurl(apiurl, ['request'], query='cmd=create')
810 f = http_POST(u, data=xml)
812 root = ET.parse(f).getroot()
813 sr_ids.append(root.get('id'))
815 print "Requests created: ",
818 sys.exit('Successfull finished')
821 # try using the working copy at hand
822 p = findpacs(os.curdir)[0]
823 src_project = p.prjname
826 if len(args) == 0 and p.islink():
827 dst_project = p.linkinfo.project
828 dst_package = p.linkinfo.package
830 dst_project = args[0]
832 dst_package = args[1]
834 dst_package = src_package
836 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
837 'Please provide it the target via commandline arguments.' % p.name)
839 modified = [i for i in p.filenamelist if p.status(i) != ' ' and p.status(i) != '?']
840 if len(modified) > 0:
841 print 'Your working copy has local modifications.'
842 repl = raw_input('Proceed without committing the local changes? (y|N) ')
846 # get the arguments from the commandline
847 src_project, src_package, dst_project = args[0:3]
849 dst_package = args[3]
851 dst_package = src_package
853 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
854 + self.get_cmd_help('request'))
856 if not opts.nodevelproject:
859 devloc = show_develproject(apiurl, dst_project, dst_package)
860 except urllib2.HTTPError:
861 print >>sys.stderr, """\
862 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
863 % (dst_project, dst_package)
867 dst_project != devloc and \
868 src_project != devloc:
870 A different project, %s, is defined as the place where development
871 of the package %s primarily takes place.
872 Please submit there instead, or use --nodevelproject to force direct submission.""" \
873 % (devloc, dst_package)
878 if opts.diff or not opts.message:
880 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
881 rdiff += server_diff(apiurl,
882 dst_project, dst_package, opts.revision,
883 src_project, src_package, None, True)
889 reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit')
890 user = conf.get_apiurl_usr(apiurl)
891 myreqs = [ i for i in reqs if i.state.who == user ]
894 print 'You already created the following submit request: %s.' % \
895 ', '.join([str(i.reqid) for i in myreqs ])
896 repl = raw_input('Supersede the old requests? (y/n/c) ')
897 if repl.lower() == 'c':
898 print >>sys.stderr, 'Aborting'
904 changes_re = re.compile(r'^--- .*\.changes ')
905 for line in rdiff.split('\n'):
906 if line.startswith('--- '):
907 if changes_re.match(line):
912 difflines.append(line)
913 opts.message = edit_message(footer=rdiff, template='\n'.join(parse_diff_for_commit_message('\n'.join(difflines))))
915 result = create_submit_request(apiurl,
916 src_project, src_package,
917 dst_project, dst_package,
918 opts.message, orev=opts.revision, src_update=src_update)
919 if repl.lower() == 'y':
921 change_request_state(apiurl, str(req.reqid), 'superseded',
922 'superseded by %s' % result, result)
925 r = change_request_state(conf.config['apiurl'],
926 opts.supersede, 'superseded', opts.message or '', result)
928 print 'created request id', result
931 @cmdln.option('-m', '--message', metavar='TEXT',
932 help='specify message TEXT')
934 @cmdln.alias("deletereq")
935 def do_deleterequest(self, subcmd, opts, *args):
936 """${cmd_name}: Create request to delete a package or project
940 osc deletereq [-m TEXT] PROJECT [PACKAGE]
944 args = slash_split(args)
947 raise oscerr.WrongArgs('Please specify at least a project.')
949 raise oscerr.WrongArgs('Too many arguments.')
951 apiurl = conf.config['apiurl']
959 opts.message = edit_message()
961 result = create_delete_request(apiurl, project, package, opts.message)
965 @cmdln.option('-m', '--message', metavar='TEXT',
966 help='specify message TEXT')
968 @cmdln.alias("changedevelreq")
969 def do_changedevelrequest(self, subcmd, opts, *args):
970 """${cmd_name}: Create request to change the devel package definition.
972 [See http://en.opensuse.org/Build_Service/Collaboration for information
975 See the "request" command for showing and modifing existing requests.
977 osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
981 raise oscerr.WrongArgs('Too many arguments.')
983 if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
985 devel_project = store_read_project(wd)
986 devel_package = package = store_read_package(wd)
987 apiurl = store_read_apiurl(wd)
988 project = conf.config['getpac_default_project']
991 raise oscerr.WrongArgs('Too few arguments.')
993 apiurl = conf.config['apiurl']
995 devel_project = args[2]
998 devel_package = package
1000 devel_package = args[3]
1002 if not opts.message:
1004 footer=textwrap.TextWrapper(width = 66).fill(
1005 'please explain why you like to change the devel project of %s/%s to %s/%s'
1006 % (project,package,devel_project,devel_package))
1007 opts.message = edit_message(footer)
1009 result = create_change_devel_request(apiurl,
1010 devel_project, devel_package,
1016 @cmdln.option('-d', '--diff', action='store_true',
1017 help='generate a diff')
1018 @cmdln.option('-u', '--unified', action='store_true',
1019 help='output the diff in the unified diff format')
1020 @cmdln.option('-m', '--message', metavar='TEXT',
1021 help='specify message TEXT')
1022 @cmdln.option('-t', '--type', metavar='TYPE',
1023 help='limit to requests which contain a given action type (submit/delete/change_devel)')
1024 @cmdln.option('-a', '--all', action='store_true',
1025 help='all states. Same as\'-s all\'')
1026 @cmdln.option('-s', '--state', default='', # default is 'all' if no args given, 'new' otherwise
1027 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]')
1028 @cmdln.option('-D', '--days', metavar='DAYS',
1029 help='only list requests in state "new" or changed in the last DAYS. [default=%(request_list_days)s]')
1030 @cmdln.option('-U', '--user', metavar='USER',
1031 help='same as -M, but for the specified USER')
1032 @cmdln.option('-b', '--brief', action='store_true', default=False,
1033 help='print output in list view as list subcommand')
1034 @cmdln.option('-M', '--mine', action='store_true',
1035 help='only show requests created by yourself')
1036 @cmdln.option('-B', '--bugowner', action='store_true',
1037 help='also show requests about packages where I am bugowner')
1038 @cmdln.option('-i', '--interactive', action='store_true',
1039 help='interactive review of request')
1040 @cmdln.option('--non-interactive', action='store_true',
1041 help='non-interactive review of request')
1042 @cmdln.option('--exclude-target-project', action='append',
1043 help='exclude target project from request list')
1044 @cmdln.option('--involved-projects', action='store_true',
1045 help='show all requests for project/packages where USER is involved')
1047 @cmdln.alias("review")
1048 def do_request(self, subcmd, opts, *args):
1049 """${cmd_name}: Show and modify requests
1051 [See http://en.opensuse.org/Build_Service/Collaboration for information
1054 This command shows and modifies existing requests. To create new requests
1055 you need to call one of the following:
1058 osc changedevelrequest
1059 To send low level requests to the buildservice API, use:
1062 This command has the following sub commands:
1064 "list" lists open requests attached to a project or package or person.
1065 Uses the project/package of the current directory if none of
1066 -M, -U USER, project/package are given.
1068 "log" will show the history of the given ID
1070 "show" will show the request itself, and generate a diff for review, if
1071 used with the --diff option. The keyword show can be omitted if the ID is numeric.
1073 "decline" will change the request state to "declined" and append a
1074 message that you specify with the --message option.
1076 "wipe" will permanently delete a request.
1078 "revoke" will set the request state to "revoked" and append a
1079 message that you specify with the --message option.
1081 "accept" will change the request state to "accepted" and will trigger
1082 the actual submit process. That would normally be a server-side copy of
1083 the source package to the target package.
1085 "checkout" will checkout the request's source package. This only works for "submit" requests.
1088 osc request list [-M] [-U USER] [-s state] [-D DAYS] [-t type] [-B] [PRJ [PKG]]
1090 osc request [show] [-d] [-b] ID
1091 osc request accept [-m TEXT] ID
1092 osc request decline [-m TEXT] ID
1093 osc request revoke [-m TEXT] ID
1095 osc request checkout/co ID
1096 osc review accept [-m TEXT] ID
1097 osc review decline [-m TEXT] ID
1101 args = slash_split(args)
1103 if opts.all and opts.state:
1104 raise oscerr.WrongOptions('Sorry, the options \'--all\' and \'--state\' ' \
1105 'are mutually exclusive.')
1106 if opts.mine and opts.user:
1107 raise oscerr.WrongOptions('Sorry, the options \'--user\' and \'--mine\' ' \
1108 'are mutually exclusive.')
1109 if opts.interactive and opts.non_interactive:
1110 raise oscerr.WrongOptions('Sorry, the options \'--interactive\' and ' \
1111 '\'--non-interactive\' are mutually exclusive')
1116 if opts.state == '':
1119 if opts.state == '':
1122 cmds = ['list', 'log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co', 'help']
1123 if not args or args[0] not in cmds:
1124 raise oscerr.WrongArgs('Unknown request action %s. Choose one of %s.' \
1125 % (args[0],', '.join(cmds)))
1131 return self.do_help(['help', 'request'])
1134 min_args, max_args = 1, 1
1135 elif cmd in ['list']:
1136 min_args, max_args = 0, 2
1138 min_args, max_args = 1, 1
1139 if len(args) < min_args:
1140 raise oscerr.WrongArgs('Too few arguments.')
1141 if len(args) > max_args:
1142 raise oscerr.WrongArgs('Too many arguments.')
1144 apiurl = conf.config['apiurl']
1151 elif not opts.mine and not opts.user:
1153 project = store_read_project(os.curdir)
1154 apiurl = store_read_apiurl(os.curdir)
1155 package = store_read_package(os.curdir)
1156 except oscerr.NoWorkingCopy:
1161 elif cmd in ['log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co']:
1166 states = ('new', 'accepted', 'revoked', 'declined')
1167 state_list = opts.state.split(',')
1168 if opts.state == 'all':
1169 state_list = ['all']
1171 for s in state_list:
1173 raise oscerr.WrongArgs('Unknown state \'%s\', try one of %s' % (s, ','.join(states)))
1176 who = conf.get_apiurl_usr(apiurl)
1180 state_list = ['all']
1182 ## FIXME -B not implemented!
1184 if (self.options.debug):
1185 print 'list: option --bugowner ignored: not impl.'
1187 if opts.involved_projects:
1188 who = who or conf.get_apiurl_usr(apiurl)
1189 results = get_user_projpkgs_request_list(apiurl, who, req_state=state_list,
1190 req_type=opts.type, exclude_projects=opts.exclude_target_project or [])
1192 results = get_request_list(apiurl, project, package, who,
1193 state_list, opts.type, opts.exclude_target_project or [])
1194 results.sort(reverse=True)
1196 days = opts.days or conf.config['request_list_days']
1203 since = time.strftime('%Y-%m-%dT%H:%M:%S',time.localtime(time.time()-days*24*3600))
1206 ## bs has received 2009-09-20 a new xquery compare() function
1207 ## which allows us to limit the list inside of get_request_list
1208 ## That would be much faster for coolo. But counting the remainder
1209 ## would not be possible with current xquery implementation.
1210 ## Workaround: fetch all, and filter on client side.
1212 ## FIXME: date filtering should become implemented on server side
1213 for result in results:
1214 if days == 0 or result.state.when > since or result.state.name == 'new':
1215 print result.list_view()
1219 print "There are %d requests older than %s days.\n" % (skipped, days)
1222 for l in get_request_log(conf.config['apiurl'], reqid):
1227 r = get_request(conf.config['apiurl'], reqid)
1230 elif (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive:
1231 return request_interactive_review(conf.config['apiurl'], r)
1234 # fixme: will inevitably fail if the given target doesn't exist
1235 if opts.diff and r.actions[0].type != 'submit':
1236 raise oscerr.WrongOptions('\'--diff\' is not possible for request type: \'%s\'' % r.actions[0].type)
1239 print server_diff(conf.config['apiurl'],
1240 r.actions[0].dst_project, r.actions[0].dst_package, None,
1241 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified)
1242 except urllib2.HTTPError, e:
1243 e.osc_msg = 'Diff not possible'
1247 elif cmd == 'checkout' or cmd == 'co':
1248 r = get_request(conf.config['apiurl'], reqid)
1249 submits = [ i for i in r.actions if i.type == 'submit' ]
1250 if not len(submits):
1251 raise oscerr.WrongArgs('\'checkout\' only works for \'submit\' requests')
1252 checkout_package(conf.config['apiurl'], submits[0].src_project, submits[0].src_package, \
1253 submits[0].src_rev, expand_link=True, prj_dir=submits[0].src_project)
1256 if not opts.message:
1257 opts.message = edit_message()
1258 state_map = {'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked'}
1259 # Change review state only
1260 if subcmd == 'review':
1261 if cmd in ['accept', 'decline']:
1262 r = change_review_state(conf.config['apiurl'],
1263 reqid, state_map[cmd], conf.config['user'], '', opts.message or '')
1265 # Change state of entire request
1266 elif cmd in ['accept', 'decline', 'wipe', 'revoke']:
1267 r = change_request_state(conf.config['apiurl'],
1268 reqid, state_map[cmd], opts.message or '')
1271 # editmeta and its aliases are all depracated
1272 @cmdln.alias("editprj")
1273 @cmdln.alias("createprj")
1274 @cmdln.alias("editpac")
1275 @cmdln.alias("createpac")
1276 @cmdln.alias("edituser")
1277 @cmdln.alias("usermeta")
1279 def do_editmeta(self, subcmd, opts, *args):
1282 Obsolete command to edit metadata. Use 'meta' now.
1284 See the help output of 'meta'.
1288 print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
1289 print >>sys.stderr, 'See \'osc help meta\'.'
1290 #self.do_help([None, 'meta'])
1294 @cmdln.option('-r', '--revision', metavar='rev',
1295 help='use the specified revision.')
1296 @cmdln.option('-u', '--unset', action='store_true',
1297 help='remove revision in link, it will point always to latest revision')
1298 def do_setlinkrev(self, subcmd, opts, *args):
1299 """${cmd_name}: Updates a revision number in a source link.
1301 This command adds or updates a specified revision number in a source link.
1302 The current revision of the source is used, if no revision number is specified.
1306 osc setlinkrev PROJECT [PACKAGE]
1310 args = slash_split(args)
1311 apiurl = conf.config['apiurl']
1314 p = findpacs(os.curdir)[0]
1319 sys.exit('Local directory is no checked out source link package, aborting')
1320 elif len(args) == 2:
1323 elif len(args) == 1:
1326 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1327 + self.get_cmd_help('setlinkrev'))
1330 packages = [ package ]
1332 packages = meta_get_packagelist(apiurl, project)
1335 print "setting revision for package", p
1339 rev, dummy = parseRevisionOption(opts.revision)
1340 set_link_rev(apiurl, project, p, rev)
1343 def do_linktobranch(self, subcmd, opts, *args):
1344 """${cmd_name}: Convert a package containing a classic link with patch to a branch
1346 This command tells the server to convert a _link with or without a project.diff
1347 to a branch. This is a full copy with a _link file pointing to the branched place.
1350 osc linktobranch # can be used in checked out package
1351 osc linktobranch PROJECT PACKAGE
1355 args = slash_split(args)
1358 project = store_read_project(wd)
1359 package = store_read_package(wd)
1360 apiurl = store_read_apiurl(wd)
1361 update_local_dir = True
1363 raise oscerr.WrongArgs('Too few arguments (required none or two)')
1365 raise oscerr.WrongArgs('Too many arguments (required none or two)')
1367 apiurl = conf.config['apiurl']
1370 update_local_dir = False
1373 link_to_branch(apiurl, project, package)
1374 if update_local_dir:
1376 pac.update(rev=pac.latest_rev())
1379 @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'],
1380 help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.')
1381 @cmdln.option('-c', '--current', action='store_true',
1382 help='link fixed against current revision.')
1383 @cmdln.option('-r', '--revision', metavar='rev',
1384 help='link the specified revision.')
1385 @cmdln.option('-f', '--force', action='store_true',
1386 help='overwrite an existing link file if it is there.')
1387 @cmdln.option('-d', '--disable-publish', action='store_true',
1388 help='disable publishing of the linked package')
1389 def do_linkpac(self, subcmd, opts, *args):
1390 """${cmd_name}: "Link" a package to another package
1392 A linked package is a clone of another package, but plus local
1393 modifications. It can be cross-project.
1395 The DESTPAC name is optional; the source packages' name will be used if
1398 Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
1400 To add a patch, add the patch as file and add it to the _link file.
1401 You can also specify text which will be inserted at the top of the spec file.
1403 See the examples in the _link file.
1406 osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1410 args = slash_split(args)
1412 if not args or len(args) < 3:
1413 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1414 + self.get_cmd_help('linkpac'))
1416 rev, dummy = parseRevisionOption(opts.revision)
1418 src_project = args[0]
1419 src_package = args[1]
1420 dst_project = args[2]
1422 dst_package = args[3]
1424 dst_package = src_package
1426 if src_project == dst_project and src_package == dst_package:
1427 raise oscerr.WrongArgs('Error: source and destination are the same.')
1429 if src_project == dst_project and not opts.cicount:
1430 # in this case, the user usually wants to build different spec
1431 # files from the same source
1432 opts.cicount = "copy"
1435 rev = show_upstream_rev(conf.config['apiurl'], src_project, src_package)
1437 if rev and not checkRevision(src_project, src_package, rev):
1438 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
1441 link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish)
1443 @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]',
1444 help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]')
1445 @cmdln.option('-d', '--disable-publish', action='store_true',
1446 help='disable publishing of the aggregated package')
1447 def do_aggregatepac(self, subcmd, opts, *args):
1448 """${cmd_name}: "Aggregate" a package to another package
1450 Aggregation of a package means that the build results (binaries) of a
1451 package are basically copied into another project.
1452 This can be used to make packages available from building that are
1453 needed in a project but available only in a different project. Note
1454 that this is done at the expense of disk space. See
1455 http://en.opensuse.org/Build_Service/Tips_and_Tricks#_link_and__aggregate
1456 for more information.
1458 The DESTPAC name is optional; the source packages' name will be used if
1462 osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1466 args = slash_split(args)
1468 if not args or len(args) < 3:
1469 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1470 + self.get_cmd_help('aggregatepac'))
1472 src_project = args[0]
1473 src_package = args[1]
1474 dst_project = args[2]
1476 dst_package = args[3]
1478 dst_package = src_package
1480 if src_project == dst_project and src_package == dst_package:
1481 raise oscerr.WrongArgs('Error: source and destination are the same.')
1485 for pair in opts.map_repo.split(','):
1486 src_tgt = pair.split('=')
1487 if len(src_tgt) != 2:
1488 raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo)
1489 repo_map[src_tgt[0]] = src_tgt[1]
1491 aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish)
1494 @cmdln.option('-c', '--client-side-copy', action='store_true',
1495 help='do a (slower) client-side copy')
1496 @cmdln.option('-k', '--keep-maintainers', action='store_true',
1497 help='keep original maintainers. Default is remove all and replace with the one calling the script.')
1498 @cmdln.option('-d', '--keep-develproject', action='store_true',
1499 help='keep develproject tag in the package metadata')
1500 @cmdln.option('-r', '--revision', metavar='rev',
1501 help='link the specified revision.')
1502 @cmdln.option('-t', '--to-apiurl', metavar='URL',
1503 help='URL of destination api server. Default is the source api server.')
1504 @cmdln.option('-m', '--message', metavar='TEXT',
1505 help='specify message TEXT')
1506 @cmdln.option('-e', '--expand', action='store_true',
1507 help='if the source package is a link then copy the expanded version of the link')
1508 def do_copypac(self, subcmd, opts, *args):
1509 """${cmd_name}: Copy a package
1511 A way to copy package to somewhere else.
1513 It can be done across buildservice instances, if the -t option is used.
1514 In that case, a client-side copy is implied.
1516 Using --client-side-copy always involves downloading all files, and
1517 uploading them to the target.
1519 The DESTPAC name is optional; the source packages' name will be used if
1523 osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1527 args = slash_split(args)
1529 if not args or len(args) < 3:
1530 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1531 + self.get_cmd_help('copypac'))
1533 src_project = args[0]
1534 src_package = args[1]
1535 dst_project = args[2]
1537 dst_package = args[3]
1539 dst_package = src_package
1541 src_apiurl = conf.config['apiurl']
1543 dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl)
1545 dst_apiurl = src_apiurl
1547 if src_project == dst_project and \
1548 src_package == dst_package and \
1549 src_apiurl == dst_apiurl:
1550 raise oscerr.WrongArgs('Source and destination are the same.')
1552 if src_apiurl != dst_apiurl:
1553 opts.client_side_copy = True
1555 rev, dummy = parseRevisionOption(opts.revision)
1558 comment = opts.message
1561 rev = show_upstream_rev(src_apiurl, src_project, src_package)
1562 comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev )
1564 r = copy_pac(src_apiurl, src_project, src_package,
1565 dst_apiurl, dst_project, dst_package,
1566 client_side_copy=opts.client_side_copy,
1567 keep_maintainers=opts.keep_maintainers,
1568 keep_develproject=opts.keep_develproject,
1575 @cmdln.option('-c', '--checkout', action='store_true',
1576 help='Checkout branched package afterwards ' \
1577 '(\'osc bco\' is a shorthand for this option)' )
1578 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
1579 help='Use this attribute to find affected packages (default is OBS:Maintained)')
1580 @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE',
1581 help='Use this attribute to find update projects (default is OBS:UpdateProject) ')
1582 def do_mbranch(self, subcmd, opts, *args):
1583 """${cmd_name}: Multiple branch of a package
1585 [See http://en.opensuse.org/Build_Service/Concepts/Maintenance for information
1588 This command is used for creating multiple links of defined version of a package
1589 in one project. This is esp. used for maintenance updates.
1591 The branched package will live in
1592 home:USERNAME:branches:ATTRIBUTE:PACKAGE
1593 if nothing else specified.
1596 osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ]
1599 args = slash_split(args)
1602 maintained_attribute = conf.config['maintained_attribute']
1603 maintained_update_project_attribute = conf.config['maintained_update_project_attribute']
1605 if not len(args) or len(args) > 2:
1606 raise oscerr.WrongArgs('Wrong number of arguments.')
1612 r = attribute_branch_pkg(conf.config['apiurl'], maintained_attribute, maintained_update_project_attribute, \
1616 print >>sys.stderr, 'ERROR: Attribute branch call came not back with a project.'
1619 print "Project " + r + " created."
1622 init_project_dir(conf.config['apiurl'], r, r)
1623 print statfrmt('A', r)
1626 for package in meta_get_packagelist(conf.config['apiurl'], r):
1628 checkout_package(conf.config['apiurl'], r, package, expand_link = True, prj_dir = r)
1630 print >>sys.stderr, 'Error while checkout package:\n', package
1632 if conf.config['verbose']:
1633 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1636 @cmdln.alias('branchco')
1638 @cmdln.alias('getpac')
1639 @cmdln.option('--nodevelproject', action='store_true',
1640 help='do not follow a defined devel project ' \
1641 '(primary project where a package is developed)')
1642 @cmdln.option('-c', '--checkout', action='store_true',
1643 help='Checkout branched package afterwards ' \
1644 '(\'osc bco\' is a shorthand for this option)' )
1645 @cmdln.option('-r', '--revision', metavar='rev',
1646 help='branch against a specific revision')
1647 @cmdln.option('-m', '--message', metavar='TEXT',
1648 help='specify message TEXT')
1649 def do_branch(self, subcmd, opts, *args):
1650 """${cmd_name}: Branch a package
1652 [See http://en.opensuse.org/Build_Service/Collaboration for information
1655 Create a source link from a package of an existing project to a new
1656 subproject of the requesters home project (home:branches:)
1658 The branched package will live in
1659 home:USERNAME:branches:PROJECT/PACKAGE
1660 if nothing else specified.
1662 With getpac or bco, the branched package will come from
1663 %(getpac_default_project)s
1664 if nothing else specified.
1667 osc branch SOURCEPROJECT SOURCEPACKAGE
1668 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT
1669 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE
1670 osc getpac SOURCEPACKAGE
1675 if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': opts.checkout = True
1676 args = slash_split(args)
1677 tproject = tpackage = None
1679 if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1:
1680 print >>sys.stderr, 'defaulting to %s/%s' % (conf.config['getpac_default_project'], args[0])
1681 # python has no args.unshift ???
1682 args = [ conf.config['getpac_default_project'] , args[0] ]
1684 if len(args) < 2 or len(args) > 4:
1685 raise oscerr.WrongArgs('Wrong number of arguments.')
1686 expected = 'home:%s:branches:%s' % (conf.config['user'], args[0])
1688 expected = tproject = args[2]
1692 if not opts.message:
1693 footer='please specify the purpose of your branch'
1694 template='This package was branched from %s in order to ...\n' % args[0]
1695 opts.message = edit_message(footer, template)
1697 exists, targetprj, targetpkg, srcprj, srcpkg = \
1698 branch_pkg(conf.config['apiurl'], args[0], args[1],
1699 nodevelproject=opts.nodevelproject, rev=opts.revision,
1700 target_project=tproject, target_package=tpackage,
1701 return_existing=opts.checkout, msg=opts.message or '')
1703 print >>sys.stderr, 'Using existing branch project: %s' % targetprj
1706 if not exists and (srcprj is not None and srcprj != args[0] or \
1707 srcprj is None and targetprj != expected):
1708 devloc = srcprj or targetprj
1709 if not srcprj and 'branches:' in targetprj:
1710 devloc = targetprj.split('branches:')[1]
1711 print '\nNote: The branch has been created of a different project,\n' \
1713 ' which is the primary location of where development for\n' \
1714 ' that package takes place.\n' \
1715 ' That\'s also where you would normally make changes against.\n' \
1716 ' A direct branch of the specified package can be forced\n' \
1717 ' with the --nodevelproject option.\n' % devloc
1719 package = tpackage or args[1]
1721 checkout_package(conf.config['apiurl'], targetprj, package,
1722 expand_link=True, prj_dir=targetprj)
1723 if conf.config['verbose']:
1724 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1727 if conf.get_configParser().get('general', 'apiurl') != conf.config['apiurl']:
1728 apiopt = '-A %s ' % conf.config['apiurl']
1729 print 'A working copy of the branched package can be checked out with:\n\n' \
1731 % (apiopt, targetprj, package)
1732 print_request_list(conf.config['apiurl'], args[0], args[1])
1734 print_request_list(conf.config['apiurl'], devloc, args[1])
1738 @cmdln.option('-f', '--force', action='store_true',
1739 help='deletes a package or an empty project')
1740 def do_rdelete(self, subcmd, opts, *args):
1741 """${cmd_name}: Delete a project or packages on the server.
1743 As a safety measure, project must be empty (i.e., you need to delete all
1744 packages first). If you are sure that you want to remove this project and all
1745 its packages use \'--force\' switch.
1748 osc rdelete -f PROJECT
1749 osc rdelete PROJECT PACKAGE [PACKAGE ...]
1754 args = slash_split(args)
1756 raise oscerr.WrongArgs('Missing argument.')
1762 # careful: if pkg is an empty string, the package delete request results
1763 # into a project delete request - which works recursively...
1765 delete_package(conf.config['apiurl'], prj, pkg)
1766 elif len(meta_get_packagelist(conf.config['apiurl'], prj)) >= 1 and not opts.force:
1767 print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
1768 'If you are sure that you want to remove this project and all its ' \
1769 'packages use the \'--force\' switch'
1772 delete_project(conf.config['apiurl'], prj)
1775 def do_deletepac(self, subcmd, opts, *args):
1776 print """${cmd_name} is obsolete !
1779 osc delete for checked out packages or projects
1781 osc rdelete for server side operations."""
1786 @cmdln.option('-f', '--force', action='store_true',
1787 help='deletes a project and its packages')
1788 def do_deleteprj(self, subcmd, opts, project):
1789 """${cmd_name} is obsolete !
1796 @cmdln.alias('metafromspec')
1797 @cmdln.option('', '--specfile', metavar='FILE',
1798 help='Path to specfile. (if you pass more than working copy this option is ignored)')
1799 def do_updatepacmetafromspec(self, subcmd, opts, *args):
1800 """${cmd_name}: Update package meta information from a specfile
1802 ARG, if specified, is a package working copy.
1808 args = parseargs(args)
1809 if opts.specfile and len(args) == 1:
1810 specfile = opts.specfile
1813 pacs = findpacs(args)
1815 p.read_meta_from_spec(specfile)
1816 p.update_package_meta()
1820 @cmdln.option('-c', '--change', metavar='rev',
1821 help='the change made by revision rev (like -r rev-1:rev).'
1822 'If rev is negative this is like -r rev:rev-1.')
1823 @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
1824 help='If rev1 is specified it will compare your working copy against '
1825 'the revision (rev1) on the server. '
1826 'If rev1 and rev2 are specified it will compare rev1 against rev2 '
1827 '(NOTE: changes in your working copy are ignored in this case)')
1828 @cmdln.option('-p', '--plain', action='store_true',
1829 help='output the diff in plain (not unified) diff format')
1830 def do_diff(self, subcmd, opts, *args):
1831 """${cmd_name}: Generates a diff
1833 Generates a diff, comparing local changes against the repository
1836 ARG, specified, is a filename to include in the diff.
1842 args = parseargs(args)
1843 pacs = findpacs(args)
1847 rev = int(opts.change)
1857 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1860 rev1, rev2 = parseRevisionOption(opts.revision)
1864 diff += ''.join(make_diff(pac, rev1))
1866 diff += server_diff(pac.apiurl, pac.prjname, pac.name, rev1,
1867 pac.prjname, pac.name, rev2, not opts.plain)
1872 @cmdln.option('--oldprj', metavar='OLDPRJ',
1873 help='project to compare against'
1874 ' (deprecated, use 3 argument form)')
1875 @cmdln.option('--oldpkg', metavar='OLDPKG',
1876 help='package to compare against'
1877 ' (deprecated, use 3 argument form)')
1878 @cmdln.option('-r', '--revision', metavar='N[:M]',
1879 help='revision id, where N = old revision and M = new revision')
1880 @cmdln.option('-p', '--plain', action='store_true',
1881 help='output the diff in plain (not unified) diff format')
1882 @cmdln.option('-c', '--change', metavar='rev',
1883 help='the change made by revision rev (like -r rev-1:rev). '
1884 'If rev is negative this is like -r rev:rev-1.')
1885 def do_rdiff(self, subcmd, opts, *args):
1886 """${cmd_name}: Server-side "pretty" diff of two packages
1888 Compares two packages (three or four arguments) or shows the
1889 changes of a specified revision of a package (two arguments)
1891 If no revision is specified the latest revision is used.
1893 Note that this command doesn't return a normal diff (which could be
1894 applied as patch), but a "pretty" diff, which also compares the content
1899 osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC]
1900 osc ${cmd_name} PROJECT PACKAGE
1904 args = slash_split(args)
1915 new_project = args[0]
1916 new_package = args[1]
1918 old_project = opts.oldprj
1920 old_package = opts.oldpkg
1921 elif len(args) == 3 or len(args) == 4:
1922 if opts.oldprj or opts.oldpkg:
1923 raise oscerr.WrongArgs('--oldpkg and --oldprj are only valid with two arguments')
1924 old_project = args[0]
1925 new_package = old_package = args[1]
1926 new_project = args[2]
1928 new_package = args[3]
1930 raise oscerr.WrongArgs('Wrong number of arguments')
1935 rev = int(opts.change)
1945 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1949 rev1, rev2 = parseRevisionOption(opts.revision)
1951 rdiff = server_diff(conf.config['apiurl'],
1952 old_project, old_package, rev1,
1953 new_project, new_package, rev2, not opts.plain)
1959 def do_install(self, subcmd, opts, *args):
1960 """${cmd_name}: install a package after build via zypper in -r
1962 Not implemented yet. Use osc repourls,
1963 select the url you best like (standard),
1964 chop off after the last /, this should work with zypper.
1971 args = slash_split(args)
1972 args = expand_proj_pack(args)
1975 ## if there is only one argument, and it ends in .ymp
1976 ## then fetch it, Parse XML to get the first
1977 ## metapackage.group.repositories.repository.url
1978 ## and construct zypper cmd's for all
1979 ## metapackage.group.software.item.name
1981 ## if args[0] is already an url, the use it as is.
1983 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])
1984 print self.do_install.__doc__
1985 print "Example: \n" + cmd
1988 def do_repourls(self, subcmd, opts, *args):
1989 """${cmd_name}: Shows URLs of .repo files
1991 Shows URLs on which to access the project .repos files (yum-style
1992 metadata) on download.opensuse.org.
1995 osc repourls [PROJECT]
2000 apiurl = conf.config['apiurl']
2004 elif len(args) == 0:
2005 project = store_read_project('.')
2006 apiurl = store_read_apiurl('.')
2008 raise oscerr.WrongArgs('Wrong number of arguments')
2010 # XXX: API should somehow tell that
2011 url_tmpl = 'http://download.opensuse.org/repositories/%s/%s/%s.repo'
2012 repos = get_repositories_of_project(apiurl, project)
2014 print url_tmpl % (project.replace(':', ':/'), repo, project)
2017 @cmdln.option('-r', '--revision', metavar='rev',
2018 help='checkout the specified revision. '
2019 'NOTE: if you checkout the complete project '
2020 'this option is ignored!')
2021 @cmdln.option('-e', '--expand-link', action='store_true',
2022 help='if a package is a link, check out the expanded '
2023 'sources (no-op, since this became the default)')
2024 @cmdln.option('-u', '--unexpand-link', action='store_true',
2025 help='if a package is a link, check out the _link file ' \
2026 'instead of the expanded sources')
2027 @cmdln.option('-c', '--current-dir', action='store_true',
2028 help='place PACKAGE folder in the current directory' \
2029 'instead of a PROJECT/PACKAGE directory')
2030 @cmdln.option('-s', '--source-service-files', action='store_true',
2031 help='server side generated files of source services' \
2032 'gets downloaded as well' )
2034 def do_checkout(self, subcmd, opts, *args):
2035 """${cmd_name}: Check out content from the repository
2037 Check out content from the repository server, creating a local working
2040 When checking out a single package, the option --revision can be used
2041 to specify a revision of the package to be checked out.
2043 When a package is a source link, then it will be checked out in
2044 expanded form. If --unexpand-link option is used, the checkout will
2045 instead produce the raw _link file plus patches.
2048 osc co PROJECT [PACKAGE] [FILE]
2049 osc co PROJECT # entire project
2050 osc co PROJECT PACKAGE # a package
2051 osc co PROJECT PACKAGE FILE # single file -> to current dir
2053 while inside a project directory:
2054 osc co PACKAGE # check out PACKAGE from project
2059 if opts.unexpand_link:
2063 if opts.source_service_files:
2064 service_files = True
2066 service_files = False
2068 args = slash_split(args)
2069 project = package = filename = None
2070 apiurl = conf.config['apiurl']
2072 project = project_dir = args[0]
2078 if args and len(args) == 1:
2079 localdir = os.getcwd()
2080 if is_project_dir(localdir):
2081 project = store_read_project(localdir)
2082 project_dir = localdir
2084 apiurl = store_read_apiurl(localdir)
2086 rev, dummy = parseRevisionOption(opts.revision)
2090 if rev and rev != "latest" and not checkRevision(project, package, rev):
2091 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2095 get_source_file(apiurl, project, package, filename, revision=rev, progress_obj=self.download_progress)
2098 if opts.current_dir:
2100 checkout_package(apiurl, project, package, rev, expand_link=expand_link, \
2101 prj_dir=project_dir, service_files=service_files, progress_obj=self.download_progress)
2102 print_request_list(apiurl, project, package)
2106 if sys.platform[:3] == 'win':
2107 prj_dir = prj_dir.replace(':', ';')
2108 if os.path.exists(prj_dir):
2109 sys.exit('osc: project \'%s\' already exists' % project)
2111 # check if the project does exist (show_project_meta will throw an exception)
2112 show_project_meta(apiurl, project)
2114 init_project_dir(apiurl, prj_dir, project)
2115 print statfrmt('A', prj_dir)
2118 for package in meta_get_packagelist(apiurl, project):
2120 checkout_package(apiurl, project, package, expand_link = expand_link, \
2121 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress)
2122 except oscerr.LinkExpandError, e:
2123 print >>sys.stderr, 'Link cannot be expanded:\n', e
2124 print >>sys.stderr, 'Use "osc repairlink" for fixing merge conflicts:\n'
2125 # check out in unexpanded form at least
2126 checkout_package(apiurl, project, package, expand_link = False, \
2127 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress)
2128 print_request_list(apiurl, project)
2131 raise oscerr.WrongArgs('Missing argument.\n\n' \
2132 + self.get_cmd_help('checkout'))
2135 @cmdln.option('-q', '--quiet', action='store_true',
2136 help='print as little as possible')
2137 @cmdln.option('-v', '--verbose', action='store_true',
2138 help='print extra information')
2140 def do_status(self, subcmd, opts, *args):
2141 """${cmd_name}: Show status of files in working copy
2143 Show the status of files in a local working copy, indicating whether
2144 files have been changed locally, deleted, added, ...
2146 The first column in the output specifies the status and is one of the
2147 following characters:
2148 ' ' no modifications
2153 '?' item is not under version control
2154 '!' item is missing (removed by non-osc command) or incomplete
2159 osc st file1 file2 ...
2162 osc status [OPTS] [PATH...]
2166 args = parseargs(args)
2168 # storage for single Package() objects
2170 # storage for a project dir ( { prj_instance : [ package objects ] } )
2173 # when 'status' is run inside a project dir, it should
2174 # stat all packages existing in the wc
2175 if is_project_dir(arg):
2176 prj = Project(arg, False)
2178 if conf.config['do_package_tracking']:
2180 for pac in prj.pacs_have:
2181 # we cannot create package objects if the dir does not exist
2182 if not pac in prj.pacs_broken:
2183 prjpacs[prj].append(os.path.join(arg, pac))
2185 pacpaths += [arg + '/' + n for n in prj.pacs_have]
2186 elif is_package_dir(arg):
2187 pacpaths.append(arg)
2188 elif os.path.isfile(arg):
2189 pacpaths.append(arg)
2191 msg = '\'%s\' is neither a project or a package directory' % arg
2192 raise oscerr.NoWorkingCopy, msg
2194 # process single packages
2195 lines = getStatus(findpacs(pacpaths), None, opts.verbose, opts.quiet)
2196 # process project dirs
2197 for prj, pacs in prjpacs.iteritems():
2198 lines += getStatus(findpacs(pacs), prj, opts.verbose, opts.quiet)
2200 print '\n'.join(lines)
2203 def do_add(self, subcmd, opts, *args):
2204 """${cmd_name}: Mark files to be added upon the next commit
2207 osc add FILE [FILE...]
2211 raise oscerr.WrongArgs('Missing argument.\n\n' \
2212 + self.get_cmd_help('add'))
2214 filenames = parseargs(args)
2218 def do_mkpac(self, subcmd, opts, *args):
2219 """${cmd_name}: Create a new package under version control
2222 osc mkpac new_package
2225 if not conf.config['do_package_tracking']:
2226 print >>sys.stderr, "to use this feature you have to enable \'do_package_tracking\' " \
2227 "in the [general] section in the configuration file"
2231 raise oscerr.WrongArgs('Wrong number of arguments.')
2233 createPackageDir(args[0])
2235 @cmdln.option('-r', '--recursive', action='store_true',
2236 help='If CWD is a project dir then scan all package dirs as well')
2238 def do_addremove(self, subcmd, opts, *args):
2239 """${cmd_name}: Adds new files, removes disappeared files
2241 Adds all files new in the local copy, and removes all disappeared files.
2243 ARG, if specified, is a package working copy.
2249 args = parseargs(args)
2251 for arg in arg_list:
2252 if is_project_dir(arg) and conf.config['do_package_tracking']:
2253 prj = Project(arg, False)
2254 for pac in prj.pacs_unvers:
2255 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2256 if os.path.isdir(pac_dir):
2257 addFiles([pac_dir], prj)
2258 for pac in prj.pacs_broken:
2259 if prj.get_state(pac) != 'D':
2260 prj.set_state(pac, 'D')
2261 print statfrmt('D', getTransActPath(os.path.join(prj.dir, pac)))
2263 for pac in prj.pacs_have:
2264 state = prj.get_state(pac)
2265 if state != None and state != 'D':
2266 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2267 args.append(pac_dir)
2269 prj.write_packages()
2270 elif is_project_dir(arg):
2271 print >>sys.stderr, 'osc: addremove is not supported in a project dir unless ' \
2272 '\'do_package_tracking\' is enabled in the configuration file'
2275 pacs = findpacs(args)
2277 p.todo = p.filenamelist + p.filenamelist_unvers
2279 for filename in p.todo:
2280 if os.path.isdir(filename):
2282 # ignore foo.rXX, foo.mine for files which are in 'C' state
2283 if os.path.splitext(filename)[0] in p.in_conflict:
2285 state = p.status(filename)
2288 # TODO: should ignore typical backup files suffix ~ or .orig
2290 print statfrmt('A', getTransActPath(os.path.join(p.dir, filename)))
2292 p.put_on_deletelist(filename)
2293 p.write_deletelist()
2294 os.unlink(os.path.join(p.storedir, filename))
2295 print statfrmt('D', getTransActPath(os.path.join(p.dir, filename)))
2300 @cmdln.alias('checkin')
2301 @cmdln.option('-m', '--message', metavar='TEXT',
2302 help='specify log message TEXT')
2303 @cmdln.option('-F', '--file', metavar='FILE',
2304 help='read log message from FILE')
2305 @cmdln.option('-f', '--force', default=False, action="store_true",
2306 help='force commit - do not tests a file list')
2307 def do_commit(self, subcmd, opts, *args):
2308 """${cmd_name}: Upload content to the repository server
2310 Upload content which is changed in your working copy, to the repository
2313 Optionally checks the state of a working copy, if found a file with
2314 unknown state, it requests an user input:
2315 * skip - don't change anything, just move to another file
2316 * remove - remove a file from dir
2317 * edit file list - edit filelist using EDITOR
2318 * commit - don't check anything and commit package
2319 * abort - abort commit - this is default value
2320 This can be supressed by check_filelist config item, or -f/--force
2321 command line option.
2324 osc ci # current dir
2326 osc ci file1 file2 ...
2332 args = parseargs(args)
2339 msg = open(opts.file).read()
2341 sys.exit('could not open file \'%s\'.' % opts.file)
2344 for arg in arg_list:
2345 if conf.config['do_package_tracking'] and is_project_dir(arg):
2346 Project(arg).commit(msg=msg)
2348 msg = edit_message()
2351 pacs = findpacs(args)
2353 if conf.config['check_filelist'] and not opts.force:
2354 check_filelist_before_commit(pacs)
2357 template = store_read_file(os.path.abspath('.'), '_commit_msg')
2358 # open editor for commit message
2359 # but first, produce status and diff to append to the template
2363 changed = getStatus([pac], quiet=True)
2366 diffs += ['\nDiff for working copy: %s' % pac.dir]
2367 diffs += make_diff(pac, 0)
2368 lines.extend(get_commit_message_template(pac))
2369 if template == None:
2370 template='\n'.join(lines)
2371 # if footer is empty, there is nothing to commit, and no edit needed.
2373 msg = edit_message(footer='\n'.join(footer), template=template)
2376 store_write_string(os.path.abspath('.'), '_commit_msg', msg)
2378 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2380 if conf.config['do_package_tracking'] and len(pacs) > 0:
2384 # it is possible to commit packages from different projects at the same
2385 # time: iterate over all pacs and put each pac to the right project in the dict
2387 path = os.path.normpath(os.path.join(pac.dir, os.pardir))
2388 if is_project_dir(path):
2389 pac_path = os.path.basename(os.path.normpath(pac.absdir))
2390 prj_paths.setdefault(path, []).append(pac_path)
2391 files[pac_path] = pac.todo
2393 single_paths.append(pac.dir)
2394 for prj, packages in prj_paths.iteritems():
2395 Project(prj).commit(tuple(packages), msg, files)
2396 for pac in single_paths:
2397 Package(pac).commit(msg)
2402 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2404 @cmdln.option('-r', '--revision', metavar='REV',
2405 help='update to specified revision (this option will be ignored '
2406 'if you are going to update the complete project or more than '
2408 @cmdln.option('-u', '--unexpand-link', action='store_true',
2409 help='if a package is an expanded link, update to the raw _link file')
2410 @cmdln.option('-e', '--expand-link', action='store_true',
2411 help='if a package is a link, update to the expanded sources')
2412 @cmdln.option('-s', '--source-service-files', action='store_true',
2413 help='Use server side generated sources instead of local generation.' )
2415 def do_update(self, subcmd, opts, *args):
2416 """${cmd_name}: Update a working copy
2421 If the current working directory is a package, update it.
2422 If the directory is a project directory, update all contained
2423 packages, AND check out newly added packages.
2425 To update only checked out packages, without checking out new
2426 ones, you might want to use "osc up *" from within the project
2430 Update the packages specified by the path argument(s)
2432 When --expand-link is used with source link packages, the expanded
2433 sources will be checked out. Without this option, the _link file and
2434 patches will be checked out. The option --unexpand-link can be used to
2435 switch back to the "raw" source with a _link file plus patch(es).
2441 if (opts.expand_link and opts.unexpand_link) \
2442 or (opts.expand_link and opts.revision) \
2443 or (opts.unexpand_link and opts.revision):
2444 raise oscerr.WrongOptions('Sorry, the options --expand-link, --unexpand-link and '
2445 '--revision are mutually exclusive.')
2447 if opts.source_service_files: service_files = True
2448 else: service_files = False
2450 args = parseargs(args)
2453 for arg in arg_list:
2454 if is_project_dir(arg):
2455 prj = Project(arg, progress_obj=self.download_progress)
2457 if conf.config['do_package_tracking']:
2458 prj.update(expand_link=opts.expand_link,
2459 unexpand_link=opts.unexpand_link)
2462 # if not tracking package, and 'update' is run inside a project dir,
2463 # it should do the following:
2464 # (a) update all packages
2465 args += prj.pacs_have
2466 # (b) fetch new packages
2467 prj.checkout_missing_pacs(expand_link = not opts.unexpand_link)
2469 print_request_list(prj.apiurl, prj.name)
2472 pacs = findpacs(args, progress_obj=self.download_progress)
2474 if opts.revision and len(args) == 1:
2475 rev, dummy = parseRevisionOption(opts.revision)
2476 if not checkRevision(pacs[0].prjname, pacs[0].name, rev, pacs[0].apiurl):
2477 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2484 print 'Updating %s' % p.name
2486 # FIXME: ugly workaround for #399247
2487 if opts.expand_link or opts.unexpand_link:
2488 if [ i for i in p.filenamelist+p.filenamelist_unvers if p.status(i) != ' ' and p.status(i) != '?']:
2489 print >>sys.stderr, 'osc: cannot expand/unexpand because your working ' \
2490 'copy has local modifications.\nPlease revert/commit them ' \
2495 if opts.expand_link and p.islink() and not p.isexpanded():
2496 if p.haslinkerror():
2498 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev)
2500 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev, linkrev="base")
2503 rev = p.linkinfo.xsrcmd5
2504 print 'Expanding to rev', rev
2505 elif opts.unexpand_link and p.islink() and p.isexpanded():
2506 print 'Unexpanding to rev', p.linkinfo.lsrcmd5
2507 rev = p.linkinfo.lsrcmd5
2508 elif p.islink() and p.isexpanded():
2509 rev = p.latest_rev()
2511 p.update(rev, service_files)
2512 if opts.unexpand_link:
2515 print_request_list(p.apiurl, p.prjname, p.name)
2518 @cmdln.option('-f', '--force', action='store_true',
2519 help='forces removal of entire package and its files')
2522 @cmdln.alias('remove')
2523 def do_delete(self, subcmd, opts, *args):
2524 """${cmd_name}: Mark files or package directories to be deleted upon the next 'checkin'
2527 cd .../PROJECT/PACKAGE
2528 osc delete FILE [...]
2530 osc delete PACKAGE [...]
2532 This command works on check out copies. Use "rdelete" for working on server
2533 side only. This is needed for removing the entire project.
2535 As a safety measure, projects must be empty (i.e., you need to delete all
2538 If you are sure that you want to remove a package and all
2539 its files use \'--force\' switch. Sometimes this also works without --force.
2545 raise oscerr.WrongArgs('Missing argument.\n\n' \
2546 + self.get_cmd_help('delete'))
2548 args = parseargs(args)
2549 # check if args contains a package which was removed by
2550 # a non-osc command and mark it with the 'D'-state
2553 if not os.path.exists(i):
2554 prj_dir, pac_dir = getPrjPacPaths(i)
2555 if is_project_dir(prj_dir):
2556 prj = Project(prj_dir, False)
2557 if i in prj.pacs_broken:
2558 if prj.get_state(i) != 'A':
2559 prj.set_state(pac_dir, 'D')
2561 prj.del_package_node(i)
2562 print statfrmt('D', getTransActPath(i))
2564 prj.write_packages()
2565 pacs = findpacs(args)
2569 prj_dir, pac_dir = getPrjPacPaths(p.absdir)
2570 if is_project_dir(prj_dir):
2571 if conf.config['do_package_tracking']:
2572 prj = Project(prj_dir, False)
2573 prj.delPackage(p, opts.force)
2575 print "WARNING: package tracking is disabled, operation skipped !"
2577 pathn = getTransActPath(p.dir)
2578 for filename in p.todo:
2579 ret, state = p.delete_file(filename, opts.force)
2581 print statfrmt('D', os.path.join(pathn, filename))
2584 sys.exit('\'%s\' is not under version control' % filename)
2585 elif state in ['A', 'M'] and not opts.force:
2586 sys.exit('\'%s\' has local modifications (use --force to remove this file)' % filename)
2589 def do_resolved(self, subcmd, opts, *args):
2590 """${cmd_name}: Remove 'conflicted' state on working copy files
2592 If an upstream change can't be merged automatically, a file is put into
2593 in 'conflicted' ('C') state. Within the file, conflicts are marked with
2594 special <<<<<<< as well as ======== and >>>>>>> lines.
2596 After manually resolving all conflicting parts, use this command to
2597 remove the 'conflicted' state.
2599 Note: this subcommand does not semantically resolve conflicts or
2600 remove conflict markers; it merely removes the conflict-related
2601 artifact files and allows PATH to be committed again.
2604 osc resolved FILE [FILE...]
2609 raise oscerr.WrongArgs('Missing argument.\n\n' \
2610 + self.get_cmd_help('resolved'))
2612 args = parseargs(args)
2613 pacs = findpacs(args)
2616 for filename in p.todo:
2617 print 'Resolved conflicted state of "%s"' % filename
2618 p.clear_from_conflictlist(filename)
2621 @cmdln.alias('platforms')
2622 def do_repositories(self, subcmd, opts, *args):
2623 """${cmd_name}: Shows available repositories
2627 Shows all available repositories/build targets
2629 2. osc repositories <project>
2630 Shows the configured repositories/build targets of a project
2638 print '\n'.join(get_repositories_of_project(conf.config['apiurl'], project))
2640 print '\n'.join(get_repositories(conf.config['apiurl']))
2644 def do_results_meta(self, subcmd, opts, *args):
2645 print "Command results_meta is obsolete. Please use: osc results --xml"
2649 @cmdln.option('-l', '--last-build', action='store_true',
2650 help='show last build results (succeeded/failed/unknown)')
2651 @cmdln.option('-r', '--repo', action='append', default = [],
2652 help='Show results only for specified repo(s)')
2653 @cmdln.option('-a', '--arch', action='append', default = [],
2654 help='Show results only for specified architecture(s)')
2655 @cmdln.option('', '--xml', action='store_true',
2656 help='generate output in XML (former results_meta)')
2657 def do_rresults(self, subcmd, opts, *args):
2658 print "Command rresults is obsolete. Running 'osc results' instead"
2659 self.do_results('results', opts, *args)
2663 @cmdln.option('-f', '--force', action='store_true', default=False,
2664 help="Don't ask and delete files")
2665 def do_rremove(self, subcmd, opts, project, package, *files):
2666 """${cmd_name}: Remove source files from selected package
2673 if not '/' in project:
2674 raise oscerr.WrongArgs("Missing operand, type osc help rremove for help")
2677 project, package = project.split('/')
2681 resp = raw_input("rm: remove source file `%s' from `%s/%s'? (yY|nN) " % (file, project, package))
2682 if resp not in ('y', 'Y'):
2685 delete_files(conf.config['apiurl'], project, package, (file, ))
2686 except urllib2.HTTPError, e:
2688 print >>sys.stderr, e
2690 if e.code in [ 400, 403, 404, 500 ]:
2691 if '<summary>' in body:
2692 msg = body.split('<summary>')[1]
2693 msg = msg.split('</summary>')[0]
2694 print >>sys.stderr, msg
2699 @cmdln.option('-l', '--last-build', action='store_true',
2700 help='show last build results (succeeded/failed/unknown)')
2701 @cmdln.option('-r', '--repo', action='append', default = [],
2702 help='Show results only for specified repo(s)')
2703 @cmdln.option('-a', '--arch', action='append', default = [],
2704 help='Show results only for specified architecture(s)')
2705 @cmdln.option('', '--xml', action='store_true',
2706 help='generate output in XML (former results_meta)')
2707 def do_results(self, subcmd, opts, *args):
2708 """${cmd_name}: Shows the build results of a package
2711 osc results (inside working copy)
2712 osc results remote_project remote_package
2717 args = slash_split(args)
2719 apiurl = conf.config['apiurl']
2722 if is_project_dir(wd):
2726 opts.hide_legend = None
2727 opts.name_filter = None
2728 opts.status_filter = None
2729 opts.vertical = None
2730 self.do_prjresults('prjresults', opts, *args)
2733 project = store_read_project(wd)
2734 package = store_read_package(wd)
2735 apiurl = store_read_apiurl(wd)
2737 raise oscerr.WrongArgs('Too few arguments (required none or two)')
2739 raise oscerr.WrongArgs('Too many arguments (required none or two)')
2748 func = show_results_meta
2751 print delim.join(func(apiurl, project, package, opts.last_build, opts.repo, opts.arch))
2753 # WARNING: this function is also called by do_results. You need to set a default there
2754 # as well when adding a new option!
2755 @cmdln.option('-q', '--hide-legend', action='store_true',
2756 help='hide the legend')
2757 @cmdln.option('-c', '--csv', action='store_true',
2759 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2760 help='show only packages with buildstatus STATUS (see legend)')
2761 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2762 help='show only packages whose names match EXPR')
2763 @cmdln.option('-a', '--arch', metavar='ARCH',
2764 help='show results only for specified architecture(s)')
2765 @cmdln.option('-r', '--repo', metavar='REPO',
2766 help='show results only for specified repo(s)')
2767 @cmdln.option('-V', '--vertical', action='store_true',
2768 help='list packages vertically instead horizontally')
2770 def do_prjresults(self, subcmd, opts, *args):
2771 """${cmd_name}: Shows project-wide build results
2774 osc prjresults (inside working copy)
2775 osc prjresults PROJECT
2781 apiurl = conf.config['apiurl']
2785 raise oscerr.WrongArgs('Wrong number of arguments.')
2788 project = store_read_project(wd)
2789 apiurl = store_read_apiurl(wd)
2791 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))
2794 @cmdln.option('-q', '--hide-legend', action='store_true',
2795 help='hide the legend')
2796 @cmdln.option('-c', '--csv', action='store_true',
2798 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2799 help='show only packages with buildstatus STATUS (see legend)')
2800 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2801 help='show only packages whose names match EXPR')
2804 def do_rprjresults(self, subcmd, opts, *args):
2805 print "Command rprjresults is obsolete. Please use 'osc prjresults'"
2809 @cmdln.option('-s', '--start', metavar='START',
2810 help='get log starting from the offset')
2811 def do_buildlog(self, subcmd, opts, *args):
2812 """${cmd_name}: Shows the build log of a package
2814 Shows the log file of the build of a package. Can be used to follow the
2815 log while it is being written.
2816 Needs to be called from within a package directory.
2818 The arguments REPOSITORY and ARCH are the first two columns in the 'osc
2819 results' output. If the buildlog url is used buildlog command has the
2820 same behavior as remotebuildlog.
2822 ${cmd_usage} [REPOSITORY ARCH | BUILDLOGURL]
2826 repository = arch = None
2828 if len(args) == 1 and args[0].startswith('http'):
2829 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2832 package = store_read_package(wd)
2833 project = store_read_project(wd)
2834 apiurl = store_read_apiurl(wd)
2838 offset = int(opts.start)
2840 if not repository or not arch:
2844 repository = args[0]
2847 print_buildlog(apiurl, project, package, repository, arch, offset)
2850 def print_repos(self):
2853 if is_package_dir(wd):
2856 elif is_project_dir(wd):
2861 print 'Valid arguments for this %s are:' % str
2863 self.do_repos(None, None)
2865 raise oscerr.WrongArgs('Missing arguments')
2868 @cmdln.alias('rbuildlog')
2869 @cmdln.option('-s', '--start', metavar='START',
2870 help='get log starting from the offset')
2871 def do_remotebuildlog(self, subcmd, opts, *args):
2872 """${cmd_name}: Shows the build log of a package
2874 Shows the log file of the build of a package. Can be used to follow the
2875 log while it is being written.
2878 osc remotebuildlog project package repository arch
2880 osc remotebuildlog project/package/repository/arch
2882 osc remotebuildlog buildlogurl
2885 if len(args) == 1 and args[0].startswith('http'):
2886 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2888 args = slash_split(args)
2889 apiurl = conf.config['apiurl']
2891 raise oscerr.WrongArgs('Too few arguments.')
2893 raise oscerr.WrongArgs('Too many arguments.')
2895 project, package, repository, arch = args
2899 offset = int(opts.start)
2901 print_buildlog(apiurl, project, package, repository, arch, offset)
2904 @cmdln.option('-s', '--start', metavar='START',
2905 help='get log starting from offset')
2906 def do_localbuildlog(self, subcmd, opts, *args):
2907 """${cmd_name}: Shows the build log of a local buildchroot
2910 osc lbl [REPOSITORY ARCH]
2911 osc lbl # show log of newest last local build
2915 if conf.config['build-type']:
2916 # FIXME: raise Exception instead
2917 print >>sys.stderr, 'Not implemented for VMs'
2921 package = store_read_package('.')
2923 files = glob.glob(os.path.join(os.getcwd(), store, "_buildinfo-*"))
2926 raise oscerr.WrongArgs('No buildconfig found, please specify repo and arch manually.')
2930 if os.stat(f).st_mtime > os.stat(cfg).st_mtime:
2932 root = ET.parse(cfg).getroot()
2933 project = root.get("project")
2934 repo = root.get("repository")
2935 arch = root.find("arch").text
2936 elif len(args) == 2:
2937 project = store_read_project('.')
2938 package = store_read_package('.')
2942 if is_package_dir(os.curdir):
2944 raise oscerr.WrongArgs('Wrong number of arguments.')
2946 buildroot = os.environ.get('OSC_BUILD_ROOT', conf.config['build-root'])
2947 buildroot = buildroot % {'project': project, 'package': package,
2948 'repo': repo, 'arch': arch}
2951 offset = int(opts.start)
2952 logfile = os.path.join(buildroot, '.build.log')
2953 if not os.path.isfile(logfile):
2954 raise oscerr.OscIOError(None, 'logfile \'%s\' does not exist' % logfile)
2955 f = open(logfile, 'r')
2957 data = f.read(BUFSIZE)
2959 sys.stdout.write(data)
2960 data = f.read(BUFSIZE)
2964 def do_triggerreason(self, subcmd, opts, *args):
2965 """${cmd_name}: Show reason why a package got triggered to build
2967 The server decides when a package needs to get rebuild, this command
2968 shows the detailed reason for a package. A brief reason is also stored
2969 in the jobhistory, which can be accessed via "osc jobhistory".
2971 Trigger reasons might be:
2972 - new build (never build yet or rebuild manually forced)
2973 - source change (eg. on updating sources)
2974 - meta change (packages which are used for building have changed)
2975 - rebuild count sync (In case that it is configured to sync release numbers)
2977 usage in package or project directory:
2978 osc reason REPOSITORY ARCH
2979 osc reason PROJECT PACKAGE REPOSITORY ARCH
2984 args = slash_split(args)
2985 project = package = repository = arch = None
2990 if len(args) == 2: # 2
2991 if is_package_dir('.'):
2992 package = store_read_package(wd)
2994 raise oscerr.WrongArgs('package is not specified.')
2995 project = store_read_project(wd)
2996 apiurl = store_read_apiurl(wd)
2997 repository = args[0]
2999 elif len(args) == 4:
3000 apiurl = conf.config['apiurl']
3003 repository = args[2]
3006 raise oscerr.WrongArgs('Too many arguments.')
3008 print apiurl, project, package, repository, arch
3009 xml = show_package_trigger_reason(apiurl, project, package, repository, arch)
3010 root = ET.fromstring(xml)
3011 reason = root.find('explain').text
3013 if reason == "meta change":
3014 print "changed keys:"
3015 for package in root.findall('packagechange'):
3016 print " ", package.get('change'), package.get('key')
3019 # FIXME: the new osc syntax should allow to specify multiple packages
3020 # FIXME: the command should optionally use buildinfo data to show all dependencies
3021 @cmdln.alias('whatdependson')
3022 def do_dependson(self, subcmd, opts, *args):
3023 """${cmd_name}: Show the build dependencies
3025 The command dependson and whatdependson can be used to find out what
3026 will be triggered when a certain package changes.
3027 This is no guarantee, since the new build might have changed dependencies.
3029 dependson shows the build dependencies inside of a project, valid for a
3030 given repository and architecture.
3031 NOTE: to see all binary packages, which can trigger a build you need to
3032 refer the buildinfo, since this command shows only the dependencies
3033 inside of a project.
3035 The arguments REPOSITORY and ARCH can be taken from the first two columns
3036 of the 'osc repos' output.
3038 usage in package or project directory:
3039 osc dependson REPOSITORY ARCH
3040 osc whatdependson REPOSITORY ARCH
3043 osc dependson PROJECT [PACKAGE] REPOSITORY ARCH
3044 osc whatdependson PROJECT [PACKAGE] REPOSITORY ARCH
3049 args = slash_split(args)
3050 project = packages = repository = arch = reverse = None
3052 if len(args) < 2 and (is_package_dir('.') or is_project_dir('.')):
3056 raise oscerr.WrongArgs('Too many arguments.')
3058 if len(args) < 3: # 2
3059 if is_package_dir('.'):
3060 packages = [store_read_package(wd)]
3061 elif not is_project_dir('.'):
3062 raise oscerr.WrongArgs('Project and package is not specified.')
3063 project = store_read_project(wd)
3064 apiurl = store_read_apiurl(wd)
3065 repository = args[0]
3069 apiurl = conf.config['apiurl']
3071 repository = args[1]
3075 apiurl = conf.config['apiurl']
3077 packages = [args[1]]
3078 repository = args[2]
3081 if subcmd == 'whatdependson':
3084 xml = get_dependson(apiurl, project, repository, arch, packages, reverse)
3086 root = ET.fromstring(xml)
3087 for package in root.findall('package'):
3088 print package.get('name'), ":"
3089 for dep in package.findall('pkgdep'):
3093 @cmdln.option('-x', '--extra-pkgs', metavar='PAC', action='append',
3094 help='Add this package when computing the buildinfo')
3095 def do_buildinfo(self, subcmd, opts, *args):
3096 """${cmd_name}: Shows the build info
3098 Shows the build "info" which is used in building a package.
3099 This command is mostly used internally by the 'build' subcommand.
3100 It needs to be called from within a package directory.