← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~blr/turnip/api-diff into lp:turnip

 

Bayard 'kit' Randel has proposed merging lp:~blr/turnip/api-diff into lp:turnip with lp:~blr/turnip/api-refs as a prerequisite.

Requested reviews:
  William Grant (wgrant)

For more details, see:
https://code.launchpad.net/~blr/turnip/api-diff/+merge/251855

Initial diff api, with additional test helpers for index staging and creating commits from arbitrary blob data and paths.
-- 
Your team Launchpad code reviewers is subscribed to branch lp:turnip.
=== modified file 'turnip/api/store.py'
--- turnip/api/store.py	2015-03-05 01:24:43 +0000
+++ turnip/api/store.py	2015-03-05 01:24:43 +0000
@@ -79,3 +79,14 @@
                "object": {'sha': git_object.oid.hex,
                           'type': get_ref_type_name(git_object.type)}}
         return ref
+
+    @staticmethod
+    def get_diff(repo_path, hash1, hash2):
+        """Get diff of two commits."""
+        repo = Store.open_repo(repo_path)
+        shas = [hash1, hash2]
+        try:
+            commits = [repo.revparse_single(sha) for sha in shas]
+        except (TypeError, KeyError):
+            raise
+        return repo.diff(commits[0], commits[1]).patch

=== modified file 'turnip/api/tests/test_api.py'
--- turnip/api/tests/test_api.py	2015-03-05 01:24:43 +0000
+++ turnip/api/tests/test_api.py	2015-03-05 01:24:43 +0000
@@ -15,7 +15,7 @@
 from webtest import TestApp
 
 from turnip import api
-from turnip.api.tests import test_helpers
+from turnip.api.tests.test_helpers import RepoFactory
 
 
 class ApiTestCase(TestCase):
@@ -28,11 +28,9 @@
         self.repo_path = str(uuid.uuid1())
         self.repo_store = os.path.join(repo_store, self.repo_path)
         self.commit = {'ref': 'refs/heads/master', 'message': 'test commit.'}
-        self.tag = {'name': 'test-tag', 'message': 'tag message'}
+        self.tag = {'ref': 'refs/tags/tag0', 'message': 'tag message'}
 
     def get_ref(self, ref):
-        test_helpers.init_repo(self.repo_store,
-                               commits=[self.commit], tags=[self.tag])
         resp = self.app.get('/repo/{}/{}'.format(
             self.repo_path, ref))
         return json.loads(resp.json_body)
@@ -51,28 +49,42 @@
     def test_repo_get_refs(self):
         """Ensure expected ref objects are returned and shas match."""
         ref = self.commit.get('ref')
-        repo = test_helpers.init_repo(self.repo_store,
-                                      commits=[self.commit], tags=[self.tag])
+        repo = RepoFactory(self.repo_store, num_commits=1, num_tags=1).build()
         resp = self.app.get('/repo/{}/refs'.format(self.repo_path))
         body = json.loads(resp.json_body)
 
         self.assertTrue(ref in body)
-        self.assertTrue('refs/tags/{}'.format(self.tag.get('name') in body))
+        self.assertTrue(self.tag.get('ref') in body)
 
         oid = repo.head.get_object().oid.hex  # git object sha
         resp_sha = body[ref]['object'].get('sha')
         self.assertEqual(oid, resp_sha)
 
     def test_repo_get_ref(self):
-        ref = 'refs/heads/master'
+        RepoFactory(self.repo_store, num_commits=1).build()
+        ref = self.commit.get('ref')
         resp = self.get_ref(ref)
         self.assertEqual(ref, resp['ref'])
 
     def test_repo_get_tag(self):
-        tag = 'refs/tags/test-tag'
+        RepoFactory(self.repo_store, num_commits=1, num_tags=1).build()
+        tag = self.tag.get('ref')
         resp = self.get_ref(tag)
         self.assertEqual(tag, resp['ref'])
 
+    def test_repo_compare_commits(self):
+        repo = RepoFactory(self.repo_store)
+
+        c1_oid = repo.add_commit('foo', 'foobar.txt')
+        c2_oid = repo.add_commit('bar', 'foobar.txt', parents=[c1_oid])
+
+        path = '/repo/{}/compare/{}..{}'.format(self.repo_path, c1_oid, c2_oid)
+        resp = self.app.get(path)
+        resp_body = json.loads(resp.body)
+        self.assertTrue('-foo' in resp_body)
+        self.assertTrue('+bar' in resp_body)
+
 
 if __name__ == '__main__':
+
     unittest.main()

=== modified file 'turnip/api/tests/test_helpers.py'
--- turnip/api/tests/test_helpers.py	2015-03-05 01:24:43 +0000
+++ turnip/api/tests/test_helpers.py	2015-03-05 01:24:43 +0000
@@ -1,39 +1,85 @@
 # Copyright 2015 Canonical Ltd.  All rights reserved.
 
