1
0
mirror of https://github.com/openSUSE/osc.git synced 2024-12-25 17:36:13 +01:00

add opensuse-commander, a python library / commandline tool

This commit is contained in:
Dr. Peter Poeml 2006-04-20 14:26:50 +00:00
commit 1e3a8f82d8
4 changed files with 892 additions and 0 deletions

72
osc.README Normal file
View File

@ -0,0 +1,72 @@
osc -- opensuse-commander with svn like handling
>>> BUG: at the moment, 'up' overwrites files with
local modifications.
>>> At the moment, 'status' is highly inefficient.
Please send patches or contact me to get write access:
http://svn.poeml.de/svn/osc
http://svn.poeml.de/viewcvs/osc/
The program needs the package pyxml installed.
For authentication, put your account data into your ~/.netrc file, like this
line:
machine api.opensuse.org login $login password $pass
Usage examples:
osc ls # list projects
osc ls Apache # list packages in a project
osc ls Apache subversion # list files of package of a project
# check out
osc co Apache # entire project
osc co Apache subversion # a package
osc co Apache subversion foo # single file
# update working copy
osc up
# check in
osc ci # current dir
osc ci <dir>
osc ci file1 file2 ...
# show status
osc st
osc st <directory>
# initialize a source directory to be a
# working copy of project <prj> package <pac>
osc init <prj> <pac>
# schedule file foo to be added / deleted
osc add foo
osc rm foo
# add all unknown files and remove all missing files
osc addremove
# show diff
osc diff [file]
# show logfile (must be run from workingdir)
osc log <platform> <arch>
# show platforms
osc platforms
# show platforms used by project Apache
osc platforms Apache
# show various xml meta
osc meta Apache
osc meta Apache subversion
osc id username

319
osc.py Executable file
View File

