blob: 3d5e151529ab53dbbf8ee0c973370a94addbb7a2 [file] [log] [blame]
import subprocess, re, os, tempfile
class GitError(Exception):
pass
class SHAError(GitError):
pass
class ExecutionError(GitError):
def __init__(self, errcode):
self.error_code = errcode
def _check(process):
if process.returncode != 0:
raise ExecutionError(process.returncode)
_sha_re = re.compile('^[0-9a-fA-F]*$')
def rev_parse(rev='HEAD', tree=None):
process = subprocess.Popen(['git', 'rev-parse', rev], stdout=subprocess.PIPE,
close_fds=True, universal_newlines=True, cwd=tree)
stdout = process.communicate()[0]
process.wait()
_check(process)
sha = stdout.strip()
if not _sha_re.match(sha):
raise SHAError()
return sha
def clean(tree=None):
cmd = ['git', 'clean', '-f', '-x', '-d', '-q']
process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
close_fds=True, universal_newlines=True, cwd=tree)
stdout = process.communicate()[0]
process.wait()
_check(process)
def fetch(tree=None):
cmd = ['git', 'fetch']
process = subprocess.Popen(cmd, cwd=tree)
process.wait()
_check(process)
def status(tree=None):
'''
Return a list (that may be empty) of current changes. Each entry is a
tuple, just like returned from git status, with the difference that
the filename(s) are no longer space-separated but rather the tuple is
of the form
('XY', 'filename')
or
('XY', 'filename_to', 'filename_from') [if X is 'R' for rename]
'''
cmd = ['git', 'status', '--porcelain', '-z']
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, close_fds=True,
universal_newlines=True, cwd=tree)
stdout = process.communicate()[0]
process.wait()
_check(process)
l = stdout.split('\0')
result = []
cur = []
for i in l:
if not i:
continue
if not cur:
cur.append(i[:2])
assert i[2] == ' '
cur.append(i[3:])
if i[0] == 'R':
continue
else:
cur.append(i)
result.append(tuple(cur))
cur = []
return result
def describe(rev='HEAD', tree=None, extra_args=[]):
cmd = ['git', 'describe', '--always']
cmd.extend(extra_args)
if rev is not None:
cmd.append(rev)
process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
close_fds=True, universal_newlines=True, cwd=tree)
stdout = process.communicate()[0]
process.wait()
_check(process)
return stdout.strip()
def verify(git_tree):
tag = describe(rev=None, tree=git_tree, extra_args=['--dirty'])
cmd = ['git', 'tag', '-v', tag]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
close_fds=True, universal_newlines=True, cwd=git_tree)
stdout = process.communicate()[0]
process.wait()
return dict(r=process.returncode, output=stdout)
def paranoia(tree):
clean(tree)
poo = status(tree)
if (poo):
return dict(r=-1, output=poo)
return verify(tree)
def init(tree=None):
process = subprocess.Popen(['git', 'init'], stdout=subprocess.PIPE,
close_fds=True, universal_newlines=True, cwd=tree)
stdout = process.communicate()[0]
process.wait()
_check(process)
def add(path, tree=None):
process = subprocess.Popen(['git', 'add', '--ignore-removal', path],
stdout=subprocess.PIPE,
close_fds=True, universal_newlines=True, cwd=tree)
stdout = process.communicate()[0]
process.wait()
_check(process)
def commit_all(message, tree=None):
add('.', tree=tree)
process = subprocess.Popen(['git', 'commit', '--allow-empty', '-a', '-m', message],
stdout=subprocess.PIPE,
close_fds=True, universal_newlines=True, cwd=tree)
stdout = process.communicate()[0]
process.wait()
_check(process)
def ls_tree(rev, files, tree=None):
process = subprocess.Popen(['git', 'ls-tree', '-z', '-r', rev, '--', ] + list(files),
stdout=subprocess.PIPE,
close_fds=True, universal_newlines=True, cwd=tree)
stdout = process.communicate()[0]
files = stdout.split('\0')
ret = []
for f in files:
if not f:
continue
meta, f = f.split('\t', 1)
meta = meta.split()
meta.append(f)
ret.append(meta)
process.wait()
_check(process)
return ret
def get_blob(blob, outf, tree=None):
try:
import git, gitdb
r = git.Repo(path=tree)
b = r.rev_parse(blob + '^{blob}')
b.stream_data(outf)
except ImportError:
process = subprocess.Popen(['git', 'show', blob],
stdout=outf, close_fds=True, cwd=tree)
process.wait()
_check(process)
def clone(gittree, outputdir, options=[], env=None):
process = subprocess.Popen(['git', 'clone'] + options + [gittree, outputdir],
env=env)
process.wait()
_check(process)
def set_origin(giturl, gitdir):
process = subprocess.Popen(['git', 'remote', 'rm', 'origin'],
close_fds=True, universal_newlines=True, cwd=gitdir)
process.wait()
process = subprocess.Popen(['git', 'remote', 'add', 'origin', giturl],
close_fds=True, universal_newlines=True, cwd=gitdir)
process.wait()
_check(process)
def remote_update(gitdir, env=None):
process = subprocess.Popen(['git', 'remote', 'update'],
close_fds=True, universal_newlines=True, cwd=gitdir,
env=env)
process.wait()
_check(process)
def shortlog(from_commit, to_commit, tree=None, files=None):
if files:
fargs = ['--'] + files
else:
fargs = []
process = subprocess.Popen(['git', 'shortlog', from_commit + '..' + to_commit] + fargs,
stdout=subprocess.PIPE,
close_fds=True, universal_newlines=True,
cwd=tree)
stdout = process.communicate()[0]
process.wait()
_check(process)
return stdout
def commit_env_vars(commitid, tree=None):
process = subprocess.Popen(['git', 'show', '--name-only',
'--format=format:GIT_AUTHOR_NAME=%an%nGIT_AUTHOR_EMAIL=%ae%nGIT_AUTHOR_DATE=%aD%x00',
commitid],
stdout=subprocess.PIPE,
close_fds=True, universal_newlines=True,
cwd=tree)
stdout = process.communicate()[0]
process.wait()
_check(process)
data = stdout.split('\x00')[0]
vals = data.split('\n')
d = {}
for k, v in map(lambda x: x.split('=', 1), vals):
d[k] = v
return d
def commit_message(commitid, tree=None):
process = subprocess.Popen(['git', 'show', '--name-only',
'--format=format:%s%n%n%b%x00', commitid],
stdout=subprocess.PIPE,
close_fds=True, universal_newlines=True,
cwd=tree)
stdout = process.communicate()[0]
process.wait()
_check(process)
return stdout.split('\x00')[0]
def remove_config(cfg, tree=None):
process = subprocess.Popen(['git', 'config', '--unset-all', cfg],
close_fds=True, universal_newlines=True, cwd=tree)
process.wait()
_check(process)
def ls_remote(branch, tree=None, remote='origin', env=None):
process = subprocess.Popen(['git', 'ls-remote', '--exit-code', remote, 'refs/heads/' + branch],
stdout=subprocess.PIPE, env=env,
close_fds=True, universal_newlines=True, cwd=tree)
stdout = process.communicate()[0]
process.wait()
_check(process)
sha = stdout.split()[0]
if not _sha_re.match(sha):
raise SHAError()
return sha
def add(fn, tree=None):
process = subprocess.Popen(['git', 'add', '--ignore-removal', fn], cwd=tree,
close_fds=True, universal_newlines=True)
process.wait()
_check(process)
def commit(msg, tree=None, env = {}, opts=[]):
stdin = tempfile.NamedTemporaryFile(mode='wr')
stdin.write(msg)
stdin.seek(0)
process = subprocess.Popen(['git', 'commit', '--file=-'] + opts,
stdin=stdin.file, universal_newlines=True, env=env,
cwd=tree)
process.wait()
_check(process)
def push(opts=[], tree=None):
process = subprocess.Popen(['git', 'push'] + opts,
close_fds=True, universal_newlines=True, cwd=tree)
process.wait()
_check(process)
def log_commits(from_commit, to_commit, tree=None):
process = subprocess.Popen(['git', 'log', '--first-parent', '--format=format:%H',
from_commit + '..' + to_commit],
stdout=subprocess.PIPE,
close_fds=True, universal_newlines=True,
cwd=tree)
stdout = process.communicate()[0]
process.wait()
_check(process)
vals = stdout.split()
vals.reverse()
return vals
def commit_env_vars(commitid, tree=None):
process = subprocess.Popen(['git', 'show', '--name-only',
'--format=format:GIT_AUTHOR_NAME=%an%nGIT_AUTHOR_EMAIL=%ae%nGIT_AUTHOR_DATE=%aD%x00',
commitid],
stdout=subprocess.PIPE,
close_fds=True, universal_newlines=True,
cwd=tree)
stdout = process.communicate()[0]
process.wait()
_check(process)
data = stdout.split('\x00')[0]
vals = data.split('\n')
d = {}
for k, v in map(lambda x: x.split('=', 1), vals):
d[k] = v
return d
def rm(opts=[], tree=None):
process = subprocess.Popen(['git', 'rm'] + opts,
close_fds=True, universal_newlines=True, cwd=tree)
process.wait()
_check(process)
def reset(opts=[], tree=None):
process = subprocess.Popen(['git', 'reset'] + opts,
close_fds=True, universal_newlines=True, cwd=tree)
process.wait()
_check(process)
def diff(tree=None, extra_args=None):
cmd = ['git', 'diff', '--color=always'] + extra_args
process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
close_fds=True, universal_newlines=True, cwd=tree)
stdout = process.communicate()[0]
process.wait()
_check(process)
return stdout