+import os
+
 from pygit2 import (
     init_repository,
+    GIT_FILEMODE_BLOB,
     GIT_OBJ_COMMIT,
+    GIT_SORT_TIME,
+    IndexEntry,
     Signature,
     )
 
-AUTHOR = Signature('Test Author', 'author@xxxxxxx')
-COMMITTER = Signature('Test Commiter', 'committer@xxxxxxx')
-
-
-def create_commits(repo, commits, parents=[]):
-    tree = repo.TreeBuilder().write()
-    for commit in commits:
-        commit = repo.create_commit(
-            commit['ref'],
-            AUTHOR, COMMITTER, commit['message'],
-            tree,
-            parents
-        )
-    return repo
-
-
-def create_tags(repo, tags):
-    oid = repo.head.get_object().oid
-    for tag in tags:
-        tag = repo.create_tag(
-            tag['name'], oid, GIT_OBJ_COMMIT, COMMITTER, tag['message'])
-    return repo
-
-
-def init_repo(repo_path, commits=None, tags=None):
-    repo = init_repository(repo_path, True)
-    if commits:
-        repo = create_commits(repo, commits)
-    if tags:
-        repo = create_tags(repo, tags)
-    return repo
+
+class RepoFactory():
+    """Builds a git repository in a user defined state."""
+
+    def __init__(self, repo_store=None, num_commits=None, num_tags=None):
+        self.author = Signature('Test Author', 'author@xxxxxxx')
+        self.committer = Signature('Test Commiter', 'committer@xxxxxxx')
+        self.num_commits = num_commits
+        self.num_tags = num_tags
+        self.repo_store = repo_store
+        self.repo = self.init_repo()
+
+    @property
+    def commits(self):
+        """Walk repo from HEAD and returns list of commit objects."""
+        last = self.repo[self.repo.head.target]
+        return list(self.repo.walk(last.id, GIT_SORT_TIME))
+
+    def add_commit(self, blob_content, file_path, parents=[],
+                   ref='refs/heads/master'):
+        """Create a commit from blob_content and file_path."""
+        repo = self.repo
+
+        blob_oid = repo.create_blob(blob_content)
+        blob_entry = IndexEntry(file_path, blob_oid, GIT_FILEMODE_BLOB)
+        repo.index.add(blob_entry)
+        tree_id = repo.index.write_tree()
+        oid = repo.create_commit(ref,
+                                 self.author,
+                                 self.committer,
+                                 'commit', tree_id, parents)
+        return oid
+
+    def stage(self, file_path):
+        """Stage a file and return a tree id."""
+        repo = self.repo
+        repo.index.add(file_path)
+        repo.index.write()
+        return repo.index.write_tree()
+
+    def generate_commits(self):
+        """Generate n number of commits."""
+        parents = []
+        for i in xrange(self.num_commits):
+            blob_content = b'commit {}'.format(i)
+            test_file = 'test.txt'
+            with open(os.path.join(self.repo_store, test_file), 'w') as f:
+                f.write(blob_content)
+
+            self.stage(test_file)
+
+            commit_oid = self.add_commit(blob_content, test_file, parents)
+            parents = [commit_oid]
+
+    def generate_tags(self):
+        """Generate n number of tags."""
+        repo = self.repo
+        oid = repo.head.get_object().oid
+        for i in xrange(self.num_tags):
+            repo.create_tag('tag{}'.format(i), oid, GIT_OBJ_COMMIT,
+                            self.committer, 'tag message {}'.format(i))
+
+    def init_repo(self):
+        return init_repository(self.repo_store)
+
+    def build(self):
+        """Return a repo, optionally with generated commits and tags."""
+        if self.num_commits:
+            self.generate_commits()
+        if self.num_tags:
+            self.generate_tags()
+        return self.repo

=== modified file 'turnip/api/views.py'
--- turnip/api/views.py	2015-03-05 01:24:43 +0000
+++ turnip/api/views.py	2015-03-05 01:24:43 +0000
@@ -80,3 +80,28 @@
         except Exception:
             return exc.HTTPNotFound()
         return json.dumps(ref)
+
+
+@resource(path='/repo/{name}/compare/{c1}..{c2}')
+class DiffAPI(object):
+    """Provides HTTP API for git references."""
+
+    def __init__(self, request):
+        config = TurnipConfig()
+        self.request = request
+        self.repo_store = config.get('repo_store')
+
+    @repo_path
+    def get(self):
+        """Returns diff of two commits."""
+        c1 = self.request.matchdict['c1']
+        c2 = self.request.matchdict['c2']
+        for sha in self.request.matchdict.iteritems():
+            if 'c' in sha[0] and not 7 <= len(sha[1]) <= 40:
+                return exc.HTTPBadRequest(
+                    comment='invalid sha1: {}'.format(sha))
+        try:
+            patch = Store.get_diff(self.repo, c1, c2)
+        except:
+            return exc.HTTPNotFound()
+        return json.dumps(patch)


Follow ups