@ -0,0 +1,319 @@
#!/usr/bin/python
# Copyright (C) 2006 Peter Poeml. 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 osclib import *
def main():
cmd = sys.argv[1]
project = package = filename = None
# try:
# project = sys.argv[2]
# package = sys.argv[3]
# filename = sys.argv[4]
# except:
# pass
if cmd == 'init':
project = sys.argv[2]
package = sys.argv[3]
init_package_dir(project, package, os.path.curdir)
print 'Initializing %s (Project: %s, Package: %s)' % (os.curdir, project, package)
elif cmd == 'ls':
if len(sys.argv) == 2:
print '\n'.join(get_slash_source())
if len(sys.argv) == 3:
project = sys.argv[2]
print '\n'.join(meta_get_packagelist(project))
if len(sys.argv) == 4:
project = sys.argv[2]
package = sys.argv[3]
print '\n'.join(meta_get_filelist(project, package))
elif cmd == 'meta':
if len(sys.argv) == 4:
project = sys.argv[2]
package = sys.argv[3]
print ''.join(show_package_meta(project, package))
elif len(sys.argv) == 3:
project = sys.argv[2]
print ''.join(show_project_meta(project))
elif cmd == 'diff':
wd = os.curdir
package = store_read_package(wd)
project = store_read_project(wd)
if len(sys.argv) > 2:
filename = sys.argv[2]
if filename:
print get_source_file_diff(project, package, filename)
else:
d = []
for filename in meta_get_filelist(project, package):
d.append(get_source_file_diff(project, package, filename))
print ''.join(d)
elif cmd == 'co':
try:
project = sys.argv[2]
package = sys.argv[3]
filename = sys.argv[4]
except:
pass
if filename:
get_source_file(project, package, filename)
elif package:
checkout_package(project, package)
else:
# all packages
for package in meta_get_packagelist(project):
checkout_package(project, package)
elif cmd == 'st' or cmd == 'status':
if len(sys.argv) > 2:
args = sys.argv[2:]
else:
args = [ os.curdir ]
#print args
for arg in args:
if os.path.isfile(arg):
wd = os.path.dirname(arg)
filenames = [ os.path.basename(arg) ]
elif os.path.isdir(arg):
wd = arg
package = store_read_package(wd)
project = store_read_project(wd)
filenames = meta_get_filelist(project, package)
# add files which are not listed in _meta
for i in os.listdir(arg):
if i not in filenames and i not in exclude_stuff:
filenames.insert(0, i)
os.chdir(wd)
for filename in filenames:
s = get_file_status(project, package, filename)
#if not s.startswith(' '):
# print s
print s
elif cmd == 'add':
filenames = sys.argv[2:]
for filename in filenames:
localmeta_addfile(filename)
print 'A ', filename
elif cmd == 'addremove':
if len(sys.argv) > 2:
args = sys.argv[2:]
else:
args = [ os.curdir ]
for arg in args:
if os.path.isfile(arg):
wd = os.path.dirname(arg)
filenames = [ os.path.basename(arg) ]
elif os.path.isdir(arg):
wd = arg
package = store_read_package(wd)
project = store_read_project(wd)
filenames = meta_get_filelist(project, package)
# add files which are not listed in _meta
for i in os.listdir(arg):
if i not in filenames and i not in exclude_stuff:
filenames.insert(0, i)
for filename in filenames:
st = get_file_status(project, package, filename)
if st.startswith('?'):
localmeta_addfile(filename)
print 'A ', filename
elif st.startswith('!'):
print 'D ', filename
localmeta_removefile(filename)
elif cmd == 'ci' or cmd == 'checkin':
if len(sys.argv) > 2:
args = sys.argv[2:]
else:
args = [ os.curdir ]
#print args
for arg in args:
if os.path.isfile(arg):
wd = os.path.dirname(arg)
filenames = [ os.path.basename(arg) ]
elif os.path.isdir(arg):
wd = arg
package = store_read_package(wd)
project = store_read_project(wd)
filenames = meta_get_filelist(project, package)
# add files which are not listed in _meta
for i in os.listdir(arg):
if i not in filenames and i not in exclude_stuff:
filenames.insert(0, i)
os.chdir(wd)
files_to_send = []
files_to_delete = []
for filename in filenames:
st = get_file_status(project, package, filename)
if st.startswith('A') or st.startswith('M'):
files_to_send.append(filename)
print 'Sending %s' % filename
elif st.startswith('D'):
files_to_delete.append(filename)
print 'Deleting %s' % filename
if not files_to_send and not files_to_delete:
print 'nothing to do'
sys.exit(0)
print 'Transmitting file data ',
for filename in files_to_send:
put_source_file(project, package, filename)
copy_file(filename, os.path.join(store, filename))
for filename in files_to_delete:
del_source_file(project, package, filename)
print
print 'Transmitting meta data ',
put_source_file(project, package, os.path.join(store, '_meta'))
print
elif cmd == 'up' or cmd == 'update':
if len(sys.argv) > 2:
args = sys.argv[2:]
else:
args = [ os.curdir ]
#print args
for arg in args:
if os.path.isfile(arg):
wd = os.path.dirname(arg)
filenames = [ os.path.basename(arg) ]
elif os.path.isdir(arg):
wd = arg
package = store_read_package(wd)
project = store_read_project(wd)
filenames = meta_get_filelist(project, package)
## add files which are not listed in _meta
#for i in os.listdir(arg):
# if i not in filenames and i not in exclude_stuff:
# filenames.insert(0, i)
os.chdir(wd)
os.chdir(store)
for filename in filenames:
get_source_file(project, package, filename)
wcfilename = os.path.join(os.pardir, os.path.basename(filename))
if not os.path.exists(wcfilename):
print 'A %s' % filename
copy_file(filename, wcfilename)
elif dgst(wcfilename) != dgst(filename):
print 'U %s' % filename
copy_file(filename, wcfilename)
else:
pass
# get current meta file
f = open('_meta', 'w')
f.write(''.join(show_package_meta(project, package)))
f.close()
elif cmd == 'rm' or cmd == 'delete':
if len(sys.argv) > 2:
args = sys.argv[2:]
else:
print '%s requires at least one argument' % cmd
sys.exit(1)
for arg in args:
if os.path.isfile(arg):
wd = os.path.dirname(arg)
if not wd: wd = os.curdir
filenames = [ os.path.basename(arg) ]
os.chdir(wd)
for filename in filenames:
localmeta_removefile(filename)
print 'D %s' % filename
elif cmd == 'id':
print ''.join(get_user_id(sys.argv[2]))
elif cmd == 'platforms':
if project:
print '\n'.join(get_platforms_of_project(project))
else:
print '\n'.join(get_platforms())
elif cmd == 'results':
if len(sys.argv) > 4:
platform = sys.argv[4]
print ''.join(get_results(project, package, platform))
else:
for platform in get_platforms_of_project(project):
print ''.join(get_results(project, package, platform))
elif cmd == 'log':
wd = os.curdir
package = store_read_package(wd)
project = store_read_project(wd)
platform = sys.argv[2]
arch = sys.argv[3]
print ''.join(get_log(project, package, platform, arch))
else:
print "unknown command '%s'" % cmd
if __name__ == '__main__':
init_basicauth()
main()

