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
347 l = meta_get_filelist(conf.config['apiurl'],
350 verbose=opts.verbose,
353 link_seen = '_link' in l
355 out = [ '%s %7s %9d %s %s' % (i.md5, i.rev, i.size, shorttime(i.mtime), i.name) \
356 for i in l if not fname or fname == i.name ]
358 print_not_found = False
364 print_not_found = False
367 if opts.expand or opts.unexpand or not link_seen: break
368 m = show_files_meta(conf.config['apiurl'], project, package)
370 li.read(ET.fromstring(''.join(m)).find('linkinfo'))
372 raise oscerr.LinkExpandError(project, package, li.error)
373 project, package, rev = li.project, li.package, li.rev
375 print '# -> %s %s (%s)' % (project, package, rev)
377 print '# -> %s %s (latest)' % (project, package)
379 if fname and print_not_found:
380 print 'file \'%s\' does not exist' % fname
383 @cmdln.option('-f', '--force', action='store_true',
384 help='force generation of new patchinfo file')
385 @cmdln.option('--force-update', action='store_true',
386 help='drops away collected packages from an already built patch and let it collect again')
387 def do_patchinfo(self, subcmd, opts, *args):
388 """${cmd_name}: Generate and edit a patchinfo file.
390 A patchinfo file describes the packages for an update and the kind of
395 osc patchinfo PATCH_NAME
399 project_dir = localdir = os.getcwd()
400 if is_project_dir(localdir):
401 project = store_read_project(localdir)
402 apiurl = store_read_apiurl(localdir)
404 sys.exit('This command must be called in a checked out project.')
406 for p in meta_get_packagelist(apiurl, project):
407 if p.startswith("_patchinfo:"):
410 if opts.force or not patchinfo:
411 print "Creating initial patchinfo..."
412 query='cmd=createpatchinfo'
414 query += "&name=" + args[0]
415 url = makeurl(apiurl, ['source', project], query=query)
417 for p in meta_get_packagelist(apiurl, project):
418 if p.startswith("_patchinfo:"):
421 if not os.path.exists(project_dir + "/" + patchinfo):
422 checkout_package(apiurl, project, patchinfo, prj_dir=project_dir)
424 if sys.platform[:3] != 'win':
425 editor = os.getenv('EDITOR', default='vim')
427 editor = os.getenv('EDITOR', default='notepad')
428 subprocess.call('%s %s' % (editor, project_dir + "/" + patchinfo + "/_patchinfo"), shell=True)
431 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
432 help='affect only a given attribute')
433 @cmdln.option('--attribute-defaults', action='store_true',
434 help='include defined attribute defaults')
435 @cmdln.option('--attribute-project', action='store_true',
436 help='include project values, if missing in packages ')
437 @cmdln.option('-F', '--file', metavar='FILE',
438 help='read metadata from FILE, instead of opening an editor. '
439 '\'-\' denotes standard input. ')
440 @cmdln.option('-e', '--edit', action='store_true',
441 help='edit metadata')
442 @cmdln.option('-c', '--create', action='store_true',
443 help='create attribute without values')
444 @cmdln.option('-s', '--set', metavar='ATTRIBUTE_VALUES',
445 help='set attribute values')
446 @cmdln.option('--delete', action='store_true',
447 help='delete a pattern or attribute')
448 def do_meta(self, subcmd, opts, *args):
449 """${cmd_name}: Show meta information, or edit it
451 Show or edit build service metadata of type <prj|pkg|prjconf|user|pattern>.
453 This command displays metadata on buildservice objects like projects,
454 packages, or users. The type of metadata is specified by the word after
455 "meta", like e.g. "meta prj".
457 prj denotes metadata of a buildservice project.
458 prjconf denotes the (build) configuration of a project.
459 pkg denotes metadata of a buildservice package.
460 user denotes the metadata of a user.
461 pattern denotes installation patterns defined for a project.
463 To list patterns, use 'osc meta pattern PRJ'. An additional argument
464 will be the pattern file to view or edit.
466 With the --edit switch, the metadata can be edited. Per default, osc
467 opens the program specified by the environmental variable EDITOR with a
468 temporary file. Alternatively, content to be saved can be supplied via
469 the --file switch. If the argument is '-', input is taken from stdin:
470 osc meta prjconf home:user | sed ... | osc meta prjconf home:user -F -
472 When trying to edit a non-existing resource, it is created implicitly.
478 osc meta pkg PRJ PKG -e
479 osc meta attribute PRJ [PKG [SUBPACKAGE]] [--attribute ATTRIBUTE] [--create|--delete|--set [value_list]]
482 osc meta <prj|pkg|prjconf|user|pattern|attribute> ARGS...
483 osc meta <prj|pkg|prjconf|user|pattern|attribute> -e|--edit ARGS...
484 osc meta <prj|pkg|prjconf|user|pattern|attribute> -F|--file ARGS...
485 osc meta pattern --delete PRJ PATTERN
489 args = slash_split(args)
491 if not args or args[0] not in metatypes.keys():
492 raise oscerr.WrongArgs('Unknown meta type. Choose one of %s.' \
493 % ', '.join(metatypes))
499 min_args, max_args = 2, 2
500 elif cmd in ['pattern']:
501 min_args, max_args = 1, 2
502 elif cmd in ['attribute']:
503 min_args, max_args = 1, 3
505 min_args, max_args = 1, 1
506 if len(args) < min_args:
507 raise oscerr.WrongArgs('Too few arguments.')
508 if len(args) > max_args:
509 raise oscerr.WrongArgs('Too many arguments.')
516 project, package = args[0:2]
517 elif cmd == 'attribute':
523 if opts.attribute_project:
524 raise oscerr.WrongOptions('--attribute-project works only when also a package is given')
529 attributepath.append('source')
530 attributepath.append(project)
532 attributepath.append(package)
534 attributepath.append(subpackage)
535 attributepath.append('_attribute')
536 elif cmd == 'prjconf':
540 elif cmd == 'pattern':
546 # enforce pattern argument if needed
547 if opts.edit or opts.file:
548 raise oscerr.WrongArgs('A pattern file argument is required.')
551 if not opts.edit and not opts.file and not opts.delete and not opts.create and not opts.set:
553 sys.stdout.write(''.join(show_project_meta(conf.config['apiurl'], project)))
555 sys.stdout.write(''.join(show_package_meta(conf.config['apiurl'], project, package)))
556 elif cmd == 'attribute':
557 sys.stdout.write(''.join(show_attribute_meta(conf.config['apiurl'], project, package, subpackage, opts.attribute, opts.attribute_defaults, opts.attribute_project)))
558 elif cmd == 'prjconf':
559 sys.stdout.write(''.join(show_project_conf(conf.config['apiurl'], project)))
561 r = get_user_meta(conf.config['apiurl'], user)
563 sys.stdout.write(''.join(r))
564 elif cmd == 'pattern':
566 r = show_pattern_meta(conf.config['apiurl'], project, pattern)
568 sys.stdout.write(''.join(r))
570 r = show_pattern_metalist(conf.config['apiurl'], project)
572 sys.stdout.write('\n'.join(r) + '\n')
575 if opts.edit and not opts.file:
577 edit_meta(metatype='prj',
579 path_args=quote_plus(project),
582 'user': conf.config['user']}))
584 edit_meta(metatype='pkg',
586 path_args=(quote_plus(project), quote_plus(package)),
589 'user': conf.config['user']}))
590 elif cmd == 'prjconf':
591 edit_meta(metatype='prjconf',
593 path_args=quote_plus(project),
596 edit_meta(metatype='user',
598 path_args=(quote_plus(user)),
599 template_args=({'user': user}))
600 elif cmd == 'pattern':
601 edit_meta(metatype='pattern',
603 path_args=(project, pattern),
606 # create attribute entry
607 if (opts.create or opts.set) and cmd == 'attribute':
608 if not opts.attribute:
609 raise oscerr.WrongOptions('no attribute given to create')
612 opts.set = opts.set.replace('&', '&').replace('<', '<').replace('>', '>')
613 for i in opts.set.split(','):
614 values += '<value>%s</value>' % i
615 aname = opts.attribute.split(":")
616 d = '<attributes><attribute namespace=\'%s\' name=\'%s\' >%s</attribute></attributes>' % (aname[0], aname[1], values)
617 url = makeurl(conf.config['apiurl'], attributepath)
618 for data in streamfile(url, http_POST, data=d):
619 sys.stdout.write(data)
628 f = open(opts.file).read()
630 sys.exit('could not open file \'%s\'.' % opts.file)
633 edit_meta(metatype='prj',
636 path_args=quote_plus(project))
638 edit_meta(metatype='pkg',
641 path_args=(quote_plus(project), quote_plus(package)))
642 elif cmd == 'prjconf':
643 edit_meta(metatype='prjconf',
646 path_args=quote_plus(project))
648 edit_meta(metatype='user',
651 path_args=(quote_plus(user)))
652 elif cmd == 'pattern':
653 edit_meta(metatype='pattern',
656 path_args=(project, pattern))
661 path = metatypes[cmd]['path']
663 path = path % (project, pattern)
664 u = makeurl(conf.config['apiurl'], [path])
666 elif cmd == 'attribute':
667 if not opts.attribute:
668 raise oscerr.WrongOptions('no attribute given to create')
669 attributepath.append(opts.attribute)
670 u = makeurl(conf.config['apiurl'], attributepath)
671 for data in streamfile(u, http_DELETE):
672 sys.stdout.write(data)
674 raise oscerr.WrongOptions('The --delete switch is only for pattern metadata or attributes.')
677 @cmdln.option('-m', '--message', metavar='TEXT',
678 help='specify message TEXT')
679 @cmdln.option('-r', '--revision', metavar='REV',
680 help='for "create", specify a certain source revision ID (the md5 sum)')
681 @cmdln.option('-s', '--supersede', metavar='SUPERSEDE',
682 help='Superseding another request by this one')
683 @cmdln.option('--nodevelproject', action='store_true',
684 help='do not follow a defined devel project ' \
685 '(primary project where a package is developed)')
686 @cmdln.option('--cleanup', action='store_true',
687 help='remove package if submission gets accepted (default for home:<id>:branch projects)')
688 @cmdln.option('--no-cleanup', action='store_true',
689 help='never remove source package on accept, but update its content')
690 @cmdln.option('--no-update', action='store_true',
691 help='never touch source package on accept (will break source links)')
692 @cmdln.option('-d', '--diff', action='store_true',
693 help='show diff only instead of creating the actual request')
694 @cmdln.option('--yes', action='store_true',
695 help='proceed without asking.')
697 @cmdln.alias("submitreq")
698 @cmdln.alias("submitpac")
699 def do_submitrequest(self, subcmd, opts, *args):
700 """${cmd_name}: Create request to submit source into another Project
702 [See http://en.opensuse.org/Build_Service/Collaboration for information
705 See the "request" command for showing and modifing existing requests.
708 osc submitreq [OPTIONS]
709 osc submitreq [OPTIONS] DESTPRJ [DESTPKG]
710 osc submitreq [OPTIONS] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG]
714 src_update = conf.config['submitrequest_on_accept_action'] or None
715 # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server
717 src_update = "cleanup"
718 elif opts.no_cleanup:
719 src_update = "update"
721 src_update = "noupdate"
723 args = slash_split(args)
725 # remove this block later again
726 oldcmds = ['create', 'list', 'log', 'show', 'decline', 'accept', 'delete', 'revoke']
727 if args and args[0] in oldcmds:
728 print "************************************************************************"
729 print "* WARNING: It looks that you are using this command with a *"
730 print "* deprecated syntax. *"
731 print "* Please run \"osc sr --help\" and \"osc rq --help\" *"
732 print "* to see the new syntax. *"
733 print "************************************************************************"
734 if args[0] == 'create':
740 raise oscerr.WrongArgs('Too many arguments.')
742 if len(args) > 0 and len(args) <= 2 and is_project_dir(os.getcwd()):
743 sys.exit('osc submitrequest from project directory is only working without target specs and for source linked files\n')
745 apiurl = conf.config['apiurl']
747 if len(args) == 0 and is_project_dir(os.getcwd()):
749 # submit requests for multiple packages are currently handled via multiple requests
750 # They could be also one request with multiple actions, but that avoids to accepts parts of it.
751 project = store_read_project(os.curdir)
752 apiurl = store_read_apiurl(os.curdir)
758 # loop via all packages for checking their state
759 for p in meta_get_packagelist(apiurl, project):
760 if p.startswith("_patchinfo:"):
763 # get _link info from server, that knows about the local state ...
764 u = makeurl(apiurl, ['source', project, p])
766 root = ET.parse(f).getroot()
767 linkinfo = root.find('linkinfo')
769 print "Package ", p, " is not a source link."
770 sys.exit("This is currently not supported.")
771 if linkinfo.get('error'):
772 print "Package ", p, " is a broken source link."
773 sys.exit("Please fix this first")
774 t = linkinfo.get('project')
776 if len(root.findall('entry')) > 1: # This is not really correct, but should work mostly
777 # Real fix is to ask the api if sources are modificated
778 # but there is no such call yet.
779 targetprojects.append(t)
781 print "Submitting package ", p
783 print " Skipping package ", p
785 print "Skipping package ", p, " since it is a source link pointing inside the project."
789 print "Submitting patchinfo ", ', '.join(pi), " to ", ', '.join(targetprojects)
790 print "\nEverything fine? Can we create the requests ? [y/n]"
791 if sys.stdin.read(1) != "y":
792 sys.exit("Aborted...")
794 # loop via all packages to do the action
796 result = create_submit_request(apiurl, project, p)
799 sys.exit("submit request creation failed")
800 sr_ids.append(result)
802 # create submit requests for all found patchinfos
806 options_block="""<options><sourceupdate>%s</sourceupdate></options> """ % (src_update)
809 for t in targetprojects:
810 s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \
811 (project, p, t, p, options_block)
815 xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \
816 (actionxml, cgi.escape(opts.message or ""))
817 u = makeurl(apiurl, ['request'], query='cmd=create')
818 f = http_POST(u, data=xml)
820 root = ET.parse(f).getroot()
821 sr_ids.append(root.get('id'))
823 print "Requests created: ",
826 sys.exit('Successfull finished')
829 # try using the working copy at hand
830 p = findpacs(os.curdir)[0]
831 src_project = p.prjname
834 if len(args) == 0 and p.islink():
835 dst_project = p.linkinfo.project
836 dst_package = p.linkinfo.package
838 dst_project = args[0]
840 dst_package = args[1]
842 dst_package = src_package
844 sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n'
845 'Please provide it the target via commandline arguments.' % p.name)
847 modified = [i for i in p.filenamelist if p.status(i) != ' ' and p.status(i) != '?']
848 if len(modified) > 0:
849 print 'Your working copy has local modifications.'
850 repl = raw_input('Proceed without committing the local changes? (y|N) ')
854 # get the arguments from the commandline
855 src_project, src_package, dst_project = args[0:3]
857 dst_package = args[3]
859 dst_package = src_package
861 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
862 + self.get_cmd_help('request'))
864 if not opts.nodevelproject:
867 devloc = show_develproject(apiurl, dst_project, dst_package)
868 except urllib2.HTTPError:
869 print >>sys.stderr, """\
870 Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \
871 % (dst_project, dst_package)
875 dst_project != devloc and \
876 src_project != devloc:
878 A different project, %s, is defined as the place where development
879 of the package %s primarily takes place.
880 Please submit there instead, or use --nodevelproject to force direct submission.""" \
881 % (devloc, dst_package)
886 if opts.diff or not opts.message:
888 rdiff = 'old: %s/%s\nnew: %s/%s' %(dst_project, dst_package, src_project, src_package)
889 rdiff += server_diff(apiurl,
890 dst_project, dst_package, opts.revision,
891 src_project, src_package, None, True)
897 reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit')
898 user = conf.get_apiurl_usr(apiurl)
899 myreqs = [ i for i in reqs if i.state.who == user ]
902 print 'You already created the following submit request: %s.' % \
903 ', '.join([str(i.reqid) for i in myreqs ])
904 repl = raw_input('Supersede the old requests? (y/n/c) ')
905 if repl.lower() == 'c':
906 print >>sys.stderr, 'Aborting'
912 changes_re = re.compile(r'^--- .*\.changes ')
913 for line in rdiff.split('\n'):
914 if line.startswith('--- '):
915 if changes_re.match(line):
920 difflines.append(line)
921 opts.message = edit_message(footer=rdiff, template='\n'.join(parse_diff_for_commit_message('\n'.join(difflines))))
923 result = create_submit_request(apiurl,
924 src_project, src_package,
925 dst_project, dst_package,
926 opts.message, orev=opts.revision, src_update=src_update)
927 if repl.lower() == 'y':
929 change_request_state(apiurl, str(req.reqid), 'superseded',
930 'superseded by %s' % result, result)
933 r = change_request_state(conf.config['apiurl'],
934 opts.supersede, 'superseded', opts.message or '', result)
936 print 'created request id', result
939 @cmdln.option('-m', '--message', metavar='TEXT',
940 help='specify message TEXT')
942 @cmdln.alias("deletereq")
943 def do_deleterequest(self, subcmd, opts, *args):
944 """${cmd_name}: Create request to delete a package or project
948 osc deletereq [-m TEXT] PROJECT [PACKAGE]
952 args = slash_split(args)
955 raise oscerr.WrongArgs('Please specify at least a project.')
957 raise oscerr.WrongArgs('Too many arguments.')
959 apiurl = conf.config['apiurl']
967 opts.message = edit_message()
969 result = create_delete_request(apiurl, project, package, opts.message)
973 @cmdln.option('-m', '--message', metavar='TEXT',
974 help='specify message TEXT')
976 @cmdln.alias("changedevelreq")
977 def do_changedevelrequest(self, subcmd, opts, *args):
978 """${cmd_name}: Create request to change the devel package definition.
980 [See http://en.opensuse.org/Build_Service/Collaboration for information
983 See the "request" command for showing and modifing existing requests.
985 osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE]
989 raise oscerr.WrongArgs('Too many arguments.')
991 if len(args) == 0 and is_package_dir('.') and len(conf.config['getpac_default_project']):
993 devel_project = store_read_project(wd)
994 devel_package = package = store_read_package(wd)
995 apiurl = store_read_apiurl(wd)
996 project = conf.config['getpac_default_project']
999 raise oscerr.WrongArgs('Too few arguments.')
1001 apiurl = conf.config['apiurl']
1003 devel_project = args[2]
1006 devel_package = package
1008 devel_package = args[3]
1010 if not opts.message:
1012 footer=textwrap.TextWrapper(width = 66).fill(
1013 'please explain why you like to change the devel project of %s/%s to %s/%s'
1014 % (project,package,devel_project,devel_package))
1015 opts.message = edit_message(footer)
1017 result = create_change_devel_request(apiurl,
1018 devel_project, devel_package,
1024 @cmdln.option('-d', '--diff', action='store_true',
1025 help='generate a diff')
1026 @cmdln.option('-u', '--unified', action='store_true',
1027 help='output the diff in the unified diff format')
1028 @cmdln.option('-m', '--message', metavar='TEXT',
1029 help='specify message TEXT')
1030 @cmdln.option('-t', '--type', metavar='TYPE',
1031 help='limit to requests which contain a given action type (submit/delete/change_devel)')
1032 @cmdln.option('-a', '--all', action='store_true',
1033 help='all states. Same as\'-s all\'')
1034 @cmdln.option('-s', '--state', default='', # default is 'all' if no args given, 'new' otherwise
1035 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]')
1036 @cmdln.option('-D', '--days', metavar='DAYS',
1037 help='only list requests in state "new" or changed in the last DAYS. [default=%(request_list_days)s]')
1038 @cmdln.option('-U', '--user', metavar='USER',
1039 help='same as -M, but for the specified USER')
1040 @cmdln.option('-b', '--brief', action='store_true', default=False,
1041 help='print output in list view as list subcommand')
1042 @cmdln.option('-M', '--mine', action='store_true',
1043 help='only show requests created by yourself')
1044 @cmdln.option('-B', '--bugowner', action='store_true',
1045 help='also show requests about packages where I am bugowner')
1046 @cmdln.option('-i', '--interactive', action='store_true',
1047 help='interactive review of request')
1048 @cmdln.option('--non-interactive', action='store_true',
1049 help='non-interactive review of request')
1050 @cmdln.option('--exclude-target-project', action='append',
1051 help='exclude target project from request list')
1052 @cmdln.option('--involved-projects', action='store_true',
1053 help='show all requests for project/packages where USER is involved')
1055 @cmdln.alias("review")
1056 def do_request(self, subcmd, opts, *args):
1057 """${cmd_name}: Show and modify requests
1059 [See http://en.opensuse.org/Build_Service/Collaboration for information
1062 This command shows and modifies existing requests. To create new requests
1063 you need to call one of the following:
1066 osc changedevelrequest
1067 To send low level requests to the buildservice API, use:
1070 This command has the following sub commands:
1072 "list" lists open requests attached to a project or package or person.
1073 Uses the project/package of the current directory if none of
1074 -M, -U USER, project/package are given.
1076 "log" will show the history of the given ID
1078 "show" will show the request itself, and generate a diff for review, if
1079 used with the --diff option. The keyword show can be omitted if the ID is numeric.
1081 "decline" will change the request state to "declined" and append a
1082 message that you specify with the --message option.
1084 "wipe" will permanently delete a request.
1086 "revoke" will set the request state to "revoked" and append a
1087 message that you specify with the --message option.
1089 "accept" will change the request state to "accepted" and will trigger
1090 the actual submit process. That would normally be a server-side copy of
1091 the source package to the target package.
1093 "checkout" will checkout the request's source package. This only works for "submit" requests.
1096 osc request list [-M] [-U USER] [-s state] [-D DAYS] [-t type] [-B] [PRJ [PKG]]
1098 osc request [show] [-d] [-b] ID
1099 osc request accept [-m TEXT] ID
1100 osc request decline [-m TEXT] ID
1101 osc request revoke [-m TEXT] ID
1103 osc request checkout/co ID
1104 osc review accept [-m TEXT] ID
1105 osc review decline [-m TEXT] ID
1109 args = slash_split(args)
1111 if opts.all and opts.state:
1112 raise oscerr.WrongOptions('Sorry, the options \'--all\' and \'--state\' ' \
1113 'are mutually exclusive.')
1114 if opts.mine and opts.user:
1115 raise oscerr.WrongOptions('Sorry, the options \'--user\' and \'--mine\' ' \
1116 'are mutually exclusive.')
1117 if opts.interactive and opts.non_interactive:
1118 raise oscerr.WrongOptions('Sorry, the options \'--interactive\' and ' \
1119 '\'--non-interactive\' are mutually exclusive')
1124 if opts.state == '':
1127 if opts.state == '':
1130 cmds = ['list', 'log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co', 'help']
1131 if not args or args[0] not in cmds:
1132 raise oscerr.WrongArgs('Unknown request action %s. Choose one of %s.' \
1133 % (args[0],', '.join(cmds)))
1139 return self.do_help(['help', 'request'])
1142 min_args, max_args = 1, 1
1143 elif cmd in ['list']:
1144 min_args, max_args = 0, 2
1146 min_args, max_args = 1, 1
1147 if len(args) < min_args:
1148 raise oscerr.WrongArgs('Too few arguments.')
1149 if len(args) > max_args:
1150 raise oscerr.WrongArgs('Too many arguments.')
1152 apiurl = conf.config['apiurl']
1159 elif not opts.mine and not opts.user:
1161 project = store_read_project(os.curdir)
1162 apiurl = store_read_apiurl(os.curdir)
1163 package = store_read_package(os.curdir)
1164 except oscerr.NoWorkingCopy:
1169 elif cmd in ['log', 'show', 'decline', 'accept', 'wipe', 'revoke', 'checkout', 'co']:
1174 states = ('new', 'accepted', 'revoked', 'declined')
1175 state_list = opts.state.split(',')
1176 if opts.state == 'all':
1177 state_list = ['all']
1179 for s in state_list:
1181 raise oscerr.WrongArgs('Unknown state \'%s\', try one of %s' % (s, ','.join(states)))
1184 who = conf.get_apiurl_usr(apiurl)
1188 state_list = ['all']
1190 ## FIXME -B not implemented!
1192 if (self.options.debug):
1193 print 'list: option --bugowner ignored: not impl.'
1195 if opts.involved_projects:
1196 who = who or conf.get_apiurl_usr(apiurl)
1197 results = get_user_projpkgs_request_list(apiurl, who, req_state=state_list,
1198 req_type=opts.type, exclude_projects=opts.exclude_target_project or [])
1200 results = get_request_list(apiurl, project, package, who,
1201 state_list, opts.type, opts.exclude_target_project or [])
1202 results.sort(reverse=True)
1204 days = opts.days or conf.config['request_list_days']
1211 since = time.strftime('%Y-%m-%dT%H:%M:%S',time.localtime(time.time()-days*24*3600))
1214 ## bs has received 2009-09-20 a new xquery compare() function
1215 ## which allows us to limit the list inside of get_request_list
1216 ## That would be much faster for coolo. But counting the remainder
1217 ## would not be possible with current xquery implementation.
1218 ## Workaround: fetch all, and filter on client side.
1220 ## FIXME: date filtering should become implemented on server side
1221 for result in results:
1222 if days == 0 or result.state.when > since or result.state.name == 'new':
1223 print result.list_view()
1227 print "There are %d requests older than %s days.\n" % (skipped, days)
1230 for l in get_request_log(conf.config['apiurl'], reqid):
1235 r = get_request(conf.config['apiurl'], reqid)
1238 elif (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive:
1239 return request_interactive_review(conf.config['apiurl'], r)
1242 # fixme: will inevitably fail if the given target doesn't exist
1243 if opts.diff and r.actions[0].type != 'submit':
1244 raise oscerr.WrongOptions('\'--diff\' is not possible for request type: \'%s\'' % r.actions[0].type)
1247 print server_diff(conf.config['apiurl'],
1248 r.actions[0].dst_project, r.actions[0].dst_package, None,
1249 r.actions[0].src_project, r.actions[0].src_package, r.actions[0].src_rev, opts.unified)
1250 except urllib2.HTTPError, e:
1251 e.osc_msg = 'Diff not possible'
1255 elif cmd == 'checkout' or cmd == 'co':
1256 r = get_request(conf.config['apiurl'], reqid)
1257 submits = [ i for i in r.actions if i.type == 'submit' ]
1258 if not len(submits):
1259 raise oscerr.WrongArgs('\'checkout\' only works for \'submit\' requests')
1260 checkout_package(conf.config['apiurl'], submits[0].src_project, submits[0].src_package, \
1261 submits[0].src_rev, expand_link=True, prj_dir=submits[0].src_project)
1264 if not opts.message:
1265 opts.message = edit_message()
1266 state_map = {'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked'}
1267 # Change review state only
1268 if subcmd == 'review':
1269 if cmd in ['accept', 'decline']:
1270 r = change_review_state(conf.config['apiurl'],
1271 reqid, state_map[cmd], conf.config['user'], '', opts.message or '')
1273 # Change state of entire request
1274 elif cmd in ['accept', 'decline', 'wipe', 'revoke']:
1275 r = change_request_state(conf.config['apiurl'],
1276 reqid, state_map[cmd], opts.message or '')
1279 # editmeta and its aliases are all depracated
1280 @cmdln.alias("editprj")
1281 @cmdln.alias("createprj")
1282 @cmdln.alias("editpac")
1283 @cmdln.alias("createpac")
1284 @cmdln.alias("edituser")
1285 @cmdln.alias("usermeta")
1287 def do_editmeta(self, subcmd, opts, *args):
1290 Obsolete command to edit metadata. Use 'meta' now.
1292 See the help output of 'meta'.
1296 print >>sys.stderr, 'This command is obsolete. Use \'osc meta <metatype> ...\'.'
1297 print >>sys.stderr, 'See \'osc help meta\'.'
1298 #self.do_help([None, 'meta'])
1302 @cmdln.option('-r', '--revision', metavar='rev',
1303 help='use the specified revision.')
1304 @cmdln.option('-u', '--unset', action='store_true',
1305 help='remove revision in link, it will point always to latest revision')
1306 def do_setlinkrev(self, subcmd, opts, *args):
1307 """${cmd_name}: Updates a revision number in a source link.
1309 This command adds or updates a specified revision number in a source link.
1310 The current revision of the source is used, if no revision number is specified.
1314 osc setlinkrev PROJECT [PACKAGE]
1318 args = slash_split(args)
1319 apiurl = conf.config['apiurl']
1322 p = findpacs(os.curdir)[0]
1327 sys.exit('Local directory is no checked out source link package, aborting')
1328 elif len(args) == 2:
1331 elif len(args) == 1:
1334 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1335 + self.get_cmd_help('setlinkrev'))
1338 packages = [ package ]
1340 packages = meta_get_packagelist(apiurl, project)
1343 print "setting revision for package", p
1347 rev, dummy = parseRevisionOption(opts.revision)
1348 set_link_rev(apiurl, project, p, rev)
1351 def do_linktobranch(self, subcmd, opts, *args):
1352 """${cmd_name}: Convert a package containing a classic link with patch to a branch
1354 This command tells the server to convert a _link with or without a project.diff
1355 to a branch. This is a full copy with a _link file pointing to the branched place.
1358 osc linktobranch # can be used in checked out package
1359 osc linktobranch PROJECT PACKAGE
1363 args = slash_split(args)
1366 project = store_read_project(wd)
1367 package = store_read_package(wd)
1368 apiurl = store_read_apiurl(wd)
1369 update_local_dir = True
1371 raise oscerr.WrongArgs('Too few arguments (required none or two)')
1373 raise oscerr.WrongArgs('Too many arguments (required none or two)')
1375 apiurl = conf.config['apiurl']
1378 update_local_dir = False
1381 link_to_branch(apiurl, project, package)
1382 if update_local_dir:
1384 pac.update(rev=pac.latest_rev())
1387 @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'],
1388 help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.')
1389 @cmdln.option('-c', '--current', action='store_true',
1390 help='link fixed against current revision.')
1391 @cmdln.option('-r', '--revision', metavar='rev',
1392 help='link the specified revision.')
1393 @cmdln.option('-f', '--force', action='store_true',
1394 help='overwrite an existing link file if it is there.')
1395 @cmdln.option('-d', '--disable-publish', action='store_true',
1396 help='disable publishing of the linked package')
1397 def do_linkpac(self, subcmd, opts, *args):
1398 """${cmd_name}: "Link" a package to another package
1400 A linked package is a clone of another package, but plus local
1401 modifications. It can be cross-project.
1403 The DESTPAC name is optional; the source packages' name will be used if
1406 Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
1408 To add a patch, add the patch as file and add it to the _link file.
1409 You can also specify text which will be inserted at the top of the spec file.
1411 See the examples in the _link file.
1414 osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1418 args = slash_split(args)
1420 if not args or len(args) < 3:
1421 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1422 + self.get_cmd_help('linkpac'))
1424 rev, dummy = parseRevisionOption(opts.revision)
1426 src_project = args[0]
1427 src_package = args[1]
1428 dst_project = args[2]
1430 dst_package = args[3]
1432 dst_package = src_package
1434 if src_project == dst_project and src_package == dst_package:
1435 raise oscerr.WrongArgs('Error: source and destination are the same.')
1437 if src_project == dst_project and not opts.cicount:
1438 # in this case, the user usually wants to build different spec
1439 # files from the same source
1440 opts.cicount = "copy"
1443 rev = show_upstream_rev(conf.config['apiurl'], src_project, src_package)
1445 if rev and not checkRevision(src_project, src_package, rev):
1446 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
1449 link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish)
1451 @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]',
1452 help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]')
1453 @cmdln.option('-d', '--disable-publish', action='store_true',
1454 help='disable publishing of the aggregated package')
1455 def do_aggregatepac(self, subcmd, opts, *args):
1456 """${cmd_name}: "Aggregate" a package to another package
1458 Aggregation of a package means that the build results (binaries) of a
1459 package are basically copied into another project.
1460 This can be used to make packages available from building that are
1461 needed in a project but available only in a different project. Note
1462 that this is done at the expense of disk space. See
1463 http://en.opensuse.org/Build_Service/Tips_and_Tricks#_link_and__aggregate
1464 for more information.
1466 The DESTPAC name is optional; the source packages' name will be used if
1470 osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1474 args = slash_split(args)
1476 if not args or len(args) < 3:
1477 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1478 + self.get_cmd_help('aggregatepac'))
1480 src_project = args[0]
1481 src_package = args[1]
1482 dst_project = args[2]
1484 dst_package = args[3]
1486 dst_package = src_package
1488 if src_project == dst_project and src_package == dst_package:
1489 raise oscerr.WrongArgs('Error: source and destination are the same.')
1493 for pair in opts.map_repo.split(','):
1494 src_tgt = pair.split('=')
1495 if len(src_tgt) != 2:
1496 raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo)
1497 repo_map[src_tgt[0]] = src_tgt[1]
1499 aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish)
1502 @cmdln.option('-c', '--client-side-copy', action='store_true',
1503 help='do a (slower) client-side copy')
1504 @cmdln.option('-k', '--keep-maintainers', action='store_true',
1505 help='keep original maintainers. Default is remove all and replace with the one calling the script.')
1506 @cmdln.option('-d', '--keep-develproject', action='store_true',
1507 help='keep develproject tag in the package metadata')
1508 @cmdln.option('-r', '--revision', metavar='rev',
1509 help='link the specified revision.')
1510 @cmdln.option('-t', '--to-apiurl', metavar='URL',
1511 help='URL of destination api server. Default is the source api server.')
1512 @cmdln.option('-m', '--message', metavar='TEXT',
1513 help='specify message TEXT')
1514 @cmdln.option('-e', '--expand', action='store_true',
1515 help='if the source package is a link then copy the expanded version of the link')
1516 def do_copypac(self, subcmd, opts, *args):
1517 """${cmd_name}: Copy a package
1519 A way to copy package to somewhere else.
1521 It can be done across buildservice instances, if the -t option is used.
1522 In that case, a client-side copy is implied.
1524 Using --client-side-copy always involves downloading all files, and
1525 uploading them to the target.
1527 The DESTPAC name is optional; the source packages' name will be used if
1531 osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
1535 args = slash_split(args)
1537 if not args or len(args) < 3:
1538 raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \
1539 + self.get_cmd_help('copypac'))
1541 src_project = args[0]
1542 src_package = args[1]
1543 dst_project = args[2]
1545 dst_package = args[3]
1547 dst_package = src_package
1549 src_apiurl = conf.config['apiurl']
1551 dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl)
1553 dst_apiurl = src_apiurl
1555 if src_project == dst_project and \
1556 src_package == dst_package and \
1557 src_apiurl == dst_apiurl:
1558 raise oscerr.WrongArgs('Source and destination are the same.')
1560 if src_apiurl != dst_apiurl:
1561 opts.client_side_copy = True
1563 rev, dummy = parseRevisionOption(opts.revision)
1566 comment = opts.message
1569 rev = show_upstream_rev(src_apiurl, src_project, src_package)
1570 comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev )
1572 r = copy_pac(src_apiurl, src_project, src_package,
1573 dst_apiurl, dst_project, dst_package,
1574 client_side_copy=opts.client_side_copy,
1575 keep_maintainers=opts.keep_maintainers,
1576 keep_develproject=opts.keep_develproject,
1583 @cmdln.option('-c', '--checkout', action='store_true',
1584 help='Checkout branched package afterwards ' \
1585 '(\'osc bco\' is a shorthand for this option)' )
1586 @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE',
1587 help='Use this attribute to find affected packages (default is OBS:Maintained)')
1588 @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE',
1589 help='Use this attribute to find update projects (default is OBS:UpdateProject) ')
1590 def do_mbranch(self, subcmd, opts, *args):
1591 """${cmd_name}: Multiple branch of a package
1593 [See http://en.opensuse.org/Build_Service/Concepts/Maintenance for information
1596 This command is used for creating multiple links of defined version of a package
1597 in one project. This is esp. used for maintenance updates.
1599 The branched package will live in
1600 home:USERNAME:branches:ATTRIBUTE:PACKAGE
1601 if nothing else specified.
1604 osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ]
1607 args = slash_split(args)
1610 maintained_attribute = conf.config['maintained_attribute']
1611 maintained_update_project_attribute = conf.config['maintained_update_project_attribute']
1613 if not len(args) or len(args) > 2:
1614 raise oscerr.WrongArgs('Wrong number of arguments.')
1620 r = attribute_branch_pkg(conf.config['apiurl'], maintained_attribute, maintained_update_project_attribute, \
1624 print >>sys.stderr, 'ERROR: Attribute branch call came not back with a project.'
1627 print "Project " + r + " created."
1630 init_project_dir(conf.config['apiurl'], r, r)
1631 print statfrmt('A', r)
1634 for package in meta_get_packagelist(conf.config['apiurl'], r):
1636 checkout_package(conf.config['apiurl'], r, package, expand_link = True, prj_dir = r)
1638 print >>sys.stderr, 'Error while checkout package:\n', package
1640 if conf.config['verbose']:
1641 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1644 @cmdln.alias('branchco')
1646 @cmdln.alias('getpac')
1647 @cmdln.option('--nodevelproject', action='store_true',
1648 help='do not follow a defined devel project ' \
1649 '(primary project where a package is developed)')
1650 @cmdln.option('-c', '--checkout', action='store_true',
1651 help='Checkout branched package afterwards ' \
1652 '(\'osc bco\' is a shorthand for this option)' )
1653 @cmdln.option('-r', '--revision', metavar='rev',
1654 help='branch against a specific revision')
1655 @cmdln.option('-m', '--message', metavar='TEXT',
1656 help='specify message TEXT')
1657 def do_branch(self, subcmd, opts, *args):
1658 """${cmd_name}: Branch a package
1660 [See http://en.opensuse.org/Build_Service/Collaboration for information
1663 Create a source link from a package of an existing project to a new
1664 subproject of the requesters home project (home:branches:)
1666 The branched package will live in
1667 home:USERNAME:branches:PROJECT/PACKAGE
1668 if nothing else specified.
1670 With getpac or bco, the branched package will come from
1671 %(getpac_default_project)s
1672 if nothing else specified.
1675 osc branch SOURCEPROJECT SOURCEPACKAGE
1676 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT
1677 osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE
1678 osc getpac SOURCEPACKAGE
1683 if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': opts.checkout = True
1684 args = slash_split(args)
1685 tproject = tpackage = None
1687 if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1:
1688 print >>sys.stderr, 'defaulting to %s/%s' % (conf.config['getpac_default_project'], args[0])
1689 # python has no args.unshift ???
1690 args = [ conf.config['getpac_default_project'] , args[0] ]
1692 if len(args) < 2 or len(args) > 4:
1693 raise oscerr.WrongArgs('Wrong number of arguments.')
1694 expected = 'home:%s:branches:%s' % (conf.config['user'], args[0])
1696 expected = tproject = args[2]
1700 if not opts.message:
1701 footer='please specify the purpose of your branch'
1702 template='This package was branched from %s in order to ...\n' % args[0]
1703 opts.message = edit_message(footer, template)
1705 exists, targetprj, targetpkg, srcprj, srcpkg = \
1706 branch_pkg(conf.config['apiurl'], args[0], args[1],
1707 nodevelproject=opts.nodevelproject, rev=opts.revision,
1708 target_project=tproject, target_package=tpackage,
1709 return_existing=opts.checkout, msg=opts.message or '')
1711 print >>sys.stderr, 'Using existing branch project: %s' % targetprj
1714 if not exists and (srcprj is not None and srcprj != args[0] or \
1715 srcprj is None and targetprj != expected):
1716 devloc = srcprj or targetprj
1717 if not srcprj and 'branches:' in targetprj:
1718 devloc = targetprj.split('branches:')[1]
1719 print '\nNote: The branch has been created of a different project,\n' \
1721 ' which is the primary location of where development for\n' \
1722 ' that package takes place.\n' \
1723 ' That\'s also where you would normally make changes against.\n' \
1724 ' A direct branch of the specified package can be forced\n' \
1725 ' with the --nodevelproject option.\n' % devloc
1727 package = tpackage or args[1]
1729 checkout_package(conf.config['apiurl'], targetprj, package,
1730 expand_link=True, prj_dir=targetprj)
1731 if conf.config['verbose']:
1732 print 'Note: You can use "osc delete" or "osc submitpac" when done.\n'
1735 if conf.get_configParser().get('general', 'apiurl') != conf.config['apiurl']:
1736 apiopt = '-A %s ' % conf.config['apiurl']
1737 print 'A working copy of the branched package can be checked out with:\n\n' \
1739 % (apiopt, targetprj, package)
1740 print_request_list(conf.config['apiurl'], args[0], args[1])
1742 print_request_list(conf.config['apiurl'], devloc, args[1])
1746 @cmdln.option('-f', '--force', action='store_true',
1747 help='deletes a package or an empty project')
1748 def do_rdelete(self, subcmd, opts, *args):
1749 """${cmd_name}: Delete a project or packages on the server.
1751 As a safety measure, project must be empty (i.e., you need to delete all
1752 packages first). If you are sure that you want to remove this project and all
1753 its packages use \'--force\' switch.
1756 osc rdelete -f PROJECT
1757 osc rdelete PROJECT PACKAGE [PACKAGE ...]
1762 args = slash_split(args)
1764 raise oscerr.WrongArgs('Missing argument.')
1770 # careful: if pkg is an empty string, the package delete request results
1771 # into a project delete request - which works recursively...
1773 delete_package(conf.config['apiurl'], prj, pkg)
1774 elif len(meta_get_packagelist(conf.config['apiurl'], prj)) >= 1 and not opts.force:
1775 print >>sys.stderr, 'Project contains packages. It must be empty before deleting it. ' \
1776 'If you are sure that you want to remove this project and all its ' \
1777 'packages use the \'--force\' switch'
1780 delete_project(conf.config['apiurl'], prj)
1783 def do_deletepac(self, subcmd, opts, *args):
1784 print """${cmd_name} is obsolete !
1787 osc delete for checked out packages or projects
1789 osc rdelete for server side operations."""
1794 @cmdln.option('-f', '--force', action='store_true',
1795 help='deletes a project and its packages')
1796 def do_deleteprj(self, subcmd, opts, project):
1797 """${cmd_name} is obsolete !
1804 @cmdln.alias('metafromspec')
1805 @cmdln.option('', '--specfile', metavar='FILE',
1806 help='Path to specfile. (if you pass more than working copy this option is ignored)')
1807 def do_updatepacmetafromspec(self, subcmd, opts, *args):
1808 """${cmd_name}: Update package meta information from a specfile
1810 ARG, if specified, is a package working copy.
1816 args = parseargs(args)
1817 if opts.specfile and len(args) == 1:
1818 specfile = opts.specfile
1821 pacs = findpacs(args)
1823 p.read_meta_from_spec(specfile)
1824 p.update_package_meta()
1828 @cmdln.option('-c', '--change', metavar='rev',
1829 help='the change made by revision rev (like -r rev-1:rev).'
1830 'If rev is negative this is like -r rev:rev-1.')
1831 @cmdln.option('-r', '--revision', metavar='rev1[:rev2]',
1832 help='If rev1 is specified it will compare your working copy against '
1833 'the revision (rev1) on the server. '
1834 'If rev1 and rev2 are specified it will compare rev1 against rev2 '
1835 '(NOTE: changes in your working copy are ignored in this case)')
1836 @cmdln.option('-p', '--plain', action='store_true',
1837 help='output the diff in plain (not unified) diff format')
1838 def do_diff(self, subcmd, opts, *args):
1839 """${cmd_name}: Generates a diff
1841 Generates a diff, comparing local changes against the repository
1844 ARG, specified, is a filename to include in the diff.
1850 args = parseargs(args)
1851 pacs = findpacs(args)
1855 rev = int(opts.change)
1865 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1868 rev1, rev2 = parseRevisionOption(opts.revision)
1872 diff += ''.join(make_diff(pac, rev1))
1874 diff += server_diff(pac.apiurl, pac.prjname, pac.name, rev1,
1875 pac.prjname, pac.name, rev2, not opts.plain)
1880 @cmdln.option('--oldprj', metavar='OLDPRJ',
1881 help='project to compare against'
1882 ' (deprecated, use 3 argument form)')
1883 @cmdln.option('--oldpkg', metavar='OLDPKG',
1884 help='package to compare against'
1885 ' (deprecated, use 3 argument form)')
1886 @cmdln.option('-r', '--revision', metavar='N[:M]',
1887 help='revision id, where N = old revision and M = new revision')
1888 @cmdln.option('-p', '--plain', action='store_true',
1889 help='output the diff in plain (not unified) diff format')
1890 @cmdln.option('-c', '--change', metavar='rev',
1891 help='the change made by revision rev (like -r rev-1:rev). '
1892 'If rev is negative this is like -r rev:rev-1.')
1893 def do_rdiff(self, subcmd, opts, *args):
1894 """${cmd_name}: Server-side "pretty" diff of two packages
1896 Compares two packages (three or four arguments) or shows the
1897 changes of a specified revision of a package (two arguments)
1899 If no revision is specified the latest revision is used.
1901 Note that this command doesn't return a normal diff (which could be
1902 applied as patch), but a "pretty" diff, which also compares the content
1907 osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC]
1908 osc ${cmd_name} PROJECT PACKAGE
1912 args = slash_split(args)
1923 new_project = args[0]
1924 new_package = args[1]
1926 old_project = opts.oldprj
1928 old_package = opts.oldpkg
1929 elif len(args) == 3 or len(args) == 4:
1930 if opts.oldprj or opts.oldpkg:
1931 raise oscerr.WrongArgs('--oldpkg and --oldprj are only valid with two arguments')
1932 old_project = args[0]
1933 new_package = old_package = args[1]
1934 new_project = args[2]
1936 new_package = args[3]
1938 raise oscerr.WrongArgs('Wrong number of arguments')
1943 rev = int(opts.change)
1953 print >>sys.stderr, 'Revision \'%s\' not an integer' % opts.change
1957 rev1, rev2 = parseRevisionOption(opts.revision)
1959 rdiff = server_diff(conf.config['apiurl'],
1960 old_project, old_package, rev1,
1961 new_project, new_package, rev2, not opts.plain)
1967 def do_install(self, subcmd, opts, *args):
1968 """${cmd_name}: install a package after build via zypper in -r
1970 Not implemented yet. Use osc repourls,
1971 select the url you best like (standard),
1972 chop off after the last /, this should work with zypper.
1979 args = slash_split(args)
1980 args = expand_proj_pack(args)
1983 ## if there is only one argument, and it ends in .ymp
1984 ## then fetch it, Parse XML to get the first
1985 ## metapackage.group.repositories.repository.url
1986 ## and construct zypper cmd's for all
1987 ## metapackage.group.software.item.name
1989 ## if args[0] is already an url, the use it as is.
1991 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])
1992 print self.do_install.__doc__
1993 print "Example: \n" + cmd
1996 def do_repourls(self, subcmd, opts, *args):
1997 """${cmd_name}: Shows URLs of .repo files
1999 Shows URLs on which to access the project .repos files (yum-style
2000 metadata) on download.opensuse.org.
2003 osc repourls [PROJECT]
2008 apiurl = conf.config['apiurl']
2012 elif len(args) == 0:
2013 project = store_read_project('.')
2014 apiurl = store_read_apiurl('.')
2016 raise oscerr.WrongArgs('Wrong number of arguments')
2018 # XXX: API should somehow tell that
2019 url_tmpl = 'http://download.opensuse.org/repositories/%s/%s/%s.repo'
2020 repos = get_repositories_of_project(apiurl, project)
2022 print url_tmpl % (project.replace(':', ':/'), repo, project)
2025 @cmdln.option('-r', '--revision', metavar='rev',
2026 help='checkout the specified revision. '
2027 'NOTE: if you checkout the complete project '
2028 'this option is ignored!')
2029 @cmdln.option('-e', '--expand-link', action='store_true',
2030 help='if a package is a link, check out the expanded '
2031 'sources (no-op, since this became the default)')
2032 @cmdln.option('-u', '--unexpand-link', action='store_true',
2033 help='if a package is a link, check out the _link file ' \
2034 'instead of the expanded sources')
2035 @cmdln.option('-c', '--current-dir', action='store_true',
2036 help='place PACKAGE folder in the current directory' \
2037 'instead of a PROJECT/PACKAGE directory')
2038 @cmdln.option('-s', '--source-service-files', action='store_true',
2039 help='server side generated files of source services' \
2040 'gets downloaded as well' )
2042 def do_checkout(self, subcmd, opts, *args):
2043 """${cmd_name}: Check out content from the repository
2045 Check out content from the repository server, creating a local working
2048 When checking out a single package, the option --revision can be used
2049 to specify a revision of the package to be checked out.
2051 When a package is a source link, then it will be checked out in
2052 expanded form. If --unexpand-link option is used, the checkout will
2053 instead produce the raw _link file plus patches.
2056 osc co PROJECT [PACKAGE] [FILE]
2057 osc co PROJECT # entire project
2058 osc co PROJECT PACKAGE # a package
2059 osc co PROJECT PACKAGE FILE # single file -> to current dir
2061 while inside a project directory:
2062 osc co PACKAGE # check out PACKAGE from project
2067 if opts.unexpand_link:
2071 if opts.source_service_files:
2072 service_files = True
2074 service_files = False
2076 args = slash_split(args)
2077 project = package = filename = None
2078 apiurl = conf.config['apiurl']
2080 project = project_dir = args[0]
2086 if args and len(args) == 1:
2087 localdir = os.getcwd()
2088 if is_project_dir(localdir):
2089 project = store_read_project(localdir)
2090 project_dir = localdir
2092 apiurl = store_read_apiurl(localdir)
2094 rev, dummy = parseRevisionOption(opts.revision)
2098 if rev and rev != "latest" and not checkRevision(project, package, rev):
2099 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2103 get_source_file(apiurl, project, package, filename, revision=rev, progress_obj=self.download_progress)
2106 if opts.current_dir:
2108 checkout_package(apiurl, project, package, rev, expand_link=expand_link, \
2109 prj_dir=project_dir, service_files=service_files, progress_obj=self.download_progress)
2110 print_request_list(apiurl, project, package)
2114 if sys.platform[:3] == 'win':
2115 prj_dir = prj_dir.replace(':', ';')
2116 if os.path.exists(prj_dir):
2117 sys.exit('osc: project \'%s\' already exists' % project)
2119 # check if the project does exist (show_project_meta will throw an exception)
2120 show_project_meta(apiurl, project)
2122 init_project_dir(apiurl, prj_dir, project)
2123 print statfrmt('A', prj_dir)
2126 for package in meta_get_packagelist(apiurl, project):
2128 checkout_package(apiurl, project, package, expand_link = expand_link, \
2129 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress)
2130 except oscerr.LinkExpandError, e:
2131 print >>sys.stderr, 'Link cannot be expanded:\n', e
2132 print >>sys.stderr, 'Use "osc repairlink" for fixing merge conflicts:\n'
2133 # check out in unexpanded form at least
2134 checkout_package(apiurl, project, package, expand_link = False, \
2135 prj_dir = prj_dir, service_files = service_files, progress_obj=self.download_progress)
2136 print_request_list(apiurl, project)
2139 raise oscerr.WrongArgs('Missing argument.\n\n' \
2140 + self.get_cmd_help('checkout'))
2143 @cmdln.option('-q', '--quiet', action='store_true',
2144 help='print as little as possible')
2145 @cmdln.option('-v', '--verbose', action='store_true',
2146 help='print extra information')
2148 def do_status(self, subcmd, opts, *args):
2149 """${cmd_name}: Show status of files in working copy
2151 Show the status of files in a local working copy, indicating whether
2152 files have been changed locally, deleted, added, ...
2154 The first column in the output specifies the status and is one of the
2155 following characters:
2156 ' ' no modifications
2161 '?' item is not under version control
2162 '!' item is missing (removed by non-osc command) or incomplete
2167 osc st file1 file2 ...
2170 osc status [OPTS] [PATH...]
2174 args = parseargs(args)
2176 # storage for single Package() objects
2178 # storage for a project dir ( { prj_instance : [ package objects ] } )
2181 # when 'status' is run inside a project dir, it should
2182 # stat all packages existing in the wc
2183 if is_project_dir(arg):
2184 prj = Project(arg, False)
2186 if conf.config['do_package_tracking']:
2188 for pac in prj.pacs_have:
2189 # we cannot create package objects if the dir does not exist
2190 if not pac in prj.pacs_broken:
2191 prjpacs[prj].append(os.path.join(arg, pac))
2193 pacpaths += [arg + '/' + n for n in prj.pacs_have]
2194 elif is_package_dir(arg):
2195 pacpaths.append(arg)
2196 elif os.path.isfile(arg):
2197 pacpaths.append(arg)
2199 msg = '\'%s\' is neither a project or a package directory' % arg
2200 raise oscerr.NoWorkingCopy, msg
2202 # process single packages
2203 lines = getStatus(findpacs(pacpaths), None, opts.verbose, opts.quiet)
2204 # process project dirs
2205 for prj, pacs in prjpacs.iteritems():
2206 lines += getStatus(findpacs(pacs), prj, opts.verbose, opts.quiet)
2208 print '\n'.join(lines)
2211 def do_add(self, subcmd, opts, *args):
2212 """${cmd_name}: Mark files to be added upon the next commit
2215 osc add FILE [FILE...]
2219 raise oscerr.WrongArgs('Missing argument.\n\n' \
2220 + self.get_cmd_help('add'))
2222 filenames = parseargs(args)
2226 def do_mkpac(self, subcmd, opts, *args):
2227 """${cmd_name}: Create a new package under version control
2230 osc mkpac new_package
2233 if not conf.config['do_package_tracking']:
2234 print >>sys.stderr, "to use this feature you have to enable \'do_package_tracking\' " \
2235 "in the [general] section in the configuration file"
2239 raise oscerr.WrongArgs('Wrong number of arguments.')
2241 createPackageDir(args[0])
2243 @cmdln.option('-r', '--recursive', action='store_true',
2244 help='If CWD is a project dir then scan all package dirs as well')
2246 def do_addremove(self, subcmd, opts, *args):
2247 """${cmd_name}: Adds new files, removes disappeared files
2249 Adds all files new in the local copy, and removes all disappeared files.
2251 ARG, if specified, is a package working copy.
2257 args = parseargs(args)
2259 for arg in arg_list:
2260 if is_project_dir(arg) and conf.config['do_package_tracking']:
2261 prj = Project(arg, False)
2262 for pac in prj.pacs_unvers:
2263 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2264 if os.path.isdir(pac_dir):
2265 addFiles([pac_dir], prj)
2266 for pac in prj.pacs_broken:
2267 if prj.get_state(pac) != 'D':
2268 prj.set_state(pac, 'D')
2269 print statfrmt('D', getTransActPath(os.path.join(prj.dir, pac)))
2271 for pac in prj.pacs_have:
2272 state = prj.get_state(pac)
2273 if state != None and state != 'D':
2274 pac_dir = getTransActPath(os.path.join(prj.dir, pac))
2275 args.append(pac_dir)
2277 prj.write_packages()
2278 elif is_project_dir(arg):
2279 print >>sys.stderr, 'osc: addremove is not supported in a project dir unless ' \
2280 '\'do_package_tracking\' is enabled in the configuration file'
2283 pacs = findpacs(args)
2285 p.todo = p.filenamelist + p.filenamelist_unvers
2287 for filename in p.todo:
2288 if os.path.isdir(filename):
2290 # ignore foo.rXX, foo.mine for files which are in 'C' state
2291 if os.path.splitext(filename)[0] in p.in_conflict:
2293 state = p.status(filename)
2296 # TODO: should ignore typical backup files suffix ~ or .orig
2298 print statfrmt('A', getTransActPath(os.path.join(p.dir, filename)))
2300 p.put_on_deletelist(filename)
2301 p.write_deletelist()
2302 os.unlink(os.path.join(p.storedir, filename))
2303 print statfrmt('D', getTransActPath(os.path.join(p.dir, filename)))
2308 @cmdln.alias('checkin')
2309 @cmdln.option('-m', '--message', metavar='TEXT',
2310 help='specify log message TEXT')
2311 @cmdln.option('-F', '--file', metavar='FILE',
2312 help='read log message from FILE')
2313 @cmdln.option('-f', '--force', default=False, action="store_true",
2314 help='force commit - do not tests a file list')
2315 def do_commit(self, subcmd, opts, *args):
2316 """${cmd_name}: Upload content to the repository server
2318 Upload content which is changed in your working copy, to the repository
2321 Optionally checks the state of a working copy, if found a file with
2322 unknown state, it requests an user input:
2323 * skip - don't change anything, just move to another file
2324 * remove - remove a file from dir
2325 * edit file list - edit filelist using EDITOR
2326 * commit - don't check anything and commit package
2327 * abort - abort commit - this is default value
2328 This can be supressed by check_filelist config item, or -f/--force
2329 command line option.
2332 osc ci # current dir
2334 osc ci file1 file2 ...
2340 args = parseargs(args)
2347 msg = open(opts.file).read()
2349 sys.exit('could not open file \'%s\'.' % opts.file)
2352 for arg in arg_list:
2353 if conf.config['do_package_tracking'] and is_project_dir(arg):
2354 Project(arg).commit(msg=msg)
2356 msg = edit_message()
2359 pacs = findpacs(args)
2361 if conf.config['check_filelist'] and not opts.force:
2362 check_filelist_before_commit(pacs)
2365 template = store_read_file(os.path.abspath('.'), '_commit_msg')
2366 # open editor for commit message
2367 # but first, produce status and diff to append to the template
2371 changed = getStatus([pac], quiet=True)
2374 diffs += ['\nDiff for working copy: %s' % pac.dir]
2375 diffs += make_diff(pac, 0)
2376 lines.extend(get_commit_message_template(pac))
2377 if template == None:
2378 template='\n'.join(lines)
2379 # if footer is empty, there is nothing to commit, and no edit needed.
2381 msg = edit_message(footer='\n'.join(footer), template=template)
2384 store_write_string(os.path.abspath('.'), '_commit_msg', msg)
2386 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2388 if conf.config['do_package_tracking'] and len(pacs) > 0:
2392 # it is possible to commit packages from different projects at the same
2393 # time: iterate over all pacs and put each pac to the right project in the dict
2395 path = os.path.normpath(os.path.join(pac.dir, os.pardir))
2396 if is_project_dir(path):
2397 pac_path = os.path.basename(os.path.normpath(pac.absdir))
2398 prj_paths.setdefault(path, []).append(pac_path)
2399 files[pac_path] = pac.todo
2401 single_paths.append(pac.dir)
2402 for prj, packages in prj_paths.iteritems():
2403 Project(prj).commit(tuple(packages), msg, files)
2404 for pac in single_paths:
2405 Package(pac).commit(msg)
2410 store_unlink_file(os.path.abspath('.'), '_commit_msg')
2412 @cmdln.option('-r', '--revision', metavar='REV',
2413 help='update to specified revision (this option will be ignored '
2414 'if you are going to update the complete project or more than '
2416 @cmdln.option('-u', '--unexpand-link', action='store_true',
2417 help='if a package is an expanded link, update to the raw _link file')
2418 @cmdln.option('-e', '--expand-link', action='store_true',
2419 help='if a package is a link, update to the expanded sources')
2420 @cmdln.option('-s', '--source-service-files', action='store_true',
2421 help='Use server side generated sources instead of local generation.' )
2423 def do_update(self, subcmd, opts, *args):
2424 """${cmd_name}: Update a working copy
2429 If the current working directory is a package, update it.
2430 If the directory is a project directory, update all contained
2431 packages, AND check out newly added packages.
2433 To update only checked out packages, without checking out new
2434 ones, you might want to use "osc up *" from within the project
2438 Update the packages specified by the path argument(s)
2440 When --expand-link is used with source link packages, the expanded
2441 sources will be checked out. Without this option, the _link file and
2442 patches will be checked out. The option --unexpand-link can be used to
2443 switch back to the "raw" source with a _link file plus patch(es).
2449 if (opts.expand_link and opts.unexpand_link) \
2450 or (opts.expand_link and opts.revision) \
2451 or (opts.unexpand_link and opts.revision):
2452 raise oscerr.WrongOptions('Sorry, the options --expand-link, --unexpand-link and '
2453 '--revision are mutually exclusive.')
2455 if opts.source_service_files: service_files = True
2456 else: service_files = False
2458 args = parseargs(args)
2461 for arg in arg_list:
2462 if is_project_dir(arg):
2463 prj = Project(arg, progress_obj=self.download_progress)
2465 if conf.config['do_package_tracking']:
2466 prj.update(expand_link=opts.expand_link,
2467 unexpand_link=opts.unexpand_link)
2470 # if not tracking package, and 'update' is run inside a project dir,
2471 # it should do the following:
2472 # (a) update all packages
2473 args += prj.pacs_have
2474 # (b) fetch new packages
2475 prj.checkout_missing_pacs(expand_link = not opts.unexpand_link)
2477 print_request_list(prj.apiurl, prj.name)
2480 pacs = findpacs(args, progress_obj=self.download_progress)
2482 if opts.revision and len(args) == 1:
2483 rev, dummy = parseRevisionOption(opts.revision)
2484 if not checkRevision(pacs[0].prjname, pacs[0].name, rev, pacs[0].apiurl):
2485 print >>sys.stderr, 'Revision \'%s\' does not exist' % rev
2492 print 'Updating %s' % p.name
2494 # FIXME: ugly workaround for #399247
2495 if opts.expand_link or opts.unexpand_link:
2496 if [ i for i in p.filenamelist+p.filenamelist_unvers if p.status(i) != ' ' and p.status(i) != '?']:
2497 print >>sys.stderr, 'osc: cannot expand/unexpand because your working ' \
2498 'copy has local modifications.\nPlease revert/commit them ' \
2503 if opts.expand_link and p.islink() and not p.isexpanded():
2504 if p.haslinkerror():
2506 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev)
2508 rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev, linkrev="base")
2511 rev = p.linkinfo.xsrcmd5
2512 print 'Expanding to rev', rev
2513 elif opts.unexpand_link and p.islink() and p.isexpanded():
2514 print 'Unexpanding to rev', p.linkinfo.lsrcmd5
2515 rev = p.linkinfo.lsrcmd5
2516 elif p.islink() and p.isexpanded():
2517 rev = p.latest_rev()
2519 p.update(rev, service_files)
2520 if opts.unexpand_link:
2523 print_request_list(p.apiurl, p.prjname, p.name)
2526 @cmdln.option('-f', '--force', action='store_true',
2527 help='forces removal of entire package and its files')
2530 @cmdln.alias('remove')
2531 def do_delete(self, subcmd, opts, *args):
2532 """${cmd_name}: Mark files or package directories to be deleted upon the next 'checkin'
2535 cd .../PROJECT/PACKAGE
2536 osc delete FILE [...]
2538 osc delete PACKAGE [...]
2540 This command works on check out copies. Use "rdelete" for working on server
2541 side only. This is needed for removing the entire project.
2543 As a safety measure, projects must be empty (i.e., you need to delete all
2546 If you are sure that you want to remove a package and all
2547 its files use \'--force\' switch. Sometimes this also works without --force.
2553 raise oscerr.WrongArgs('Missing argument.\n\n' \
2554 + self.get_cmd_help('delete'))
2556 args = parseargs(args)
2557 # check if args contains a package which was removed by
2558 # a non-osc command and mark it with the 'D'-state
2561 if not os.path.exists(i):
2562 prj_dir, pac_dir = getPrjPacPaths(i)
2563 if is_project_dir(prj_dir):
2564 prj = Project(prj_dir, False)
2565 if i in prj.pacs_broken:
2566 if prj.get_state(i) != 'A':
2567 prj.set_state(pac_dir, 'D')
2569 prj.del_package_node(i)
2570 print statfrmt('D', getTransActPath(i))
2572 prj.write_packages()
2573 pacs = findpacs(args)
2577 prj_dir, pac_dir = getPrjPacPaths(p.absdir)
2578 if is_project_dir(prj_dir):
2579 if conf.config['do_package_tracking']:
2580 prj = Project(prj_dir, False)
2581 prj.delPackage(p, opts.force)
2583 print "WARNING: package tracking is disabled, operation skipped !"
2585 pathn = getTransActPath(p.dir)
2586 for filename in p.todo:
2587 ret, state = p.delete_file(filename, opts.force)
2589 print statfrmt('D', os.path.join(pathn, filename))
2592 sys.exit('\'%s\' is not under version control' % filename)
2593 elif state in ['A', 'M'] and not opts.force:
2594 sys.exit('\'%s\' has local modifications (use --force to remove this file)' % filename)
2597 def do_resolved(self, subcmd, opts, *args):
2598 """${cmd_name}: Remove 'conflicted' state on working copy files
2600 If an upstream change can't be merged automatically, a file is put into
2601 in 'conflicted' ('C') state. Within the file, conflicts are marked with
2602 special <<<<<<< as well as ======== and >>>>>>> lines.
2604 After manually resolving all conflicting parts, use this command to
2605 remove the 'conflicted' state.
2607 Note: this subcommand does not semantically resolve conflicts or
2608 remove conflict markers; it merely removes the conflict-related
2609 artifact files and allows PATH to be committed again.
2612 osc resolved FILE [FILE...]
2617 raise oscerr.WrongArgs('Missing argument.\n\n' \
2618 + self.get_cmd_help('resolved'))
2620 args = parseargs(args)
2621 pacs = findpacs(args)
2624 for filename in p.todo:
2625 print 'Resolved conflicted state of "%s"' % filename
2626 p.clear_from_conflictlist(filename)
2629 @cmdln.alias('platforms')
2630 def do_repositories(self, subcmd, opts, *args):
2631 """${cmd_name}: Shows available repositories
2635 Shows all available repositories/build targets
2637 2. osc repositories <project>
2638 Shows the configured repositories/build targets of a project
2646 print '\n'.join(get_repositories_of_project(conf.config['apiurl'], project))
2648 print '\n'.join(get_repositories(conf.config['apiurl']))
2652 def do_results_meta(self, subcmd, opts, *args):
2653 print "Command results_meta is obsolete. Please use: osc results --xml"
2657 @cmdln.option('-l', '--last-build', action='store_true',
2658 help='show last build results (succeeded/failed/unknown)')
2659 @cmdln.option('-r', '--repo', action='append', default = [],
2660 help='Show results only for specified repo(s)')
2661 @cmdln.option('-a', '--arch', action='append', default = [],
2662 help='Show results only for specified architecture(s)')
2663 @cmdln.option('', '--xml', action='store_true',
2664 help='generate output in XML (former results_meta)')
2665 def do_rresults(self, subcmd, opts, *args):
2666 print "Command rresults is obsolete. Running 'osc results' instead"
2667 self.do_results('results', opts, *args)
2671 @cmdln.option('-f', '--force', action='store_true', default=False,
2672 help="Don't ask and delete files")
2673 def do_rremove(self, subcmd, opts, project, package, *files):
2674 """${cmd_name}: Remove source files from selected package
2681 if not '/' in project:
2682 raise oscerr.WrongArgs("Missing operand, type osc help rremove for help")
2685 project, package = project.split('/')
2689 resp = raw_input("rm: remove source file `%s' from `%s/%s'? (yY|nN) " % (file, project, package))
2690 if resp not in ('y', 'Y'):
2693 delete_files(conf.config['apiurl'], project, package, (file, ))
2694 except urllib2.HTTPError, e:
2696 print >>sys.stderr, e
2698 if e.code in [ 400, 403, 404, 500 ]:
2699 if '<summary>' in body:
2700 msg = body.split('<summary>')[1]
2701 msg = msg.split('</summary>')[0]
2702 print >>sys.stderr, msg
2707 @cmdln.option('-l', '--last-build', action='store_true',
2708 help='show last build results (succeeded/failed/unknown)')
2709 @cmdln.option('-r', '--repo', action='append', default = [],
2710 help='Show results only for specified repo(s)')
2711 @cmdln.option('-a', '--arch', action='append', default = [],
2712 help='Show results only for specified architecture(s)')
2713 @cmdln.option('', '--xml', action='store_true',
2714 help='generate output in XML (former results_meta)')
2715 def do_results(self, subcmd, opts, *args):
2716 """${cmd_name}: Shows the build results of a package
2719 osc results (inside working copy)
2720 osc results remote_project remote_package
2725 args = slash_split(args)
2727 apiurl = conf.config['apiurl']
2730 if is_project_dir(wd):
2734 opts.hide_legend = None
2735 opts.name_filter = None
2736 opts.status_filter = None
2737 opts.vertical = None
2738 self.do_prjresults('prjresults', opts, *args)
2741 project = store_read_project(wd)
2742 package = store_read_package(wd)
2743 apiurl = store_read_apiurl(wd)
2745 raise oscerr.WrongArgs('Too few arguments (required none or two)')
2747 raise oscerr.WrongArgs('Too many arguments (required none or two)')
2756 func = show_results_meta
2759 print delim.join(func(apiurl, project, package, opts.last_build, opts.repo, opts.arch))
2761 # WARNING: this function is also called by do_results. You need to set a default there
2762 # as well when adding a new option!
2763 @cmdln.option('-q', '--hide-legend', action='store_true',
2764 help='hide the legend')
2765 @cmdln.option('-c', '--csv', action='store_true',
2767 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2768 help='show only packages with buildstatus STATUS (see legend)')
2769 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2770 help='show only packages whose names match EXPR')
2771 @cmdln.option('-a', '--arch', metavar='ARCH',
2772 help='show results only for specified architecture(s)')
2773 @cmdln.option('-r', '--repo', metavar='REPO',
2774 help='show results only for specified repo(s)')
2775 @cmdln.option('-V', '--vertical', action='store_true',
2776 help='list packages vertically instead horizontally')
2778 def do_prjresults(self, subcmd, opts, *args):
2779 """${cmd_name}: Shows project-wide build results
2782 osc prjresults (inside working copy)
2783 osc prjresults PROJECT
2789 apiurl = conf.config['apiurl']
2793 raise oscerr.WrongArgs('Wrong number of arguments.')
2796 project = store_read_project(wd)
2797 apiurl = store_read_apiurl(wd)
2799 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))
2802 @cmdln.option('-q', '--hide-legend', action='store_true',
2803 help='hide the legend')
2804 @cmdln.option('-c', '--csv', action='store_true',
2806 @cmdln.option('-s', '--status-filter', metavar='STATUS',
2807 help='show only packages with buildstatus STATUS (see legend)')
2808 @cmdln.option('-n', '--name-filter', metavar='EXPR',
2809 help='show only packages whose names match EXPR')
2812 def do_rprjresults(self, subcmd, opts, *args):
2813 print "Command rprjresults is obsolete. Please use 'osc prjresults'"
2817 @cmdln.option('-s', '--start', metavar='START',
2818 help='get log starting from the offset')
2819 def do_buildlog(self, subcmd, opts, *args):
2820 """${cmd_name}: Shows the build log of a package
2822 Shows the log file of the build of a package. Can be used to follow the
2823 log while it is being written.
2824 Needs to be called from within a package directory.
2826 The arguments REPOSITORY and ARCH are the first two columns in the 'osc
2827 results' output. If the buildlog url is used buildlog command has the
2828 same behavior as remotebuildlog.
2830 ${cmd_usage} [REPOSITORY ARCH | BUILDLOGURL]
2834 repository = arch = None
2836 if len(args) == 1 and args[0].startswith('http'):
2837 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2840 package = store_read_package(wd)
2841 project = store_read_project(wd)
2842 apiurl = store_read_apiurl(wd)
2846 offset = int(opts.start)
2848 if not repository or not arch:
2852 repository = args[0]
2855 print_buildlog(apiurl, project, package, repository, arch, offset)
2858 def print_repos(self):
2861 if is_package_dir(wd):
2864 elif is_project_dir(wd):
2869 print 'Valid arguments for this %s are:' % str
2871 self.do_repos(None, None)
2873 raise oscerr.WrongArgs('Missing arguments')
2876 @cmdln.alias('rbuildlog')
2877 @cmdln.option('-s', '--start', metavar='START',
2878 help='get log starting from the offset')
2879 def do_remotebuildlog(self, subcmd, opts, *args):
2880 """${cmd_name}: Shows the build log of a package
2882 Shows the log file of the build of a package. Can be used to follow the
2883 log while it is being written.
2886 osc remotebuildlog project package repository arch
2888 osc remotebuildlog project/package/repository/arch
2890 osc remotebuildlog buildlogurl
2893 if len(args) == 1 and args[0].startswith('http'):
2894 apiurl, project, package, repository, arch = parse_buildlogurl(args[0])
2896 args = slash_split(args)
2897 apiurl = conf.config['apiurl']
2899 raise oscerr.WrongArgs('Too few arguments.')
2901 raise oscerr.WrongArgs('Too many arguments.')
2903 project, package, repository, arch = args
2907 offset = int(opts.start)
2909 print_buildlog(apiurl, project, package, repository, arch, offset)
2912 @cmdln.option('-s', '--start', metavar='START',
2913 help='get log starting from offset')
2914 def do_localbuildlog(self, subcmd, opts, *args):
2915 """${cmd_name}: Shows the build log of a local buildchroot
2918 osc lbl [REPOSITORY ARCH]
2919 osc lbl # show log of newest last local build
2923 if conf.config['build-type']:
2924 # FIXME: raise Exception instead
2925 print >>sys.stderr, 'Not implemented for VMs'
2929 package = store_read_package('.')
2931 files = glob.glob(os.path.join(os.getcwd(), store, "_buildinfo-*"))
2934 raise oscerr.WrongArgs('No buildconfig found, please specify repo and arch manually.')
2938 if os.stat(f).st_mtime > os.stat(cfg).st_mtime:
2940 root = ET.parse(cfg).getroot()
2941 project = root.get("project")
2942 repo = root.get("repository")
2943 arch = root.find("arch").text
2944 elif len(args) == 2:
2945 project = store_read_project('.')
2946 package = store_read_package('.')
2950 if is_package_dir(os.curdir):
2952 raise oscerr.WrongArgs('Wrong number of arguments.')
2954 buildroot = os.environ.get('OSC_BUILD_ROOT', conf.config['build-root'])
2955 buildroot = buildroot % {'project': project, 'package': package,
2956 'repo': repo, 'arch': arch}
2959 offset = int(opts.start)
2960 logfile = os.path.join(buildroot, '.build.log')
2961 if not os.path.isfile(logfile):
2962 raise oscerr.OscIOError(None, 'logfile \'%s\' does not exist' % logfile)
2963 f = open(logfile, 'r')
2965 data = f.read(BUFSIZE)
2967 sys.stdout.write(data)
2968 data = f.read(BUFSIZE)
2972 def do_triggerreason(self, subcmd, opts, *args):
2973 """${cmd_name}: Show reason why a package got triggered to build
2975 The server decides when a package needs to get rebuild, this command
2976 shows the detailed reason for a package. A brief reason is also stored
2977 in the jobhistory, which can be accessed via "osc jobhistory".
2979 Trigger reasons might be:
2980 - new build (never build yet or rebuild manually forced)
2981 - source change (eg. on updating sources)
2982 - meta change (packages which are used for building have changed)
2983 - rebuild count sync (In case that it is configured to sync release numbers)
2985 usage in package or project directory:
2986 osc reason REPOSITORY ARCH
2987 osc reason PROJECT PACKAGE REPOSITORY ARCH
2992 args = slash_split(args)
2993 project = package = repository = arch = None
2998 if len(args) == 2: # 2
2999 if is_package_dir('.'):
3000 package = store_read_package(wd)
3002 raise oscerr.WrongArgs('package is not specified.')
3003 project = store_read_project(wd)
3004 apiurl = store_read_apiurl(wd)
3005 repository = args[0]
3007 elif len(args) == 4:
3008 apiurl = conf.config['apiurl']
3011 repository = args[2]
3014 raise oscerr.WrongArgs('Too many arguments.')
3016 print apiurl, project, package, repository, arch
3017 xml = show_package_trigger_reason(apiurl, project, package, repository, arch)
3018 root = ET.fromstring(xml)
3019 reason = root.find('explain').text
3021 if reason == "meta change":
3022 print "changed keys:"
3023 for package in root.findall('packagechange'):
3024 print " ", package.get('change'), package.get('key')
3027 # FIXME: the new osc syntax should allow to specify multiple packages
3028 # FIXME: the command should optionally use buildinfo data to show all dependencies
3029 @cmdln.alias('whatdependson')
3030 def do_dependson(self, subcmd, opts, *args):
3031 """${cmd_name}: Show the build dependencies
3033 The command dependson and whatdependson can be used to find out what
3034 will be triggered when a certain package changes.
3035 This is no guarantee, since the new build might have changed dependencies.
3037 dependson shows the build dependencies inside of a project, valid for a
3038 given repository and architecture.
3039 NOTE: to see all binary packages, which can trigger a build you need to
3040 refer the buildinfo, since this command shows only the dependencies
3041 inside of a project.
3043 The arguments REPOSITORY and ARCH can be taken from the first two columns
3044 of the 'osc repos' output.
3046 usage in package or project directory:
3047 osc dependson REPOSITORY ARCH
3048 osc whatdependson REPOSITORY ARCH
3051 osc dependson PROJECT [PACKAGE] REPOSITORY ARCH
3052 osc whatdependson PROJECT [PACKAGE] REPOSITORY ARCH
3057 args = slash_split(args)
3058 project = packages = repository = arch = reverse = None
3060 if len(args) < 2 and (is_package_dir('.') or is_project_dir('.')):
3064 raise oscerr.WrongArgs('Too many arguments.')
3066 if len(args) < 3: # 2
3067 if is_package_dir('.'):
3068 packages = [store_read_package(wd)]
3069 elif not is_project_dir('.'):
3070 raise oscerr.WrongArgs('Project and package is not specified.')
3071 project = store_read_project(wd)
3072 apiurl = store_read_apiurl(wd)
3073 repository = args[0]
3077 apiurl = conf.config['apiurl']
3079 repository = args[1]
3083 apiurl = conf.config['apiurl']
3085 packages = [args[1]]
3086 repository = args[2]
3089 if subcmd == 'whatdependson':
3092 xml = get_dependson(apiurl, project, repository, arch, packages, reverse)
3094 root = ET.fromstring(xml)
3095 for package in root.findall('package'):
3096 print package.get('name'), ":"
3097 for dep in package.findall('pkgdep'):