summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--morphlib/builder2.py20
-rw-r--r--morphlib/cachedrepo.py50
-rw-r--r--morphlib/git.py51
-rw-r--r--morphlib/localrepocache.py4
-rw-r--r--morphlib/plugins/branch_and_merge_plugin.py5
5 files changed, 107 insertions, 23 deletions
diff --git a/morphlib/builder2.py b/morphlib/builder2.py
index 0b986540..72165ce7 100644
--- a/morphlib/builder2.py
+++ b/morphlib/builder2.py
@@ -308,16 +308,14 @@ class ChunkBuilder(BuilderBase):
cache_dir = os.path.dirname(self.artifact.source.repo.path)
- def extract_repo(path, sha1, destdir):
+ def extract_repo(repo, sha1, destdir):
self.app.status(msg='Extracting %(source)s into %(target)s',
- source=path,
+ source=repo.original_name,
target=destdir)
- if not os.path.exists(destdir):
- os.mkdir(destdir)
- morphlib.git.copy_repository(self.app.runcmd, path, destdir)
- morphlib.git.checkout_ref(self.app.runcmd, destdir, sha1)
+
+ repo.checkout(sha1, destdir)
morphlib.git.reset_workdir(self.app.runcmd, destdir)
- submodules = morphlib.git.Submodules(self.app, path, sha1)
+ submodules = morphlib.git.Submodules(self.app, repo.path, sha1)
try:
submodules.load()
except morphlib.git.NoModulesFileError:
@@ -327,14 +325,14 @@ class ChunkBuilder(BuilderBase):
for sub in submodules:
cached_repo = self.repo_cache.get_repo(sub.url)
sub_dir = os.path.join(destdir, sub.path)
- tuples.append((cached_repo.path, sub.commit, sub_dir))
+ tuples.append((cached_repo, sub.commit, sub_dir))
return tuples
s = self.artifact.source
- todo = [(s.repo.path, s.sha1, srcdir)]
+ todo = [(s.repo, s.sha1, srcdir)]
while todo:
- path, sha1, srcdir = todo.pop()
- todo += extract_repo(path, sha1, srcdir)
+ repo, sha1, srcdir = todo.pop()
+ todo += extract_repo(repo, sha1, srcdir)
self.set_mtime_recursively(srcdir)
def set_mtime_recursively(self, root): # pragma: no cover
diff --git a/morphlib/cachedrepo.py b/morphlib/cachedrepo.py
index ad17785a..61526cd7 100644
--- a/morphlib/cachedrepo.py
+++ b/morphlib/cachedrepo.py
@@ -49,6 +49,14 @@ class CloneError(cliapp.AppException):
def __init__(self, repo, target_dir):
cliapp.AppException.__init__(
self,
+ 'Failed to clone %s into %s' % (repo.original_name, target_dir))
+
+
+class CopyError(cliapp.AppException):
+
+ def __init__(self, repo, target_dir):
+ cliapp.AppException.__init__(
+ self,
'Failed to copy %s into %s' % (repo.original_name, target_dir))
@@ -75,6 +83,9 @@ class CachedRepo(object):
remote Git repository. This remote repository is set up as the
'origin' remote.
+ Cached repositories are bare mirrors of the upstream. Locally created
+ branches will be lost the next time the repository updates.
+
CachedRepo objects can resolve Git refs into SHA1s. Given a SHA1
ref, they can also be asked to return the contents of a file via the
cat() method. They can furthermore check out the repository into
@@ -109,8 +120,7 @@ class CachedRepo(object):
if not self.is_valid_sha1(ref):
try:
refs = self._show_ref(ref).split('\n')
- # split each ref line into an array, drop non-origin branches
- refs = [x.split() for x in refs if 'origin' in x]
+ refs = [x.split() for x in refs]
absref = refs[0][0]
except cliapp.AppException:
raise InvalidReferenceError(self, ref)
@@ -146,8 +156,8 @@ class CachedRepo(object):
raise IOError('File %s does not exist in ref %s of repo %s' %
(filename, ref, self))
- def checkout(self, ref, target_dir):
- '''Unpacks the repository in a directory and checks out a commit ref.
+ def clone_checkout(self, ref, target_dir):
+ '''Clone from the cache into the target path and check out a given ref.
Raises a CheckoutDirectoryExistsError if the target
directory already exists. Raises an InvalidReferenceError if the
@@ -160,9 +170,28 @@ class CachedRepo(object):
if os.path.exists(target_dir):
raise CheckoutDirectoryExistsError(self, target_dir)
- os.mkdir(target_dir)
+ self.resolve_ref(ref)
+
+ self._clone_into(target_dir, ref)
+
+ def checkout(self, ref, target_dir):
+ '''Unpacks the repository in a directory and checks out a commit ref.
+
+ Raises an InvalidReferenceError if the ref is not found in the
+ repository. Raises a CopyError if something goes wrong with the copy
+ of the repository. Raises a CheckoutError if something else goes wrong
+ while copying the repository or checking out the SHA1 ref.
+
+ '''
+
+ if not os.path.exists(target_dir):
+ os.mkdir(target_dir)
+ # Note, we copy instead of cloning because it's much faster in the case
+ # that the target is on a different filesystem from the cache. We then
+ # take care to turn the copy into something as good as a real clone.
self._copy_repository(self.path, target_dir)
+
self._checkout_ref(ref, target_dir)
def ls_tree(self, ref):
@@ -219,11 +248,18 @@ class CachedRepo(object):
return self._runcmd(['git', 'cat-file', 'blob',
'%s:%s' % (ref, filename)])
+ def _clone_into(self, target_dir, ref): #pragma: no cover
+ '''Actually perform the clone'''
+ try:
+ morphlib.git.clone_into(self._runcmd, self.path, target_dir, ref)
+ except cliapp.AppException:
+ raise CloneError(self, target_dir)
+
def _copy_repository(self, source_dir, target_dir): # pragma: no cover
try:
morphlib.git.copy_repository(self._runcmd, source_dir, target_dir)
except cliapp.AppException:
- raise CloneError(self, target_dir)
+ raise CopyError(self, target_dir)
def _checkout_ref(self, ref, target_dir): # pragma: no cover
try:
@@ -232,7 +268,7 @@ class CachedRepo(object):
raise CheckoutError(self, ref, target_dir)
def _update(self): # pragma: no cover
- self._runcmd(['git', 'remote', 'update', 'origin'])
+ self._runcmd(['git', 'remote', 'update', 'origin', '--prune'])
def __str__(self): # pragma: no cover
return self.url
diff --git a/morphlib/git.py b/morphlib/git.py
index d8169c93..9ab98ad0 100644
--- a/morphlib/git.py
+++ b/morphlib/git.py
@@ -154,8 +154,42 @@ def set_remote(runcmd, gitdir, name, url):
def copy_repository(runcmd, repo, destdir):
- '''Copies a cached repository into a directory using cp.'''
- return runcmd(['cp', '-a', os.path.join(repo, '.git'), destdir])
+ '''Copies a cached repository into a directory using cp.
+
+ This also fixes up the repository afterwards, so that it can contain
+ code etc. It does not leave any given branch ready for use.
+
+ '''
+ 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)
+ # we do not want the origin remote to behave as a mirror for pulls
+ runcmd(['git', '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)
+ # set the origin url to the cached repo so that we can quickly clean up
+ runcmd(['git', '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)
+ # 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)
+ with open(os.path.join(destdir, ".git", "packed-refs"), "r") as ref_fh:
+ pack_lines = ref_fh.read().split("\n")
+ with open(os.path.join(destdir, ".git", "packed-refs"), "w") as ref_fh:
+ ref_fh.write(pack_lines.pop(0) + "\n")
+ for refline in pack_lines:
+ if ' refs/remotes/' in refline:
+ continue
+ if ' refs/heads/' in refline:
+ sha, ref = refline[:40], refline[41:]
+ if ref.startswith("refs/heads/"):
+ ref = "refs/remotes/origin/" + ref[11:]
+ 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)
def checkout_ref(runcmd, gitdir, ref):
@@ -167,3 +201,16 @@ def reset_workdir(runcmd, gitdir):
'''and the status of the working directory'''
runcmd(['git', 'clean', '-fxd'], cwd=gitdir)
runcmd(['git', '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])
+ else:
+ runcmd(['git', 'clone', '-b', ref, srcpath, targetpath])
+
+def find_first_ref(runcmd, gitdir, ref):
+ '''Find the *first* ref match and returns its sha1.'''
+ return runcmd(['git', 'show-ref', ref],
+ cwd=gitdir).split("\n")[0].split(" ")[0]
diff --git a/morphlib/localrepocache.py b/morphlib/localrepocache.py
index c5a95ebc..89be56c8 100644
--- a/morphlib/localrepocache.py
+++ b/morphlib/localrepocache.py
@@ -197,7 +197,7 @@ class LocalRepoCache(object):
return False, 'Unable to fetch bundle %s: %s' % (bundle_url, e)
try:
- self._git(['clone', '-n', bundle_path, path])
+ self._git(['clone', '--mirror', '-n', bundle_path, path])
self._git(['remote', 'set-url', 'origin', repourl], cwd=path)
except cliapp.AppException, e: # pragma: no cover
if self._exists(path):
@@ -236,7 +236,7 @@ class LocalRepoCache(object):
repourl = self._resolver.pull_url(reponame)
path = self._cache_name(repourl)
try:
- self._git(['clone', '-n', repourl, path])
+ self._git(['clone', '--mirror', '-n', repourl, path])
except cliapp.AppException, e:
errors.append('Unable to clone from %s to %s: %s' %
(repourl, path, e))
diff --git a/morphlib/plugins/branch_and_merge_plugin.py b/morphlib/plugins/branch_and_merge_plugin.py
index d3aced4b..82d83a58 100644
--- a/morphlib/plugins/branch_and_merge_plugin.py
+++ b/morphlib/plugins/branch_and_merge_plugin.py
@@ -194,7 +194,7 @@ class BranchAndMergePlugin(cliapp.Plugin):
os.makedirs(parent_dir)
# Clone it from cache to target directory.
- repo.checkout(ref, os.path.abspath(dirname))
+ repo.clone_checkout(ref, os.path.abspath(dirname))
# Remember the repo name we cloned from in order to be able
# to identify the repo again later using the same name, even
@@ -221,6 +221,9 @@ class BranchAndMergePlugin(cliapp.Plugin):
with open(filename) as f:
text = f.read()
else:
+ ref = morphlib.git.find_first_ref(self.app.runcmd, repo_dir, ref)
+ logging.warning("Running git cat-file blob %s:%s.morph in %s" % (
+ ref, name, repo_dir))
text = self.app.runcmd(['git', 'cat-file', 'blob',
'%s:%s.morph' % (ref, name)], cwd=repo_dir)
morphology = morphlib.morph2.Morphology(text)