399
osclib.py Executable file
View File

@ -0,0 +1,399 @@
#!/usr/bin/python
# Copyright (C) 2006 Peter Poeml. 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.
import os
import sys
import urllib2
from xml.utils import qp_xml
import netrc
from urlparse import urlunsplit
# the needed entry in .netrc looks like this:
# machine api.opensuse.org login your_login password your_pass
info = netrc.netrc()
username, account, password = info.authenticators("api.opensuse.org")
from xml.dom.ext.reader import Sax2
from xml.dom.ext import PrettyPrint
netloc = 'api.opensuse.org'
scheme = 'http'
BUFSIZE = 1024*1024
store = '.osc'
exclude_stuff = [store, '.svn', 'CVS']
def makeurl(l):
"""given a list of path compoments, construct a complete URL"""
return urlunsplit((scheme, netloc, '/'.join(l), '', ''))
def copy_file(src, dst):
s = open(src)
d = open(dst, 'w')
while 1:
buf = s.read(BUFSIZE)
if not buf: break
d.write(buf)
s.close()
d.close()
def init_basicauth():
passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
# this creates a password manager
passmgr.add_password(None, netloc, username, password)
# because we have put None at the start it will always
# use this username/password combination for urls
# for which `netloc` is a super-url
authhandler = urllib2.HTTPBasicAuthHandler(passmgr)
# create the AuthHandler
opener = urllib2.build_opener(authhandler)
urllib2.install_opener(opener)
# All calls to urllib2.urlopen will now use our handler
# Make sure not to include the protocol in with the URL, or
# HTTPPasswordMgrWithDefaultRealm will be very confused.
# You must (of course) use it when fetching the page though.
def init_package_dir(project, package, dir):
if not os.path.isdir(store):
os.mkdir(store)
os.chdir(store)
f = open('_project', 'w')
f.write(project + '\n')
f.close
f = open('_package', 'w')
f.write(package + '\n')
f.close
f = open('_meta', 'w')
f.write(''.join(show_package_meta(project, package)))
f.close()
return
def meta_get_packagelist(prj):
reader = Sax2.Reader()
u = makeurl(['source', prj, '_meta'])
f = urllib2.urlopen(u)
doc = reader.fromStream(f)
r = []
for i in doc.getElementsByTagName('package'):
r.append(i.getAttribute('name'))
return r
def meta_get_filelist(prj, package):
reader = Sax2.Reader()
u = makeurl(['source', prj, package, '_meta'])
f = urllib2.urlopen(u)
doc = reader.fromStream(f)
r = []
for i in doc.getElementsByTagName('file'):
r.append(i.getAttribute('filename'))
return r
def localmeta_addfile(filename):
if filename in localmeta_get_filelist():
return
reader = Sax2.Reader()
f = open(os.path.join(store, '_meta')).read()
doc = reader.fromString(f)
new = doc.createElement('file')
new.setAttribute('filetype', 'source')
new.setAttribute('filename', filename)
doc.documentElement.appendChild(new)
o = open(os.path.join(store, '_meta'), 'w')
PrettyPrint(doc, stream=o)
o.close()
def localmeta_removefile(filename):
reader = Sax2.Reader()
f = open(os.path.join(store, '_meta')).read()
doc = reader.fromString(f)
for i in doc.getElementsByTagName('file'):
if i.getAttribute('filename') == filename:
i.parentNode.removeChild(i)
o = open(os.path.join(store, '_meta'), 'w')
PrettyPrint(doc, stream=o)
o.close()
def localmeta_get_filelist():
reader = Sax2.Reader()
f = open(os.path.join(store, '_meta')).read()
doc = reader.fromString(f)
r = []
for i in doc.getElementsByTagName('file'):
r.append(i.getAttribute('filename'))
return r
def get_slash_source():
u = makeurl(['source'])
f = urllib2.urlopen(u)
parser = qp_xml.Parser()
root = parser.parse(f)
r = []
for entry in root.children:
r.append(entry.attrs[('', 'name')])
return r
def show_project_meta(prj):
f = urllib2.urlopen(makeurl(['source', prj, '_meta']))
return f.readlines()
def show_package_meta(prj, pac):
f = urllib2.urlopen(makeurl(['source', prj, pac, '_meta']))
return f.readlines()
def get_user_id(user):
u = makeurl(['person', user])
f = urllib2.urlopen(u)
return f.readlines()
def get_source_file(prj, package, filename):
u = makeurl(['source', prj, package, filename])
#print 'checking out', u
f = urllib2.urlopen(u)
o = open(filename, 'w')
while 1:
buf = f.read(BUFSIZE)
if not buf: break
o.write(buf)
o.close()
def dgst(file):
if not os.path.exists(file):
return None
import sha
s = sha.new()
f = open(file, 'r')
while 1:
buf = f.read(BUFSIZE)
if not buf: break
s.update(buf)
return s.digest()
def get_file_status(prj, package, filename):
"""
status can be:
file storefile file present STATUS
exists exists in _meta
x x - 'D'
x x x 'M', if digest differs, else ' '
x - - '?'
x - x 'A'
- x x '!'
- x - NOT DEFINED
- - x NOT DEFINED
- - - NEVER REACHED
"""
known_by_meta = False
exists = False
exists_in_store = False
if filename in localmeta_get_filelist():
known_by_meta = True
if os.path.exists(filename):
exists = True
if os.path.exists(os.path.join(store, filename)):
exists_in_store = True
if exists and exists_in_store and not known_by_meta:
state = 'D'
elif exists and exists_in_store and known_by_meta:
if dgst(filename) != dgst(os.path.join(store, filename)):
state = 'M'
else:
state = ' '
elif exists and not exists_in_store and not known_by_meta:
state = '?'
elif exists and not exists_in_store and known_by_meta:
state = 'A'
elif not exists and exists_in_store and known_by_meta:
state = '!'
elif not exists and not exists_in_store and known_by_meta:
print 'not exists and not exists_in_store and known_by_meta'
print 'this state is undefined!'
sys.exit(1)
elif not exists and exists_in_store and not known_by_meta:
print 'not exists and exists_in_store and not nown_by_meta'
print 'this state is undefined!'
sys.exit(1)
elif not exists and not exists_in_store and not known_by_meta:
print 'not exists and not exists_in_store and not nown_by_meta'
print 'this code path should never be reached!'
sys.exit(1)
return '%s %s' % (state, filename)
def get_source_file_diff(prj, package, filename):
url = makeurl(['source', prj, package, filename])
f = urllib2.urlopen(url)
localfile = open(filename, 'r')
import difflib
#print url
d = difflib.unified_diff(f.readlines(), localfile.readlines(), fromfile = url, tofile = filename)
localfile.close()
return ''.join(d)
#def put_source_file_and_meta(prj, package, filename):
# if filename == '_meta':
# put_source_file(prj, package, filename)
# return
#
# get_source_file(prj, package, '_meta')
# localmeta_addfile(os.path.basename(filename))
# put_source_file(prj, package, filename)
# put_source_file(prj, package, '_meta')
def put_source_file(prj, package, filename):
import othermethods
sys.stdout.write('.')
u = makeurl(['source', prj, package, os.path.basename(filename)])
othermethods.putfile(u, filename, username, password)
#f = urllib2.urlopen(u)
#o = open(filename, 'w')
#o.write(f.read())
#o.close()
def del_source_file(prj, package, filename):
import othermethods
u = makeurl(['source', prj, package, filename])
# not implemented in the server yet... thus, we are cheating by only removing
# the file from _meta
#othermethods.delfile(u, filename, username, password)
wcfilename = os.path.join(store, filename)
if os.path.exists(filename): os.unlink(filename)
if os.path.exists(wcfilename): os.unlink(wcfilename)
def make_dir(project, package):
#print "creating directory '%s'" % project
print 'A %s' % project
if not os.path.exists(project):
os.mkdir(project)
os.mkdir(os.path.join(project, store))
#print "creating directory '%s/%s'" % (project, package)
print 'A %s/%s' % (project, package)
if not os.path.exists(os.path.join(project, package)):
os.mkdir(os.path.join(project, package))
os.mkdir(os.path.join(project, package, store))
return(os.path.join(project, package))
def checkout_package(project, package):
olddir = os.getcwd()
os.chdir(make_dir(project, package))
for filename in meta_get_filelist(project, package):
get_source_file(project, package, filename)
copy_file(filename, os.path.join(store, filename))
print 'A ', os.path.join(project, package, filename)
init_package_dir(project, package, store)
os.chdir(olddir)
def get_platforms():
f = urllib2.urlopen(makeurl(['platform']))
parser = qp_xml.Parser()
root = parser.parse(f)
r = []
for entry in root.children:
r.append(entry.attrs[('', 'name')])
return r
def get_platforms_of_project(prj):
f = show_project_meta(prj)
parser = qp_xml.Parser()
root = parser.parse('\n'.join(f))
r = []
for entry in root.children:
if entry.name == 'repository':
r.append(entry.attrs[('', 'name')])
return r
def get_results(prj, package, platform):
u = makeurl(['result', prj, platform, package, 'result'])
f = urllib2.urlopen(u)
return f.readlines()
def get_log(prj, package, platform, arch):
u = makeurl(['result', prj, platform, package, arch, 'log'])
f = urllib2.urlopen(u)
return f.readlines()
def store_read_project(dir):
p = open(os.path.join(dir, store, '_project')).readlines()[0].strip()
return p
def store_read_package(dir):
p = open(os.path.join(dir, store, '_package')).readlines()[0].strip()
return p
#p = open(os.path.join(dir, store, '_meta')).readlines()[0]
#p = p.split('"')[1]
#return p

