launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #20068
[Merge] ~cjwatson/turnip:blob-api into turnip:master
Colin Watson has proposed merging ~cjwatson/turnip:blob-api into turnip:master.
Commit message:
Add an API endpoint for fetching blobs
/repo/NAME/blob/path/to/file?rev=master returns the contents of
path/to/file at the commit pointed to by master.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/turnip/+git/turnip/+merge/288416
Add an API endpoint for fetching blobs
/repo/NAME/blob/path/to/file?rev=master returns the contents of
path/to/file at the commit pointed to by master.
We'll use this for extracting snapcraft.yaml from branches used for snaps in order to find their snap name.
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/turnip:blob-api into turnip:master.
diff --git a/turnip/api/store.py b/turnip/api/store.py
index 5728d0c..641702b 100644
--- a/turnip/api/store.py
+++ b/turnip/api/store.py
@@ -72,6 +72,17 @@ def format_signature(signature):
}
+def format_blob(blob):
+ """Return a formatted blob dict."""
+ if blob.type != GIT_OBJ_BLOB:
+ raise GitError('Invalid type: object {} is not a blob.'.format(
+ blob.oid.hex))
+ return {
+ 'size': blob.size,
+ 'data': blob.data,
+ }
+
+
def is_bare_repo(repo_path):
return not os.path.exists(os.path.join(repo_path, '.git'))
@@ -453,3 +464,13 @@ def detect_merges(repo_store, repo_name, target_oid, source_oids):
if not search_oids:
break
return merge_info
+
+
+def get_blob(repo_store, repo_name, rev, filename):
+ """Return a blob from a revision and file name."""
+ with open_repo(repo_store, repo_name) as repo:
+ commit = repo.revparse_single(rev)
+ if commit.type != GIT_OBJ_COMMIT:
+ raise GitError('Invalid type: object {} is not a commit.'.format(
+ commit.oid.hex))
+ return format_blob(repo[commit.tree[filename].id])
diff --git a/turnip/api/tests/test_api.py b/turnip/api/tests/test_api.py
index 687e971..6bd79c0 100644
--- a/turnip/api/tests/test_api.py
+++ b/turnip/api/tests/test_api.py
@@ -645,6 +645,44 @@ class ApiTestCase(TestCase):
self.assertEqual(200, resp.status_code)
self.assertEqual({b.hex: c.hex, e.hex: g.hex}, resp.json)
+ def test_repo_blob(self):
+ """Getting an existing blob works."""
+ factory = RepoFactory(self.repo_store)
+ c1 = factory.add_commit('a\n', 'dir/file')
+ factory.add_commit('b\n', 'dir/file', parents=[c1])
+ resp = self.app.get('/repo/{}/blob/dir/file'.format(self.repo_path))
+ self.assertEqual({'size': 2, 'data': 'b\n'}, resp.json)
+ resp = self.app.get('/repo/{}/blob/dir/file?rev=master'.format(
+ self.repo_path))
+ self.assertEqual({'size': 2, 'data': 'b\n'}, resp.json)
+ resp = self.app.get('/repo/{}/blob/dir/file?rev={}'.format(
+ self.repo_path, c1.hex))
+ self.assertEqual({'size': 2, 'data': 'a\n'}, resp.json)
+
+ def test_repo_blob_missing_commit(self):
+ """Trying to get a blob from a non-existent commit returns HTTP 404."""
+ factory = RepoFactory(self.repo_store)
+ factory.add_commit('a\n', 'dir/file')
+ resp = self.app.get('/repo/{}/blob/dir/file?rev={}'.format(
+ self.repo_path, factory.nonexistent_oid()), expect_errors=True)
+ self.assertEqual(404, resp.status_code)
+
+ def test_repo_blob_missing_file(self):
+ """Trying to get a blob with a non-existent name returns HTTP 404."""
+ factory = RepoFactory(self.repo_store)
+ factory.add_commit('a\n', 'dir/file')
+ resp = self.app.get('/repo/{}/blob/nonexistent'.format(
+ self.repo_path), expect_errors=True)
+ self.assertEqual(404, resp.status_code)
+
+ def test_repo_blob_directory(self):
+ """Trying to get a blob referring to a directory returns HTTP 404."""
+ factory = RepoFactory(self.repo_store)
+ factory.add_commit('a\n', 'dir/file')
+ resp = self.app.get('/repo/{}/blob/dir'.format(
+ self.repo_path), expect_errors=True)
+ self.assertEqual(404, resp.status_code)
+
if __name__ == '__main__':
unittest.main()
diff --git a/turnip/api/views.py b/turnip/api/views.py
index db57e00..abdfaca 100644
--- a/turnip/api/views.py
+++ b/turnip/api/views.py
@@ -320,3 +320,27 @@ class DetectMergesAPI(BaseAPI):
except GitError:
return exc.HTTPNotFound()
return merges
+
+
+@resource(path='/repo/{name}/blob/{filename:.*}')
+class BlobAPI(BaseAPI):
+ """Provides HTTP API for fetching blobs."""
+
+ def __init__(self, request):
+ super(BlobAPI, self).__init__()
+ self.request = request
+
+ @validate_path
+ def get(self, repo_store, repo_name):
+ """Get blob by file name.
+
+ If supplied, the 'rev' request parameter identifies the revision (in
+ gitrevisions(7) syntax) where the blob should be looked up. It
+ defaults to 'HEAD'.
+ """
+ filename = self.request.matchdict['filename']
+ rev = self.request.params.get('rev', 'HEAD')
+ try:
+ return store.get_blob(repo_store, repo_name, rev, filename)
+ except (KeyError, GitError):
+ return exc.HTTPNotFound()
Follow ups