1
0
mirror of https://github.com/openSUSE/osc.git synced 2024-12-27 10:16:14 +01:00
github.com_openSUSE_osc/osc/commandline.py
Dr. Peter Poeml 9a944edfeb - sort output of 'status' (unknown files first, filenames alphabetically)
- fix the unit tests for the above change, and for api changes
2007-01-23 00:32:45 +00:00

1080 lines
29 KiB
Python
Executable File

#!/usr/bin/python
# Copyright (C) 2006 Peter Poeml / Novell Inc. All rights reserved.
# This program is free software; it may be used, copied, modified
# and distributed under the terms of the GNU General Public Licence,
# either version 2, or (at your option) any later version.
from core import *
import conf
usage_general = """\
usage: osc <subcommand> [options] [args]
OpenSUSE build service command-line tool, version %s.
Type 'osc help <subcommand>' for help on a specific subcommand.
Most subcommands take file and/or directory arguments, recursing
on the directories. If no arguments are supplied to such a
command, it recurses on the current directory (inclusive) by default.
Please see also:
* http://en.opensuse.org/Build_Service_Tutorial
* http://en.opensuse.org/Build_Service/CLI
Available subcommands:
""" % get_osc_version()
def init(args):
"""Initialize a directory to be a working copy of an existing buildservice
package. (This is the same as checking out a package and then copying sources
into the directory. It does NOT create a new package.)
usage: osc init <prj> <pac>
"""
project = args[0]
package = args[1]
init_package_dir(project, package, os.path.curdir)
print 'Initializing %s (Project: %s, Package: %s)' % (os.curdir, project, package)
def ls(args):
"""ls (list): List existing content on the server
usage: osc ls # list projects
ls Apache # list packages in a project
ls Apache subversion # list files of package of a project
"""
if not args:
print '\n'.join(meta_get_project_list())
elif len(args) == 1:
project = args[0]
print '\n'.join(meta_get_packagelist(project))
elif len(args) == 2:
project = args[0]
package = args[1]
print '\n'.join(meta_get_filelist(project, package))
def meta(args):
"""Shows meta information
usage: osc meta Apache # show meta of project 'Apache'
osc meta Apache subversion # show meta of package 'subversion'
"""
if not args:
print 'missing argument'
print
print meta.func_doc
sys.exit(1)
if len(args) == 2:
project = args[0]
package = args[1]
print ''.join(show_package_meta(project, package))
print ''.join(show_files_meta(project, package))
elif len(args) == 1:
project = args[0]
print ''.join(show_project_meta(project))
def editpac(args):
"""editpac (createpac): Edit package meta information
If the named package does not exist, it will be created.
usage: osc createpac <prj> <pac>
osc editpac <prj> <pac>
"""
if not args or len(args) != 2:
sys.exit('missing argument\n\n%s\n' % editpac.func_doc)
project = args[0]
package = args[1]
edit_meta(project, package)
def editprj(args):
"""editprj (createprj): Edit project meta information
If the named project does not exist, it will be created.
usage: osc createprj <prj>
osc editprj <prj>
"""
if not args or len(args) != 1:
sys.exit('missing argument\n\n%s\n' % editprj.func_doc)
project = args[0]
edit_meta(project, None)
def editmeta(args):
"""Edit project/package meta information
If the named project or package does not exist, it will be created.
usage: osc editmeta FooPrj # edit meta of project 'FooPrj'
osc editmeta FooPrj barpackage # edit meta of package 'barpackage'
"""
if not args:
print 'missing argument'
print
print editmeta.func_doc
sys.exit(1)
if len(args) == 2:
project = args[0]
package = args[1]
edit_meta(project, package)
elif len(args) == 1:
project = args[0]
edit_meta(project, None)
def edituser(args):
"""edituser: Edit user meta information
usage: osc edituser <user>
If the named user id does not exist, it will be created.
"""
if not args or len(args) != 1:
user = conf.config['user']
else:
user = args[0]
edit_user_meta(user)
def linkpac(args):
""""Link" a package to another package -- possibly cross-project.
usage: osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
The DESTPAC name is optional; the source packages' name will be used if
DESTPAC is omitted.
Afterwards, you will want to 'checkout DESTPRJ DESTPAC'.
To add a patch, add the patch as file and add it to the _link file.
You can also specify text which will be inserted at the top of the spec file.
See the examples in the _link file.
"""
if not args or len(args) < 3:
print 'missing argument'
print
print linkpac.func_doc
sys.exit(1)
src_project = args[0]
src_package = args[1]
dst_project = args[2]
if len(args) > 3:
dst_package = args[3]
else:
dst_package = src_package
if src_project == dst_project and src_package == dst_package:
sys.exit('osc: error: source and destination are the same')
link_pac(src_project, src_package, dst_project, dst_package)
def copypac(args):
""""Copy" a package, possibly cross-project.
usage: osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC]
The DESTPAC name is optional; the source packages' name will be used if
DESTPAC is omitted.
"""
if not args or len(args) < 3:
print 'missing argument'
print
print copypac.func_doc
sys.exit(1)
src_project = args[0]
src_package = args[1]
dst_project = args[2]
if len(args) > 3:
dst_package = args[3]
else:
dst_package = src_package
if src_project == dst_project and src_package == dst_package:
sys.exit('osc: error: source and destination are the same')
copy_pac(src_project, src_package, dst_project, dst_package)
def deletepac(args):
"""deletepac: Delete a package on the server.
usage: osc deletepac <prj> <pac>
"""
if not args or len(args) < 2:
print 'missing argument'
print
print deletepac.func_doc
sys.exit(1)
project = args[0]
package = args[1]
delete_package(project, package)
def deleteprj(args):
"""deleteprj: Delete a project on the server.
usage: osc deleteprj <prj>
As a safety measure, project must be empty (i.e., you first need to delete all
packages first).
"""
if not args or len(args) < 1:
print 'missing argument'
print
print deleteprj.func_doc
sys.exit(1)
project = args[0]
if meta_get_packagelist(project) != []:
sys.exit('project must be empty before deleting it.')
delete_project(project)
def updatepacmetafromspec(args):
"""Update package meta information from a specfile
usage: 1. osc updatepacmetafromspec # current dir
2. osc updatepacmetafromspec dir1 dir2 ...
"""
args = parseargs(args)
pacs = findpacs(args)
for p in pacs:
p.read_meta_from_spec()
p.update_pac_meta()
def diff(args):
"""diff: Generates a diff, to view the local changes
usage: 1. osc diff # current dir
2. osc diff file1 file2 ...
"""
args = parseargs(args)
pacs = findpacs(args)
for p in pacs:
if p.todo == []:
for i in p.filenamelist:
s = p.status(i)
if s == 'M' or s == 'C':
p.todo.append(i)
d = []
for filename in p.todo:
d.append('Index: %s\n' % filename)
d.append('===================================================================\n')
d.append(get_source_file_diff(p.dir, filename, p.rev))
if d:
print ''.join(d)
def repourls(args):
"""repourls: shows URLs on which to access the .repos files
usage: 1. osc repourls
2. osc repourls [dir1] [dir2] ...
"""
args = parseargs(args)
pacs = findpacs(args)
url_tmpl = 'http://software.opensuse.org/download/%s/%s/%s.repo'
for p in pacs:
platforms = get_platforms_of_project(p.prjname)
for platform in platforms:
print url_tmpl % (p.prjname.replace(':', ':/'), platform, p.prjname)
def checkout(args):
"""checkout (co): Check out content from the server.
usage: osc co Apache # entire project
osc co Apache subversion # a package
osc co Apache subversion foo # single file -> to current dir
"""
project = package = filename = None
try:
project = args[0]
package = args[1]
filename = args[2]
except:
pass
if filename:
get_source_file(project, package, filename)
elif package:
checkout_package(project, package)
elif project:
# all packages
for package in meta_get_packagelist(project):
checkout_package(project, package)
else:
print 'missing argument'
print
print checkout.func_doc
sys.exit(1)
def status(args):
"""Show the status (which files have been changed locally)
usage: osc st
osc st <directory>
osc st file1 file2 ...
"""
args = parseargs(args)
pacpaths = []
for arg in args:
# when 'status' is run inside a project dir, it should
# stat all packages existing in the wc
if is_project_dir(arg):
prj = Project(arg)
pacpaths += [arg + '/' + n for n in prj.pacs_have]
elif is_package_dir(arg):
pacpaths.append(arg)
elif os.path.isfile(arg):
pacpaths.append(arg)
else:
sys.exit('osc: error: %s is neither a project or a package directory' % arg)
pacs = findpacs(pacpaths)
for p in pacs:
# no files given as argument? Take all files in current dir
if not p.todo:
p.todo = p.filenamelist + p.filenamelist_unvers
p.todo.sort()
lines = []
for filename in p.todo:
if filename in p.excluded:
continue
s = p.status(filename)
if s == 'F':
lines.append(statfrmt('!', pathjoin(p.dir, filename)))
elif s != ' ':
lines.append(statfrmt(s, pathjoin(p.dir, filename)))
# for -v (later)
#else:
# lines.append(statfrmt(s, pathjoin(p.dir, filename)))
# arrange the lines in order: unknown files first
# filenames are already sorted
lines = [line for line in lines if line[0] == '?'] \
+ [line for line in lines if line[0] != '?']
if lines:
print '\n'.join(lines)
def add(args):
"""Mark files to be added upon next 'checkin'
usage: osc add file1 file2 ...
"""
if not args:
print '%s requires at least one argument' % cmd
sys.exit(1)
filenames = parseargs(args)
for filename in filenames:
if not os.path.exists(filename):
print "file '%s' does not exist" % filename
sys.exit(1)
pacs = findpacs(filenames)
for pac in pacs:
for filename in pac.todo:
if filename in pac.excluded:
continue
if filename in pac.filenamelist:
print 'osc: warning: \'%s\' is already under version control' % filename
continue
pac.addfile(filename)
print statfrmt('A', filename)
def addremove(args):
"""addremove: Adds all new files in local copy and removes all disappeared files.
usage: osc addremove
"""
args = parseargs(args)
pacs = findpacs(args)
for p in pacs:
p.todo = p.filenamelist + p.filenamelist_unvers
for filename in p.todo:
if os.path.isdir(filename):
continue
state = p.status(filename)
if state == '?':
p.addfile(filename)
print statfrmt('A', filename)
elif state == '!':
p.put_on_deletelist(filename)
p.write_deletelist()
os.unlink(os.path.join(p.storedir, filename))
print statfrmt('D', filename)
def commit(args):
"""commit (ci): Upload change content from your working copy to the repository
usage: osc ci # current dir
osc ci <dir>
osc ci file1 file2 ...
"""
args = parseargs(args)
pacs = findpacs(args)
for p in pacs:
# commit only if the upstream revision is the same as the working copy's
upstream_rev = show_upstream_rev(p.prjname, p.name)
if p.rev != upstream_rev:
print 'Working copy \'%s\' is out of date (rev %s vs rev %s).' % (p.absdir, p.rev, upstream_rev)
print 'Looks as if you need to update it first.'
sys.exit(1)
p.todo = p.filenamelist_unvers + p.filenamelist
for filename in p.todo:
st = p.status(filename)
if st == 'A' or st == 'M':
p.todo_send.append(filename)
print 'Sending %s' % filename
elif st == 'D':
p.todo_delete.append(filename)
print 'Deleting %s' % filename
if not p.todo_send and not p.todo_delete:
print 'nothing to do for package %s' % p.name
continue
print 'Transmitting file data ',
for filename in p.todo_send:
sys.stdout.write('.')
p.put_source_file(filename)
for filename in p.todo_delete:
p.delete_source_file(filename)
p.to_be_deleted.remove(filename)
if conf.config['do_commits'] == '1':
p.commit(msg='MESSAGE')
p.update_filesmeta()
p.write_deletelist()
print
def update(args):
"""Update a working copy
usage: osc up
osc up [pac_dir] # update a single package by its path
osc up * # from within a project dir, update all packages
osc up # from within a project dir, update all packages
AND check out all newly added packages
"""
args = parseargs(args)
for arg in args:
# when 'update' is run inside a project dir, it should...
if is_project_dir(arg):
prj = Project(arg)
# (a) update all packages
args += prj.pacs_have
# (b) fetch new packages
prj.checkout_missing_pacs()
args.remove(arg)
pacs = findpacs(args)
for p in pacs:
# save filelist and (modified) status before replacing the meta file
saved_filenames = p.filenamelist
saved_modifiedfiles = [ f for f in p.filenamelist if p.status(f) == 'M' ]
oldp = p
p.update_filesmeta()
p = Package(p.dir)
# which files do no longer exist upstream?
disappeared = [ f for f in saved_filenames if f not in p.filenamelist ]
for filename in saved_filenames:
if filename in disappeared:
print statfrmt('D', filename)
p.delete_localfile(filename)
continue
for filename in p.filenamelist:
state = p.status(filename)
if state == 'M' and p.findfilebyname(filename).md5 == oldp.findfilebyname(filename).md5:
# no merge necessary... local file is changed, but upstream isn't
pass
elif state == 'M' and filename in saved_modifiedfiles:
status_after_merge = p.mergefile(filename)
print statfrmt(status_after_merge, filename)
elif state == 'M':
p.updatefile(filename)
print statfrmt('U', filename)
elif state == '!':
p.updatefile(filename)
print 'Restored \'%s\'' % filename
elif state == 'F':
p.updatefile(filename)
print statfrmt('A', filename)
elif state == ' ':
pass
p.update_pacmeta()
#print ljust(p.name, 45), 'At revision %s.' % p.rev
print 'At revision %s.' % p.rev
def delete(args):
"""rm (remove, del, delete): Mark files to be deleted upon next 'checkin'
usage: osc rm file1 file2 ...
"""
if not args:
print 'delete requires at least one argument'
sys.exit(1)
args = parseargs(args)
pacs = findpacs(args)
for p in pacs:
for filename in p.todo:
p.put_on_deletelist(filename)
p.write_deletelist()
try:
os.unlink(os.path.join(p.dir, filename))
os.unlink(os.path.join(p.storedir, filename))
except:
pass
print statfrmt('D', filename)
def resolved(args):
"""If an update can't be merged automatically, a file is in 'C' (conflict)
state, and conflicts are marked with special <<<<<<< and >>>>>>> lines.
After manually resolving the problem, use
usage: osc resolved <filename>
"""
if not args:
print 'this command requires at least one argument'
sys.exit(1)
args = parseargs(args)
pacs = findpacs(args)
for p in pacs:
for filename in p.todo:
print "Resolved conflicted state of '%s'" % filename
p.clear_from_conflictlist(filename)
def usermeta(args):
"""usermeta: show metadata about user <userid>
usage: osc usermeta <userid>
"""
if not args:
print 'this command requires at least one argument'
sys.exit(1)
r = get_user_meta(args[0])
if r:
print ''.join(r)
def platforms(args):
"""platforms: Shows platforms
usage 1. osc platforms
Shows available platforms/build targets
2. osc platforms <project>
Shows the configured platforms/build targets of a project
"""
if args:
project = args[0]
print '\n'.join(get_platforms_of_project(project))
else:
print '\n'.join(get_platforms())
def results_meta(args):
"""Shows the build results of the package in raw XML
usage: osc results_meta [platform]
"""
wd = os.curdir
package = store_read_package(wd)
project = store_read_project(wd)
if args:
platform = args[0]
print ''.join(show_results_meta(project, package, platform))
else:
for platform in get_platforms_of_project(project):
print ''.join(show_results_meta(project, package, platform))
def results(args):
"""Shows the build results of a package
usage: 1. osc results # package = current dir
2. osc results <packagedir>
"""
if args and len(args) > 1:
print 'getting results for more than one package is not supported'
print sys.exit(1)
if args:
wd = args[0]
else:
wd = os.curdir
package = store_read_package(wd)
project = store_read_project(wd)
for platform in get_platforms_of_project(project):
print '\n'.join(get_results(project, package, platform))
def prjresults(args):
"""Shows the aggregated build results of an entire project
usage: 1. osc prjresults # package = current dir
2. osc prjresults <packagedir>
"""
if args and len(args) > 1:
print 'getting results for more than one project is not supported'
print sys.exit(1)
if args:
wd = args[0]
else:
wd = os.curdir
try:
project = store_read_project(wd)
except:
sys.exit('\'%s\' is not an osc project directory' % wd)
print '\n'.join(get_prj_results(project))
def log(args):
"""log: Shows the log file from a package (you need to be inside a package directory)
usage: osc log <platform> <arch>
To find out <platform> and <arch>, you can use 'osc results'
"""
if not args or len(args) != 2:
print 'missing argument'
print
print log.func_doc
sys.exit(1)
wd = os.curdir
package = store_read_package(wd)
project = store_read_project(wd)
platform = args[0]
arch = args[1]
offset = 0
try:
while True:
log_chunk = get_log(project, package, platform, arch, offset)
if len(log_chunk) == 0:
break
offset += len(log_chunk)
print log_chunk.strip()
except KeyboardInterrupt:
pass
def buildinfo(args):
"""buildinfo: Shows the build "info" which is used in building a package
This command is mostly used internally by the 'build' command.
It needs to be called inside a package directory.
usage: osc buildinfo <platform> <arch> [specfile]
The [specfile] argument is optional. <specfile> is a local specfile (or .dsc
file) which is sent to the server, and the buildinfo will be based on it.
If the argument is not supplied, the buildinfo is derived from the specfile
which is currently in the package.
The returned data is XML and contains a list of the packages used in building,
their source, and the expanded BuildRequires.
"""
wd = os.curdir
package = store_read_package(wd)
project = store_read_project(wd)
if args is None or len(args) < 2:
print 'missing argument'
print
print buildinfo.func_doc
print 'Valid arguments for this package are:'
print
repos(None)
print
sys.exit(1)
platform = args[0]
arch = args[1]
# were we given a specfile (third argument)?
try:
spec = open(args[2]).read()
except IndexError:
spec = None
except IOError, e:
sys.exit(e)
print ''.join(get_buildinfo(project, package, platform, arch, specfile=spec))
def buildconfig(args):
"""buildconfig: Shows the build configuration which is used in building a package
This command is mostly used internally by the 'build' command.
It needs to be called inside a package directory.
usage: osc buildconfig <platform> <arch>
The returned data is the project-wide build configuration in a format which is
directly readable by the build script. It contains RPM macros and BuildRequires
expansions, for example.
"""
wd = os.curdir
package = store_read_package(wd)
project = store_read_project(wd)
if args is None or len(args) < 2:
print 'missing argument'
print
print buildconfig.func_doc
print 'Valid arguments for this package are:'
print
repos(None)
print
sys.exit(1)
platform = args[0]
arch = args[1]
print ''.join(get_buildconfig(project, package, platform, arch))
def repos(args):
"""repos: Shows the repositories which are defined for a package
usage: 1. osc repos # package = current dir
2. osc repos <packagedir>
"""
args = parseargs(args)
pacs = findpacs(args)
for p in pacs:
for platform in get_repos_of_project(p.prjname):
print platform
def build(args):
"""build: build a package on your local machine
You need to call the command inside a package directory.
usage: 1. osc build <platform> <arch> <specfile> [--clean|--noinit]
2. BUILD_DIST=... osc build <specfile> [--clean|--noinit]
where BUILD_DIST equals <platform>-<arch>
You may want to configure sudo with option NOPASSWD for /usr/bin/build
and set su-wrapper to 'sudo' in .oscrc.
Note:
Configuration can be overridden by envvars, e.g.
OSC_SU_WRAPPER overrides the setting of su-wrapper.
BUILD_DIST or OSC_BUILD_DIST overrides the build target.
BUILD_ROOT or OSC_BUILD_ROOT overrides the build-root.
"""
import osc.build
if not os.path.exists('/usr/lib/build/debsort'):
sys.exit('Error: you need build.rpm with version 2006.6.14 or newer.\nSee http://software.opensuse.org/download/openSUSE:/Tools/')
builddist = os.getenv('BUILD_DIST')
if builddist:
#sys.argv[4] = sys.argv[1]
hyphen = builddist.rfind('-')
sys.argv.insert(2, builddist[hyphen+1:])
sys.argv.insert(2, builddist[:hyphen])
print sys.argv
elif args is None or len(args) < 3:
print 'missing argument'
print
print build.func_doc
print 'Valid arguments are:'
print 'you have to choose a repo to build on'
print 'possible repositories are:'
print
(i, o) = os.popen4(['osc', 'repos'])
i.close()
for line in o.readlines():
a = line.split()[1] # arch
if a == osc.build.hostarch or \
a in osc.build.can_also_build.get(osc.build.hostarch, []):
print line.strip()
sys.exit(1)
osc.build.main(sys.argv[1:])
def buildhistory(args):
"""buildhistory (buildhist): Shows the build history of a package
usage: osc buildhistory <platform> <arch>
"""
wd = os.curdir
package = store_read_package(wd)
project = store_read_project(wd)
if args is None or len(args) < 2:
print 'missing argument'
print
print buildhistory.func_doc
print 'Valid arguments for this package are:'
print
repos(None)
print
sys.exit(1)
platform = args[0]
arch = args[1]
print '\n'.join(get_buildhistory(project, package, platform, arch))
def rebuildpac(args):
"""rebuildpac: Causes a package to be rebuilt
usage: osc rebuildpac <project> <package> [<repo> [<arch>]]
With the optional <repo> and <arch> arguments, the rebuild can be limited
to a certain repository or architecture.
Note that it is normally NOT needed to kick off rebuilds like this, because
they principally happen in a fully automatic way, triggered by source
check-ins. In particular, the order in which packages are built is handled
by the build service.
"""
if args is None or len(args) < 2:
print 'missing argument'
print
print rebuildpac.func_doc
sys.exit(1)
repo = arch = None
project = args[0]
package = args[1]
if len(args) > 2:
repo = args[2]
if len(args) > 3:
arch = args[3]
print package + ':', cmd_rebuild(project, package, repo, arch)
def help(args):
"""help: Describe the usage of this program or its subcommands.
usage: osc help [SUBCOMMAND...]
"""
if args:
cmd = args[0]
for i in cmd_dict.keys():
if cmd in cmd_dict[i]:
cmd = i
break
try:
print cmd.func_doc
except AttributeError, KeyError:
print 'unknown command \'%s\'' % cmd
sys.exit(1)
else:
print usage_general
lines = []
for i in cmd_dict.keys():
line = ' ' + (i.__name__)
if len(cmd_dict[i]) > 1:
line += ' (%s)' % ', '.join(cmd_dict[i][1:])
lines.append(line)
lines.sort()
lines.append('')
print '\n'.join(lines)
# all commands and aliases are defined here
# a function with the respective name is assumed to exist
cmd_dict = {
add: ['add'],
addremove: ['addremove'],
build: ['build'],
buildconfig: ['buildconfig'],
buildinfo: ['buildinfo'],
commit: ['commit', 'ci', 'checkin'],
checkout: ['checkout', 'co'],
updatepacmetafromspec: ['updatepacmetafromspec'],
deletepac: ['deletepac'],
deleteprj: ['deleteprj'],
diff: ['diff'],
editmeta: ['editmeta'],
editpac: ['editpac', 'createpac'],
copypac: ['copypac'],
editprj: ['editprj', 'createprj'],
help: ['help'],
buildhistory: ['buildhistory', 'buildhist'],
linkpac: ['linkpac'],
usermeta: ['usermeta'],
edituser: ['edituser'],
init: ['init'], # deprecated
log: ['log'],
ls: ['ls', 'list'],
meta: ['meta'],
platforms: ['platforms'],
delete: ['delete', 'del', 'rm', 'remove'],
repos: ['repos'],
repourls: ['repourls'],
resolved: ['resolved'],
results: ['results'],
prjresults: ['prjresults'],
results_meta: ['results_meta'],
rebuildpac: ['rebuildpac'],
status: ['status', 'stat', 'st'],
update: ['update', 'up'],
}
def main():
"""handling of commandline arguments, and dispatching to subcommands"""
conf.get_config()
# which subcommand?
if len(sys.argv) < 2:
print "Type 'osc help' for usage."
sys.exit(0)
cmd = sys.argv[1]
# more arguments?
if len(sys.argv) > 2:
args = sys.argv[2:]
else:
args = None
for i in cmd_dict.keys():
if cmd in cmd_dict[i]:
cmd = i
# run subcommand
if cmd not in cmd_dict:
print 'unknown command \'%s\'' % cmd
print "Type 'osc help' for usage."
sys.exit(1)
cmd(args)
if __name__ == '__main__':
import sys, os.path
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
main()