102
othermethods.py Executable file
View File

@ -0,0 +1,102 @@
#!/usr/bin/python
# Copyright (C) 2006 Peter Poeml. 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.
"""
This file is provided because urllib2 doesn't have support for the DELETE and
PUT methods.
"""
import httplib
import base64
import os
import urlparse
BLOCKSIZE=1024
def delfile(url, file, username, password):
auth_string = base64.encodestring('%s:%s' % (username, password))
u = urlparse.urlparse(url)
host = u[1]
path = u[2]
conn = httplib.HTTP(host)
conn.putrequest('DELETE', '%s' % path)
conn.putheader('Host', host)
conn.putheader('Authorization', 'Basic %s' % auth_string)
conn.endheaders()
reply, msg, headers = conn.getreply()
if reply == 200:
#print 'done'
pass
else:
print 'error deleting %s' % file
print 'upload-DELETE reply=', reply, ' msg=', msg, 'headers=', headers
def putfile(url, file, username, password):
size = os.stat(file)[6]
auth_string = base64.encodestring('%s:%s' % (username, password))
u = urlparse.urlparse(url)
host = u[1]
path = u[2]
conn = httplib.HTTP(host)
conn.putrequest('PUT', '%s' % path)
conn.putheader('Host', host)
conn.putheader('Content-Type', 'text/plain')
conn.putheader('Content-Length', str(size))
conn.putheader('Authorization', 'Basic %s' % auth_string)
conn.endheaders()
fp = open(file, 'rb')
n = 0
while 1:
buf = fp.read(BLOCKSIZE)
n+=1
if n % 10 == 0:
#print 'upload-sending blocknum=', n
#print '.',
pass
if not buf: break
conn.send(buf)
fp.close()
reply, msg, headers = conn.getreply()
if reply == 200:
pass
#print 'done'
else:
print 'error uploading %s' % file
print 'upload-PUT reply=', reply, ' msg=', msg, 'headers=', headers
def main():
import sys
username = 'yourusername'
password = 'yourpassword'
file = sys.argv[1]
url = 'http://api.opensuse.org/source/exim/exim/%s' % os.path.basename(file)
putfile(url, file, username, password)
delfile(url, file, username, password)
if __name__ == '__main__':
main()