summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--morphlib/branchmanager_tests.py38
-rw-r--r--morphlib/cachedrepo.py22
-rwxr-xr-xmorphlib/exts/rawdisk.check21
-rw-r--r--morphlib/git.py71
-rw-r--r--morphlib/gitdir.py99
-rw-r--r--morphlib/gitdir_tests.py42
-rw-r--r--morphlib/gitindex.py5
-rw-r--r--morphlib/gitindex_tests.py9
-rw-r--r--morphlib/localrepocache.py4
-rw-r--r--morphlib/morphologyfinder_tests.py7
-rw-r--r--morphlib/sysbranchdir.py6
-rw-r--r--morphlib/sysbranchdir_tests.py13
12 files changed, 197 insertions, 140 deletions
diff --git a/morphlib/branchmanager_tests.py b/morphlib/branchmanager_tests.py
index a7988c9..cf3be73 100644
--- a/morphlib/branchmanager_tests.py
+++ b/morphlib/branchmanager_tests.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013 Codethink Limited
+# Copyright (C) 2013-2014 Codethink Limited
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -35,13 +35,13 @@ class LocalRefManagerTests(unittest.TestCase):
gd = morphlib.gitdir.init(dirname)
with open(os.path.join(dirname, 'foo'), 'w') as f:
f.write('dummy text\n')
- gd._runcmd(['git', 'add', '.'])
- gd._runcmd(['git', 'commit', '-m', 'Initial commit'])
- gd._runcmd(['git', 'checkout', '-b', 'dev-branch'])
+ morphlib.git.gitcmd(gd._runcmd, 'add', '.')
+ morphlib.git.gitcmd(gd._runcmd, 'commit', '-m', 'Initial commit')
+ morphlib.git.gitcmd(gd._runcmd, 'checkout', '-b', 'dev-branch')
with open(os.path.join(dirname, 'foo'), 'w') as f:
f.write('updated text\n')
- gd._runcmd(['git', 'add', '.'])
- gd._runcmd(['git', 'commit', '-m', 'Second commit'])
+ morphlib.git.gitcmd(gd._runcmd, 'add', '.')
+ morphlib.git.gitcmd(gd._runcmd, 'commit', '-m', 'Second commit')
self.repos.append(gd)
def tearDown(self):
@@ -245,18 +245,19 @@ class RemoteRefManagerTests(unittest.TestCase):
self.sgd = morphlib.gitdir.init(self.source)
with open(os.path.join(self.source, 'foo'), 'w') as f:
f.write('dummy text\n')
- self.sgd._runcmd(['git', 'add', '.'])
- self.sgd._runcmd(['git', 'commit', '-m', 'Initial commit'])
- self.sgd._runcmd(['git', 'checkout', '-b', 'dev-branch'])
+ morphlib.git.gitcmd(self.sgd._runcmd, 'add', '.')
+ morphlib.git.gitcmd(self.sgd._runcmd, 'commit', '-m', 'Initial commit')
+ morphlib.git.gitcmd(self.sgd._runcmd, 'checkout', '-b', 'dev-branch')
with open(os.path.join(self.source, 'foo'), 'w') as f:
f.write('updated text\n')
- self.sgd._runcmd(['git', 'add', '.'])
- self.sgd._runcmd(['git', 'commit', '-m', 'Second commit'])
- self.sgd._runcmd(['git', 'checkout', '--orphan', 'no-ff'])
+ morphlib.git.gitcmd(self.sgd._runcmd, 'add', '.')
+ morphlib.git.gitcmd(self.sgd._runcmd, 'commit', '-m', 'Second commit')
+ morphlib.git.gitcmd(self.sgd._runcmd, 'checkout', '--orphan', 'no-ff')
with open(os.path.join(self.source, 'foo'), 'w') as f:
f.write('parallel dimension text\n')
- self.sgd._runcmd(['git', 'add', '.'])
- self.sgd._runcmd(['git', 'commit', '-m', 'Non-fast-forward commit'])
+ morphlib.git.gitcmd(self.sgd._runcmd, 'add', '.')
+ morphlib.git.gitcmd(self.sgd._runcmd, 'commit', '-m',
+ 'Non-fast-forward commit')
self.remotes = []
for i in xrange(self.TARGET_COUNT):
@@ -264,11 +265,12 @@ class RemoteRefManagerTests(unittest.TestCase):
dirname = os.path.join(self.tempdir, name)
# Allow deleting HEAD
- cliapp.runcmd(['git', 'init', '--bare', dirname])
+ morphlib.git.gitcmd(cliapp.runcmd, 'init', '--bare', dirname)
gd = morphlib.gitdir.GitDirectory(dirname)
gd.set_config('receive.denyDeleteCurrent', 'warn')
- self.sgd._runcmd(['git', 'remote', 'add', name, dirname])
+ morphlib.git.gitcmd(self.sgd._runcmd, 'remote', 'add',
+ name, dirname)
self.remotes.append((name, dirname, gd))
def tearDown(self):
@@ -276,8 +278,8 @@ class RemoteRefManagerTests(unittest.TestCase):
@staticmethod
def list_refs(gd):
- out = gd._runcmd(['git', 'for-each-ref',
- '--format=%(refname)%00%(objectname)%00'])
+ out = morphlib.git.gitcmd(gd._runcmd, 'for-each-ref',
+ '--format=%(refname)%00%(objectname)%00')
return dict(line.split('\0') for line in
out.strip('\0\n').split('\0\n') if line)
diff --git a/morphlib/cachedrepo.py b/morphlib/cachedrepo.py
index 88ceed4..2fc7cfa 100644
--- a/morphlib/cachedrepo.py
+++ b/morphlib/cachedrepo.py
@@ -260,20 +260,21 @@ class CachedRepo(object):
return self.app.runcmd(*args, **kwargs)
def _rev_parse(self, ref): # pragma: no cover
- return self._runcmd(
- ['git', 'rev-parse', '--verify', '%s^{commit}' % ref])[0:40]
+ return morphlib.git.gitcmd(self._runcmd, 'rev-parse', '--verify',
+ '%s^{commit}' % ref)[0:40]
def _show_tree_hash(self, absref): # pragma: no cover
- return self._runcmd(
- ['git', 'rev-parse', '--verify', '%s^{tree}' % absref]).strip()
+ return morphlib.git.gitcmd(self._runcmd, 'rev-parse', '--verify',
+ '%s^{tree}' % absref).strip()
def _ls_tree(self, ref): # pragma: no cover
- result = self._runcmd(['git', 'ls-tree', '--name-only', ref])
+ result = morphlib.git.gitcmd(self._runcmd, 'ls-tree',
+ '--name-only', ref)
return result.split('\n')
def _cat_file(self, ref, filename): # pragma: no cover
- return self._runcmd(['git', 'cat-file', 'blob',
- '%s:%s' % (ref, filename)])
+ return morphlib.git.gitcmd(self._runcmd, 'cat-file', 'blob',
+ '%s:%s' % (ref, filename))
def _clone_into(self, target_dir, ref): #pragma: no cover
'''Actually perform the clone'''
@@ -297,10 +298,11 @@ class CachedRepo(object):
def _update(self): # pragma: no cover
try:
- self._runcmd(['git', 'remote', 'update', 'origin', '--prune'])
+ morphlib.git.gitcmd(self._runcmd, 'remote', 'update',
+ 'origin', '--prune')
except cliapp.AppException, ae:
- self._runcmd(['git', 'remote', 'prune', 'origin'])
- self._runcmd(['git', 'remote', 'update', 'origin'])
+ morphlib.git.gitcmd(self._runcmd, 'remote', 'prune', 'origin')
+ morphlib.git.gitcmd(self._runcmd, 'remote', 'update', 'origin')
def __str__(self): # pragma: no cover
return self.url
diff --git a/morphlib/exts/rawdisk.check b/morphlib/exts/rawdisk.check
index 6a656ee..5e75abe 100755
--- a/morphlib/exts/rawdisk.check
+++ b/morphlib/exts/rawdisk.check
@@ -20,6 +20,8 @@ import cliapp
import morphlib.writeexts
+import os
+
class RawdiskCheckExtension(morphlib.writeexts.WriteExtension):
def process_args(self, args):
@@ -28,4 +30,23 @@ class RawdiskCheckExtension(morphlib.writeexts.WriteExtension):
self.require_btrfs_in_deployment_host_kernel()
+ location = args[0]
+ upgrade = self.get_environment_boolean('UPGRADE')
+ if upgrade:
+ if not os.path.isfile(location):
+ raise cliapp.AppException(
+ 'Cannot upgrade %s: it is not an existing disk image' %
+ location)
+
+ version_label = os.environ.get('VERSION_LABEL')
+ if version_label is None:
+ raise cliapp.AppException(
+ 'VERSION_LABEL was not given. It is required when '
+ 'upgrading an existing system.')
+ else:
+ if os.path.exists(location):
+ raise cliapp.AppException(
+ 'Target %s already exists. Pass --upgrade if you want to '
+ 'update an existing image.' % location)
+
RawdiskCheckExtension().run()
diff --git a/morphlib/git.py b/morphlib/git.py
index ccd0632..d897de3 100644
--- a/morphlib/git.py
+++ b/morphlib/git.py
@@ -81,9 +81,9 @@ class Submodules(object):
def _read_gitmodules_file(self):
try:
# try to read the .gitmodules file from the repo/ref
- content = self.app.runcmd(
- ['git', 'cat-file', 'blob', '%s:.gitmodules' % self.ref],
- cwd=self.repo, ignore_fail=True)
+ content = gitcmd(self.app.runcmd, 'cat-file', 'blob',
+ '%s:.gitmodules' % self.ref, cwd=self.repo,
+ ignore_fail=True)
# drop indentation in sections, as RawConfigParser cannot handle it
return '\n'.join([line.strip() for line in content.splitlines()])
@@ -105,8 +105,8 @@ class Submodules(object):
try:
# list objects in the parent repo tree to find the commit
# object that corresponds to the submodule
- commit = self.app.runcmd(['git', 'ls-tree', self.ref,
- submodule.name], cwd=self.repo)
+ commit = gitcmd(self.app.runcmd, 'ls-tree', self.ref,
+ submodule.name, cwd=self.repo)
# read the commit hash from the output
fields = commit.split()
@@ -149,15 +149,13 @@ def update_submodules(app, repo_dir): # pragma: no cover
if os.path.exists(os.path.join(repo_dir, '.gitmodules')):
resolver = morphlib.repoaliasresolver.RepoAliasResolver(
app.settings['repo-alias'])
- app.runcmd(['git', 'submodule', 'init'], cwd=repo_dir)
+ gitcmd(app.runcmd, 'submodule', 'init', cwd=repo_dir)
submodules = Submodules(app, repo_dir, 'HEAD')
submodules.load()
for submodule in submodules:
- app.runcmd(['git', 'config',
- 'submodule.%s.url' % submodule.name,
- resolver.pull_url(submodule.url)],
- cwd=repo_dir)
- app.runcmd(['git', 'submodule', 'update'], cwd=repo_dir)
+ gitcmd(app.runcmd, 'config', 'submodule.%s.url' % submodule.name,
+ resolver.pull_url(submodule.url), cwd=repo_dir)
+ gitcmd(app.runcmd, 'submodule', 'update', cwd=repo_dir)
class ConfigNotSetException(cliapp.AppException):
@@ -217,7 +215,7 @@ def check_config_set(runcmd, keys, cwd='.'):
found = {}
for key in keys:
try:
- value = runcmd(['git', 'config', key], cwd=cwd,
+ value = gitcmd(runcmd, 'config', key, cwd=cwd,
print_command=False).strip()
found[key] = value
except cliapp.AppException:
@@ -229,7 +227,7 @@ def check_config_set(runcmd, keys, cwd='.'):
def set_remote(runcmd, gitdir, name, url):
'''Set remote with name 'name' use a given url at gitdir'''
- return runcmd(['git', 'remote', 'set-url', name, url], cwd=gitdir)
+ return gitcmd(runcmd, 'remote', 'set-url', name, url, cwd=gitdir)
def copy_repository(runcmd, repo, destdir, is_mirror=True):
@@ -246,16 +244,16 @@ def copy_repository(runcmd, repo, destdir, is_mirror=True):
runcmd(['cp', '-a', repo, os.path.join(destdir, '.git')])
# core.bare should be false so that git believes work trees are possible
- runcmd(['git', 'config', 'core.bare', 'false'], cwd=destdir)
+ gitcmd(runcmd, 'config', 'core.bare', 'false', cwd=destdir)
# we do not want the origin remote to behave as a mirror for pulls
- runcmd(['git', 'config', '--unset', 'remote.origin.mirror'], cwd=destdir)
+ gitcmd(runcmd, 'config', '--unset', 'remote.origin.mirror', cwd=destdir)
# we want a traditional refs/heads -> refs/remotes/origin ref mapping
- runcmd(['git', 'config', 'remote.origin.fetch',
- '+refs/heads/*:refs/remotes/origin/*'], cwd=destdir)
+ gitcmd(runcmd, 'config', 'remote.origin.fetch',
+ '+refs/heads/*:refs/remotes/origin/*', cwd=destdir)
# set the origin url to the cached repo so that we can quickly clean up
- runcmd(['git', 'config', 'remote.origin.url', repo], cwd=destdir)
+ gitcmd(runcmd, 'config', 'remote.origin.url', repo, cwd=destdir)
# by packing the refs, we can then edit then en-masse easily
- runcmd(['git', 'pack-refs', '--all', '--prune'], cwd=destdir)
+ gitcmd(runcmd, 'pack-refs', '--all', '--prune', cwd=destdir)
# turn refs/heads/* into refs/remotes/origin/* in the packed refs
# so that the new copy behaves more like a traditional clone.
logging.debug("Adjusting packed refs for %s" % destdir)
@@ -273,12 +271,12 @@ def copy_repository(runcmd, repo, destdir, is_mirror=True):
refline = "%s %s" % (sha, ref)
ref_fh.write("%s\n" % (refline))
# Finally run a remote update to clear up the refs ready for use.
- runcmd(['git', 'remote', 'update', 'origin', '--prune'], cwd=destdir)
+ gitcmd(runcmd, 'remote', 'update', 'origin', '--prune', cwd=destdir)
def checkout_ref(runcmd, gitdir, ref):
'''Checks out a specific ref/SHA1 in a git working tree.'''
- runcmd(['git', 'checkout', ref], cwd=gitdir)
+ gitcmd(runcmd, 'checkout', ref, cwd=gitdir)
gd = morphlib.gitdir.GitDirectory(gitdir)
if gd.has_fat():
gd.fat_init()
@@ -288,8 +286,8 @@ def checkout_ref(runcmd, gitdir, ref):
def index_has_changes(runcmd, gitdir):
'''Returns True if there are no staged changes to commit'''
try:
- runcmd(['git', 'diff-index', '--cached', '--quiet',
- '--ignore-submodules', 'HEAD'], cwd=gitdir)
+ gitcmd(runcmd, 'diff-index', '--cached', '--quiet',
+ '--ignore-submodules', 'HEAD', cwd=gitdir)
except cliapp.AppException:
return True
return False
@@ -298,20 +296,20 @@ def index_has_changes(runcmd, gitdir):
def reset_workdir(runcmd, gitdir):
'''Removes any differences between the current commit '''
'''and the status of the working directory'''
- runcmd(['git', 'clean', '-fxd'], cwd=gitdir)
- runcmd(['git', 'reset', '--hard', 'HEAD'], cwd=gitdir)
+ gitcmd(runcmd, 'clean', '-fxd', cwd=gitdir)
+ gitcmd(runcmd, 'reset', '--hard', 'HEAD', cwd=gitdir)
def clone_into(runcmd, srcpath, targetpath, ref=None):
'''Clones a repo in srcpath into targetpath, optionally directly at ref.'''
if ref is None:
- runcmd(['git', 'clone', srcpath, targetpath])
+ gitcmd(runcmd, 'clone', srcpath, targetpath)
elif is_valid_sha1(ref):
- runcmd(['git', 'clone', srcpath, targetpath])
- runcmd(['git', 'checkout', ref], cwd=targetpath)
+ gitcmd(runcmd, 'clone', srcpath, targetpath)
+ gitcmd(runcmd, 'checkout', ref, cwd=targetpath)
else:
- runcmd(['git', 'clone', '-b', ref, srcpath, targetpath])
+ gitcmd(runcmd, 'clone', '-b', ref, srcpath, targetpath)
gd = morphlib.gitdir.GitDirectory(targetpath)
if gd.has_fat():
gd.fat_init()
@@ -324,4 +322,17 @@ def is_valid_sha1(ref):
def rev_parse(runcmd, gitdir, ref):
'''Find the sha1 for the given ref'''
- return runcmd(['git', 'rev-parse', '--verify', ref], cwd=gitdir)[0:40]
+ return gitcmd(runcmd, 'rev-parse', '--verify', ref, cwd=gitdir)[0:40]
+
+
+def gitcmd(runcmd, *args, **kwargs):
+ '''Run git commands safely'''
+ if 'env' not in kwargs:
+ kwargs['env'] = dict(os.environ)
+ # git replace means we can't trust that just the sha1 of the branch
+ # is enough to say what it contains, so we turn it off by setting
+ # the right flag in an environment variable.
+ kwargs['env']['GIT_NO_REPLACE_OBJECTS'] = '1'
+ cmdline = ['git']
+ cmdline.extend(args)
+ return runcmd(cmdline, **kwargs)
diff --git a/morphlib/gitdir.py b/morphlib/gitdir.py
index fea26c2..40ac643 100644
--- a/morphlib/gitdir.py
+++ b/morphlib/gitdir.py
@@ -240,13 +240,14 @@ class Remote(object):
def set_fetch_url(self, url):
self.fetch_url = url
if self.name is not None:
- self.gd._runcmd(['git', 'remote', 'set-url', self.name, url])
+ morphlib.git.gitcmd(self.gd._runcmd, 'remote', 'set-url',
+ self.name, url)
def set_push_url(self, url):
self.push_url = url
if self.name is not None:
- self.gd._runcmd(['git', 'remote', 'set-url', '--push',
- self.name, url])
+ morphlib.git.gitcmd(self.gd._runcmd, 'remote', 'set-url',
+ '--push', self.name, url)
def _get_remote_url(self, remote_name, kind):
# As distasteful as it is to parse the output of porcelain
@@ -261,7 +262,7 @@ class Remote(object):
# It is only possible to use git to get the push url by parsing
# `git remote -v` or `git remote show -n <remote>`, and `git
# remote -v` is easier to parse.
- output = self.gd._runcmd(['git', 'remote', '-v'])
+ output = morphlib.git.gitcmd(self.gd._runcmd, 'remote', '-v')
for line in output.splitlines():
words = line.split()
if (len(words) == 3 and
@@ -288,7 +289,8 @@ class Remote(object):
yield sha1, refname
def ls(self): # pragma: no cover
- out = self.gd._runcmd(['git', 'ls-remote', self.get_fetch_url()])
+ out = morphlib.git.gitcmd(self.gd._runcmd, 'ls-remote',
+ self.get_fetch_url())
return self._parse_ls_remote_output(out)
@staticmethod
@@ -320,10 +322,11 @@ class Remote(object):
if not refspecs:
raise NoRefspecsError(self)
push_name = self.name or self.get_push_url()
- cmdline = ['git', 'push', '--porcelain', push_name]
+ cmdline = ['push', '--porcelain', push_name]
cmdline.extend(itertools.chain.from_iterable(
rs.push_args for rs in refspecs))
- exit, out, err = self.gd._runcmd_unchecked(cmdline)
+ exit, out, err = morphlib.git.gitcmd(self.gd._runcmd_unchecked,
+ *cmdline)
if exit != 0:
raise PushFailureError(self, refspecs, exit,
self._parse_push_output(out), err)
@@ -332,9 +335,9 @@ class Remote(object):
def pull(self, branch=None): # pragma: no cover
if branch:
repo = self.get_fetch_url()
- ret = self.gd._runcmd(['git', 'pull', repo, branch])
+ ret = morphlib.git.gitcmd(self.gd._runcmd, 'pull', repo, branch)
else:
- ret = self.gd._runcmd(['git', 'pull'])
+ ret = morphlib.git.gitcmd(self.gd._runcmd, 'pull')
return ret
@@ -355,6 +358,7 @@ class GitDirectory(object):
# so we just use the provided dirname
if not self.dirname:
self.dirname = dirname
+ self._config = {}
def _runcmd(self, argv, **kwargs):
'''Run a command at the root of the git directory.
@@ -373,7 +377,7 @@ class GitDirectory(object):
def checkout(self, branch_name): # pragma: no cover
'''Check out a git branch.'''
- self._runcmd(['git', 'checkout', branch_name])
+ morphlib.git.gitcmd(self._runcmd, 'checkout', branch_name)
if self.has_fat():
self.fat_init()
self.fat_pull()
@@ -387,10 +391,10 @@ class GitDirectory(object):
'''
- argv = ['git', 'branch', new_branch_name]
+ argv = ['branch', new_branch_name]
if base_ref is not None:
argv.append(base_ref)
- self._runcmd(argv)
+ morphlib.git.gitcmd(self._runcmd, *argv)
def is_currently_checked_out(self, ref): # pragma: no cover
'''Is ref currently checked out?'''
@@ -398,12 +402,12 @@ class GitDirectory(object):
# Try the ref name directly first. If that fails, prepend origin/
# to it. (FIXME: That's a kludge, and should be fixed.)
try:
- parsed_ref = self._runcmd(['git', 'rev-parse', ref]).strip()
+ parsed_ref = morphlib.git.gitcmd(self._runcmd, 'rev-parse', ref)
except cliapp.AppException:
- parsed_ref = self._runcmd(
- ['git', 'rev-parse', 'origin/%s' % ref]).strip()
- parsed_head = self._runcmd(['git', 'rev-parse', 'HEAD']).strip()
- return parsed_ref == parsed_head
+ parsed_ref = morphlib.git.gitcmd(self._runcmd, 'rev-parse',
+ 'origin/%s' % ref)
+ parsed_head = morphlib.git.gitcmd(self._runcmd, 'rev-parse', 'HEAD')
+ return parsed_ref.strip() == parsed_head.strip()
def get_file_from_ref(self, ref, filename): # pragma: no cover
'''Get file contents from git by ref and filename.
@@ -425,13 +429,13 @@ class GitDirectory(object):
def get_blob_contents(self, blob_id): # pragma: no cover
'''Get file contents from git by ID'''
- return self._runcmd(
- ['git', 'cat-file', 'blob', blob_id])
+ return morphlib.git.gitcmd(self._runcmd, 'cat-file', 'blob',
+ blob_id)
def get_commit_contents(self, commit_id): # pragma: no cover
'''Get commit contents from git by ID'''
- return self._runcmd(
- ['git', 'cat-file', 'commit', commit_id])
+ return morphlib.git.gitcmd(self._runcmd, 'cat-file', 'commit',
+ commit_id)
def update_submodules(self, app): # pragma: no cover
'''Change .gitmodules URLs, and checkout submodules.'''
@@ -446,13 +450,16 @@ class GitDirectory(object):
'''
- self._runcmd(['git', 'config', key, value])
+ morphlib.git.gitcmd(self._runcmd, 'config', key, value)
+ self._config[key] = value
def get_config(self, key):
'''Return value for a git repository configuration variable.'''
- value = self._runcmd(['git', 'config', '-z', key])
- return value.rstrip('\0')
+ if key not in self._config:
+ value = morphlib.git.gitcmd(self._runcmd, 'config', '-z', key)
+ self._config[key] = value.rstrip('\0')
+ return self._config[key]
def get_remote(self, *args, **kwargs):
'''Get a remote for this Repository.
@@ -465,7 +472,7 @@ class GitDirectory(object):
def update_remotes(self): # pragma: no cover
'''Run "git remote update --prune".'''
- self._runcmd(['git', 'remote', 'update', '--prune'])
+ morphlib.git.gitcmd(self._runcmd, 'remote', 'update', '--prune')
def is_bare(self):
'''Determine whether the repository has no work tree (is bare)'''
@@ -490,14 +497,15 @@ class GitDirectory(object):
def _rev_parse(self, ref):
try:
- return self._runcmd(['git', 'rev-parse', '--verify', ref]).strip()
+ return morphlib.git.gitcmd(self._runcmd, 'rev-parse',
+ '--verify', ref).strip()
except cliapp.AppException as e:
raise InvalidRefError(self, ref)
def disambiguate_ref(self, ref): # pragma: no cover
try:
- out = self._runcmd(['git', 'rev-parse', '--symbolic-full-name',
- ref])
+ out = morphlib.git.gitcmd(self._runcmd, 'rev-parse',
+ '--symbolic-full-name', ref)
return out.strip()
except cliapp.AppException: # ref not found
if ref.startswith('refs/heads/'):
@@ -523,7 +531,8 @@ class GitDirectory(object):
def _list_files_in_ref(self, ref):
tree = self.resolve_ref_to_tree(ref)
- output = self._runcmd(['git', 'ls-tree', '--name-only', '-rz', tree])
+ output = morphlib.git.gitcmd(self._runcmd, 'ls-tree',
+ '--name-only', '-rz', tree)
# ls-tree appends \0 instead of interspersing, so we need to
# strip the trailing \0 before splitting
paths = output.strip('\0').split('\0')
@@ -544,13 +553,15 @@ class GitDirectory(object):
if ref is None:
filepath = os.path.join(self.dirname, filename.lstrip('/'))
return os.path.islink(filepath)
- tree_entry = self._runcmd(['git', 'ls-tree', ref, filename])
+ tree_entry = morphlib.git.gitcmd(self._runcmd, 'ls-tree', ref,
+ filename)
file_mode = tree_entry.split(' ', 1)[0]
return file_mode == '120000'
@property
def HEAD(self):
- output = self._runcmd(['git', 'rev-parse', '--abbrev-ref', 'HEAD'])
+ output = morphlib.git.gitcmd(self._runcmd, 'rev-parse',
+ '--abbrev-ref', 'HEAD')
return output.strip()
def get_index(self, index_file=None):
@@ -568,8 +579,8 @@ class GitDirectory(object):
kwargs = {'feed_stdin': blob_contents}
else:
kwargs = {'stdin': blob_contents}
- return self._runcmd(['git', 'hash-object', '-t', 'blob',
- '-w', '--stdin'], **kwargs).strip()
+ return morphlib.git.gitcmd(self._runcmd, 'hash-object', '-t', 'blob',
+ '-w', '--stdin', **kwargs).strip()
def commit_tree(self, tree, parent, message, **kwargs):
'''Create a commit'''
@@ -586,9 +597,9 @@ class GitDirectory(object):
envname = 'GIT_%s_DATE' % who.upper()
if argname in kwargs:
env[envname] = kwargs[argname].isoformat()
- return self._runcmd(['git', 'commit-tree', tree,
- '-p', parent, '-m', message],
- env=env).strip()
+ return morphlib.git.gitcmd(self._runcmd, 'commit-tree', tree,
+ '-p', parent, '-m', message,
+ env=env).strip()
@staticmethod
def _check_is_sha1(string):
@@ -596,14 +607,14 @@ class GitDirectory(object):
raise ExpectedSha1Error(string)
def _update_ref(self, ref_args, message):
- args = ['git', 'update-ref']
+ args = ['update-ref']
# No test coverage, since while this functionality is useful,
# morph does not need an API for inspecting the reflog, so
# it existing purely to test ref updates is a tad overkill.
if message is not None: # pragma: no cover
args.extend(('-m', message))
args.extend(ref_args)
- self._runcmd(args)
+ morphlib.git.gitcmd(self._runcmd, *args)
def add_ref(self, ref, sha1, message=None):
'''Create a ref called `ref` in the repository pointing to `sha1`.
@@ -662,18 +673,18 @@ class GitDirectory(object):
raise RefDeleteError(self, ref, old_sha1, e)
def describe(self):
- version = self._runcmd(
- ['git', 'describe', '--always', '--dirty=-unreproducible'])
+ version = morphlib.git.gitcmd(self._runcmd, 'describe',
+ '--always', '--dirty=-unreproducible')
return version.strip()
def fat_init(self): # pragma: no cover
- return self._runcmd(['git', 'fat', 'init'])
+ return morphlib.git.gitcmd(self._runcmd, 'fat', 'init')
def fat_push(self): # pragma: no cover
- return self._runcmd(['git', 'fat', 'push'])
+ return morphlib.git.gitcmd(self._runcmd, 'fat', 'push')
def fat_pull(self): # pragma: no cover
- return self._runcmd(['git', 'fat', 'pull'])
+ return morphlib.git.gitcmd(self._runcmd, 'fat', 'pull')
def has_fat(self): # pragma: no cover
return os.path.isfile(self.join_path('.gitfat'))
@@ -688,7 +699,7 @@ class GitDirectory(object):
def init(dirname):
'''Initialise a new git repository.'''
- cliapp.runcmd(['git', 'init'], cwd=dirname)
+ morphlib.git.gitcmd(cliapp.runcmd, 'init', cwd=dirname)
gd = GitDirectory(dirname)
return gd
diff --git a/morphlib/gitdir_tests.py b/morphlib/gitdir_tests.py
index b3b4a8a..456e371 100644
--- a/morphlib/gitdir_tests.py
+++ b/morphlib/gitdir_tests.py
@@ -71,12 +71,13 @@ class GitDirectoryContentsTests(unittest.TestCase):
for fn in ('foo', 'bar.morph', 'baz.morph', 'quux'):
with open(os.path.join(self.dirname, fn), "w") as f:
f.write('dummy morphology text')
- gd._runcmd(['git', 'add', '.'])
- gd._runcmd(['git', 'commit', '-m', 'Initial commit'])
+ morphlib.git.gitcmd(gd._runcmd, 'add', '.')
+ morphlib.git.gitcmd(gd._runcmd, 'commit', '-m', 'Initial commit')
os.rename(os.path.join(self.dirname, 'foo'),
os.path.join(self.dirname, 'foo.morph'))
self.mirror = os.path.join(self.tempdir, 'mirror')
- gd._runcmd(['git', 'clone', '--mirror', self.dirname, self.mirror])
+ morphlib.git.gitcmd(gd._runcmd, 'clone', '--mirror', self.dirname,
+ self.mirror)
def tearDown(self):
shutil.rmtree(self.tempdir)
@@ -209,10 +210,11 @@ class GitDirectoryContentsTests(unittest.TestCase):
def test_describe(self):
gd = morphlib.gitdir.GitDirectory(self.dirname)
- gd._runcmd(['git', 'tag', '-a', '-m', 'Example', 'example', 'HEAD'])
+ morphlib.git.gitcmd(gd._runcmd, 'tag', '-a', '-m', 'Example',
+ 'example', 'HEAD')
self.assertEqual(gd.describe(), 'example-unreproducible')
- gd._runcmd(['git', 'reset', '--hard'])
+ morphlib.git.gitcmd(gd._runcmd, 'reset', '--hard')
self.assertEqual(gd.describe(), 'example')
@@ -227,10 +229,11 @@ class GitDirectoryFileTypeTests(unittest.TestCase):
f.write('dummy morphology text')
os.symlink('file', os.path.join(self.dirname, 'link'))
os.symlink('no file', os.path.join(self.dirname, 'broken'))
- gd._runcmd(['git', 'add', '.'])
- gd._runcmd(['git', 'commit', '-m', 'Initial commit'])
+ morphlib.git.gitcmd(gd._runcmd, 'add', '.')
+ morphlib.git.gitcmd(gd._runcmd, 'commit', '-m', 'Initial commit')
self.mirror = os.path.join(self.tempdir, 'mirror')
- gd._runcmd(['git', 'clone', '--mirror', self.dirname, self.mirror])
+ morphlib.git.gitcmd(gd._runcmd, 'clone', '--mirror', self.dirname,
+ self.mirror)
def tearDown(self):
shutil.rmtree(self.tempdir)
@@ -262,14 +265,14 @@ class GitDirectoryRefTwiddlingTests(unittest.TestCase):
gd = morphlib.gitdir.init(self.dirname)
with open(os.path.join(self.dirname, 'foo'), 'w') as f:
f.write('dummy text\n')
- gd._runcmd(['git', 'add', '.'])
- gd._runcmd(['git', 'commit', '-m', 'Initial commit'])
+ morphlib.git.gitcmd(gd._runcmd, 'add', '.')
+ morphlib.git.gitcmd(gd._runcmd, 'commit', '-m', 'Initial commit')
# Add a second commit for update_ref test, so it has another
# commit to roll back from
with open(os.path.join(self.dirname, 'bar'), 'w') as f:
f.write('dummy text\n')
- gd._runcmd(['git', 'add', '.'])
- gd._runcmd(['git', 'commit', '-m', 'Second commit'])
+ morphlib.git.gitcmd(gd._runcmd, 'add', '.')
+ morphlib.git.gitcmd(gd._runcmd, 'commit', '-m', 'Second commit')
def tearDown(self):
shutil.rmtree(self.tempdir)
@@ -347,7 +350,8 @@ class GitDirectoryRemoteConfigTests(unittest.TestCase):
self.assertEqual(remote.get_fetch_url(), None)
self.assertEqual(remote.get_push_url(), None)
- gitdir._runcmd(['git', 'remote', 'add', 'origin', 'foobar'])
+ morphlib.git.gitcmd(gitdir._runcmd, 'remote', 'add', 'origin',
+ 'foobar')
fetch_url = 'git://git.example.com/foo.git'
push_url = 'ssh://git@git.example.com/foo.git'
remote.set_fetch_url(fetch_url)
@@ -424,15 +428,15 @@ class GitDirectoryRemotePushTests(unittest.TestCase):
gd = morphlib.gitdir.init(self.dirname)
with open(os.path.join(self.dirname, 'foo'), 'w') as f:
f.write('dummy text\n')
- gd._runcmd(['git', 'add', '.'])
- gd._runcmd(['git', 'commit', '-m', 'Initial commit'])
- gd._runcmd(['git', 'checkout', '-b', 'foo'])
+ morphlib.git.gitcmd(gd._runcmd, 'add', '.')
+ morphlib.git.gitcmd(gd._runcmd, 'commit', '-m', 'Initial commit')
+ morphlib.git.gitcmd(gd._runcmd, 'checkout', '-b', 'foo')
with open(os.path.join(self.dirname, 'foo'), 'w') as f:
f.write('updated text\n')
- gd._runcmd(['git', 'add', '.'])
- gd._runcmd(['git', 'commit', '-m', 'Second commit'])
+ morphlib.git.gitcmd(gd._runcmd, 'add', '.')
+ morphlib.git.gitcmd(gd._runcmd, 'commit', '-m', 'Second commit')
self.mirror = os.path.join(self.tempdir, 'mirror')
- gd._runcmd(['git', 'init', '--bare', self.mirror])
+ morphlib.git.gitcmd(gd._runcmd, 'init', '--bare', self.mirror)
def tearDown(self):
shutil.rmtree(self.tempdir)
diff --git a/morphlib/gitindex.py b/morphlib/gitindex.py
index 6be4aac..e22f622 100644
--- a/morphlib/gitindex.py
+++ b/morphlib/gitindex.py
@@ -17,6 +17,7 @@
import collections
+import os
import morphlib
@@ -47,9 +48,9 @@ class GitIndex(object):
def _run_git(self, *args, **kwargs):
if self._index_file is not None:
- kwargs['env'] = kwargs.get('env', {})
+ kwargs['env'] = kwargs.get('env', dict(os.environ))
kwargs['env']['GIT_INDEX_FILE'] = self._index_file
- return self._gd._runcmd(['git'] + list(args), **kwargs)
+ return morphlib.git.gitcmd(self._gd._runcmd, *args, **kwargs)
def _get_status(self):
'''Return git status output in a Python useful format
diff --git a/morphlib/gitindex_tests.py b/morphlib/gitindex_tests.py
index 7a8953f..32d40a8 100644
--- a/morphlib/gitindex_tests.py
+++ b/morphlib/gitindex_tests.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013 Codethink Limited
+# Copyright (C) 2013-2014 Codethink Limited
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -33,10 +33,11 @@ class GitIndexTests(unittest.TestCase):
gd = morphlib.gitdir.init(self.dirname)
with open(os.path.join(self.dirname, 'foo'), 'w') as f:
f.write('dummy text\n')
- gd._runcmd(['git', 'add', '.'])
- gd._runcmd(['git', 'commit', '-m', 'Initial commit'])
+ morphlib.git.gitcmd(gd._runcmd, 'add', '.')
+ morphlib.git.gitcmd(gd._runcmd, 'commit', '-m', 'Initial commit')
self.mirror = os.path.join(self.tempdir, 'mirror')
- gd._runcmd(['git', 'clone', '--mirror', self.dirname, self.mirror])
+ morphlib.git.gitcmd(gd._runcmd, 'clone', '--mirror', self.dirname,
+ self.mirror)
def tearDown(self):
shutil.rmtree(self.tempdir)
diff --git a/morphlib/localrepocache.py b/morphlib/localrepocache.py
index 9c20e4b..8d2030c 100644
--- a/morphlib/localrepocache.py
+++ b/morphlib/localrepocache.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2013 Codethink Limited
+# Copyright (C) 2012-2014 Codethink Limited
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -110,7 +110,7 @@ class LocalRepoCache(object):
'''
- self._app.runcmd(['git'] + args, cwd=cwd)
+ morphlib.git.gitcmd(self._app.runcmd, *args, cwd=cwd)
def _fetch(self, url, path): # pragma: no cover
'''Fetch contents of url into a file.
diff --git a/morphlib/morphologyfinder_tests.py b/morphlib/morphologyfinder_tests.py
index b07b261..67161f9 100644
--- a/morphlib/morphologyfinder_tests.py
+++ b/morphlib/morphologyfinder_tests.py
@@ -34,8 +34,8 @@ class MorphologyFinderTests(unittest.TestCase):
for fn in ('foo', 'bar.morph', 'baz.morph', 'quux'):
with open(os.path.join(self.dirname, fn), "w") as f:
f.write('dummy morphology text')
- gd._runcmd(['git', 'add', '.'])
- gd._runcmd(['git', 'commit', '-m', 'Initial commit'])
+ morphlib.git.gitcmd(gd._runcmd, 'add', '.')
+ morphlib.git.gitcmd(gd._runcmd, 'commit', '-m', 'Initial commit')
# Changes for difference between commited and work tree
newmorphpath = os.path.join(self.dirname, 'foo.morph')
@@ -45,7 +45,8 @@ class MorphologyFinderTests(unittest.TestCase):
# Changes for bare repository
self.mirror = os.path.join(self.tempdir, 'mirror')
- gd._runcmd(['git', 'clone', '--mirror', self.dirname, self.mirror])
+ morphlib.git.gitcmd(gd._runcmd, 'clone', '--mirror', self.dirname,
+ self.mirror)
def tearDown(self):
shutil.rmtree(self.tempdir)
diff --git a/morphlib/sysbranchdir.py b/morphlib/sysbranchdir.py
index 19fba69..4351c6b 100644
--- a/morphlib/sysbranchdir.py
+++ b/morphlib/sysbranchdir.py
@@ -61,11 +61,13 @@ class SystemBranchDirectory(object):
def set_config(self, key, value):
'''Set a configuration key/value pair.'''
- cliapp.runcmd(['git', 'config', '-f', self._config_path, key, value])
+ morphlib.git.gitcmd(cliapp.runcmd, 'config', '-f',
+ self._config_path, key, value)
def get_config(self, key):
'''Get a configuration value for a given key.'''
- value = cliapp.runcmd(['git', 'config', '-f', self._config_path, key])
+ value = morphlib.git.gitcmd(cliapp.runcmd, 'config', '-f',
+ self._config_path, key)
return value.strip()
def _find_git_directory(self, repo_url):
diff --git a/morphlib/sysbranchdir_tests.py b/morphlib/sysbranchdir_tests.py
index 8b40f69..1aca54e 100644
--- a/morphlib/sysbranchdir_tests.py
+++ b/morphlib/sysbranchdir_tests.py
@@ -50,16 +50,17 @@ class SystemBranchDirectoryTests(unittest.TestCase):
self.path = path
os.mkdir(self.path)
- cliapp.runcmd(['git', 'init', self.path])
+ morphlib.git.gitcmd(cliapp.runcmd, 'init', self.path)
with open(os.path.join(self.path, 'filename'), 'w') as f:
f.write('this is a file\n')
- cliapp.runcmd(['git', 'add', 'filename'], cwd=self.path)
- cliapp.runcmd(
- ['git', 'commit', '-m', 'initial'], cwd=self.path)
+ morphlib.git.gitcmd(cliapp.runcmd, 'add', 'filename',
+ cwd=self.path)
+ morphlib.git.gitcmd(cliapp.runcmd, 'commit', '-m', 'initial',
+ cwd=self.path)
def clone_checkout(self, ref, target_dir):
- cliapp.runcmd(
- ['git', 'clone', '-b', ref, self.path, target_dir])
+ morphlib.git.gitcmd(cliapp.runcmd, 'clone', '-b', ref,
+ self.path, target_dir)
subdir = tempfile.mkdtemp(dir=self.tempdir)
path = os.path.join(subdir, 'foo')