← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~pappacena/turnip:copy-ref-helper into turnip:master

 

Thiago F. Pappacena has proposed merging ~pappacena/turnip:copy-ref-helper into turnip:master with ~pappacena/turnip:celery-test-fixture as a prerequisite.

Commit message:
Celery task to copy refs between repositories and delete them

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~pappacena/turnip/+git/turnip/+merge/390205
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~pappacena/turnip:copy-ref-helper into turnip:master.
diff --git a/turnip/api/store.py b/turnip/api/store.py
index f4df7e9..0a1ea72 100644
--- a/turnip/api/store.py
+++ b/turnip/api/store.py
@@ -28,7 +28,7 @@ from pygit2 import (
     )
 
 from turnip.pack.helpers import ensure_config
-
+from turnip.tasks import app
 
 REF_TYPE_NAME = {
     GIT_OBJ_COMMIT: 'commit',
@@ -114,6 +114,41 @@ def write_alternates(repo_path, alternate_repo_paths):
 object_dir_re = re.compile(r'\A[0-9a-f][0-9a-f]\Z')
 
 
+@app.task
+def copy_ref(from_root, from_ref, to_root, to_ref=None):
+    """Copy a single ref from one git repository to another.
+
+    If `to_ref` is None, the copy will use the `from_ref` name from
+    origin to the destination repository.
+
+    This is implemented now using git client's "git fetch" command,
+    since it's way easier than trying to copy the refs, commits and objects
+    manually using pygit.
+
+    :param from_root: The root directory of the source git repository.
+    :param from_ref: The source ref name.
+    :param to_root: The root directory of the destination git repository.
+    :param to_ref: The destination ref name. If None, copy_ref will use the
+                   source ref name.
+    """
+    if to_ref is None:
+        to_ref = from_ref
+    cmd = [
+        b'git', b'fetch', b'--no-tags',
+        from_root, b'%s:%s' % (from_ref, to_ref)
+    ]
+    proc = subprocess.Popen(
+        cmd, cwd=to_root, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    if proc.wait() != 0:
+        raise GitError("Error copying refs: %s" % proc.stderr.read())
+
+
+@app.task
+def delete_ref(repo_root, ref_name):
+    repo = Repository(repo_root)
+    repo.references[ref_name].delete()
+
+
 def copy_refs(from_root, to_root):
     """Copy refs from one .git directory to another.
 
diff --git a/turnip/api/tests/test_store.py b/turnip/api/tests/test_store.py
index 02ed3fd..3d8f3ce 100644
--- a/turnip/api/tests/test_store.py
+++ b/turnip/api/tests/test_store.py
@@ -26,6 +26,7 @@ from turnip.api.tests.test_helpers import (
     open_repo,
     RepoFactory,
     )
+from turnip.tests.tasks import CeleryWorkerFixture
 
 
 class InitTestCase(TestCase):
@@ -95,8 +96,9 @@ class InitTestCase(TestCase):
 
     def makeOrig(self):
         self.orig_path = os.path.join(self.repo_store, 'orig/')
-        orig = RepoFactory(
-            self.orig_path, num_branches=3, num_commits=2, num_tags=2).build()
+        self.orig_factory = RepoFactory(
+            self.orig_path, num_branches=3, num_commits=2, num_tags=2)
+        orig = self.orig_factory.build()
         self.orig_refs = {}
         for ref in orig.references.objects:
             obj = orig[ref.target]
@@ -356,3 +358,89 @@ class InitTestCase(TestCase):
             self.orig_refs, os.path.join(to_path, 'turnip-subordinate'))
         self.assertPackedRefs(
             packed_refs, os.path.join(too_path, 'turnip-subordinate'))
+
+    def test_copy_ref(self):
+        celery_fixture = CeleryWorkerFixture()
+        self.useFixture(celery_fixture)
+
+        self.makeOrig()
+        # Creates a new branch in the orig repository.
+        orig_path = self.orig_path
+        orig = self.orig_factory.repo
+        master_tip = orig.references[b'refs/heads/master'].target.hex
+
+        orig_branch_name = b'new-branch'
+        orig_ref_name = b'refs/heads/new-branch'
+        orig.create_branch(orig_branch_name, orig[master_tip])
+        orig_commit_oid = self.orig_factory.add_commit(
+            b'foobar file content', 'foobar.txt', parents=[master_tip],
+            ref=orig_ref_name)
+        orig_blob_id = orig[orig_commit_oid].tree[0].id
+
+        dest_path = os.path.join(self.repo_store, 'to/')
+        store.init_repo(dest_path, clone_from=self.orig_path)
+
+        dest = pygit2.Repository(dest_path)
+        self.assertEqual([], dest.references.objects)
+
+        dest_ref_name = b'refs/merge/123'
+        store.copy_ref.apply_async(
+            (orig_path, orig_ref_name, dest_path, dest_ref_name))
+        celery_fixture.waitUntil(5, lambda: len(dest.references.objects) == 1)
+
+        self.assertEqual(1, len(dest.references.objects))
+        copied_ref = dest.references.objects[0]
+        self.assertEqual(dest_ref_name, copied_ref.name)
+        self.assertEqual(
+            orig.references[orig_ref_name].target,
+            dest.references[dest_ref_name].target)
+        self.assertEqual(b'foobar file content', dest[orig_blob_id].data)
+
+        # Updating and copying again should work.
+        orig_commit_oid = self.orig_factory.add_commit(
+            b'changed foobar content', 'foobar.txt', parents=[orig_commit_oid],
+            ref=orig_ref_name)
+        orig_blob_id = orig[orig_commit_oid].tree[0].id
+
+        store.copy_ref.apply_async(
+            (orig_path, orig_ref_name, dest_path, dest_ref_name))
+
+        def waitForNewCommit():
+            try:
+                return dest[orig_blob_id].data == b'changed foobar content'
+            except KeyError:
+                return False
+        celery_fixture.waitUntil(5, waitForNewCommit)
+
+        self.assertEqual(1, len(dest.references.objects))
+        copied_ref = dest.references.objects[0]
+        self.assertEqual(dest_ref_name, copied_ref.name)
+        self.assertEqual(
+            orig.references[orig_ref_name].target,
+            dest.references[dest_ref_name].target)
+        self.assertEqual(b'changed foobar content', dest[orig_blob_id].data)
+
+    def test_delete_ref(self):
+        celery_fixture = CeleryWorkerFixture()
+        self.useFixture(celery_fixture)
+
+        self.makeOrig()
+        orig_path = self.orig_path
+        orig = self.orig_factory.repo
+
+        master_tip = orig.references[b'refs/heads/master'].target.hex
+        new_branch_name = b'new-branch'
+        new_ref_name = b'refs/heads/new-branch'
+        orig.create_branch(new_branch_name, orig[master_tip])
+        self.orig_factory.add_commit(
+            b'foobar file content', 'foobar.txt', parents=[master_tip],
+            ref=new_ref_name)
+
+        before_refs_len = len(orig.references.objects)
+        store.delete_ref.apply_async((orig_path, new_ref_name))
+        celery_fixture.waitUntil(
+            5, lambda: len(orig.references.objects) < before_refs_len)
+
+        self.assertEqual(before_refs_len - 1, len(orig.references.objects))
+        self.assertNotIn(
+            new_branch_name, [i.name for i in orig.references.objects])