launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #18211
[Merge] lp:~blr/turnip/api-clone into lp:turnip
Bayard 'kit' Randel has proposed merging lp:~blr/turnip/api-clone into lp:turnip.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~blr/turnip/api-clone/+merge/253942
Add clone param to RepoAPI providing init from existing repo (clone) e.g.
POST /repo {'repo_path': existing_repo_path, 'clone_path': new_repo_path})
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~blr/turnip/api-clone into lp:turnip.
=== modified file 'turnip/api/store.py'
--- turnip/api/store.py 2015-03-13 03:44:09 +0000
+++ turnip/api/store.py 2015-03-24 21:30:45 +0000
@@ -3,6 +3,8 @@
import itertools
import os
import shutil
+import urllib
+import urlparse
from pygit2 import (
GitError,
@@ -10,6 +12,7 @@
GIT_OBJ_COMMIT,
GIT_OBJ_TREE,
GIT_OBJ_TAG,
+ clone_repository,
init_repository,
Repository,
)
@@ -60,23 +63,33 @@
}
-def init_repo(repo, is_bare=True):
- """Initialise a git repository."""
- if os.path.exists(repo):
- raise Exception("Repository '%s' already exists" % repo)
- repo_path = init_repository(repo, is_bare)
- return repo_path
+def is_valid_new_path(path):
+ """Verify repo path is new, or raise Exception."""
+ if os.path.exists(path):
+ raise Exception("Repository '%s' already exists" % path)
+ return True
+
+
+def init_repo(repo_path, clone_path=None, is_bare=True):
+ """Initialise a new git repository or clone from existing."""
+ if clone_path:
+ assert is_valid_new_path(clone_path)
+ repo_url = urlparse.urljoin('file:', urllib.pathname2url(repo_path))
+ repo = clone_repository(repo_url, clone_path, is_bare)
+ else:
+ assert is_valid_new_path(repo_path)
+ repo = init_repository(repo_path, is_bare)
+ return repo.path
def open_repo(repo_path):
"""Open an existing git repository."""
- repo = Repository(repo_path)
- return repo
-
-
-def delete_repo(repo):
+ return Repository(repo_path)
+
+
+def delete_repo(repo_path):
"""Permanently delete a git repository from repo store."""
- shutil.rmtree(repo)
+ shutil.rmtree(repo_path)
def get_refs(repo_path):
=== modified file 'turnip/api/tests/test_api.py'
--- turnip/api/tests/test_api.py 2015-03-13 03:24:22 +0000
+++ turnip/api/tests/test_api.py 2015-03-24 21:30:45 +0000
@@ -14,7 +14,11 @@
from webtest import TestApp
from turnip import api
-from turnip.api.tests.test_helpers import RepoFactory
+from turnip.api.tests.test_helpers import (
+ get_revlist,
+ open_repo,
+ RepoFactory,
+ )
class ApiTestCase(TestCase):
@@ -24,8 +28,9 @@
repo_store = self.useFixture(TempDir()).path
self.useFixture(EnvironmentVariable("REPO_STORE", repo_store))
self.app = TestApp(api.main({}))
- self.repo_path = str(uuid.uuid1())
+ self.repo_path = uuid.uuid1().hex
self.repo_store = os.path.join(repo_store, self.repo_path)
+ self.repo_root = repo_store
self.commit = {'ref': 'refs/heads/master', 'message': 'test commit.'}
self.tag = {'ref': 'refs/tags/tag0', 'message': 'tag message'}
@@ -33,9 +38,31 @@
resp = self.app.get('/repo/{}/{}'.format(self.repo_path, ref))
return resp.json
- def test_repo_create(self):
+ def test_repo_init(self):
resp = self.app.post_json('/repo', {'repo_path': self.repo_path})
- self.assertEqual(resp.status_code, 200)
+ self.assertIn(self.repo_path, resp.json['repo_url'])
+ self.assertEqual(resp.status_code, 200)
+
+ def test_repo_init_with_invalid_repo_path(self):
+ resp = self.app.post_json('/repo', {'repo_path': '../1234'},
+ expect_errors=True)
+ self.assertEqual(resp.status_code, 500)
+
+ def test_repo_init_with_clone(self):
+ """Repo can be initialised with optional clone."""
+ factory = RepoFactory(self.repo_store, num_commits=2)
+ factory.build()
+ new_repo_path = uuid.uuid1().hex
+ resp = self.app.post_json('/repo', {'repo_path': self.repo_path,
+ 'clone_path': new_repo_path})
+ repo1_revlist = get_revlist(factory.repo)
+ clone_path = resp.json['repo_url'].split('/')[-1]
+ repo2 = open_repo(os.path.join(self.repo_root, clone_path))
+ repo2_revlist = get_revlist(repo2)
+
+ self.assertEqual(repo1_revlist, repo2_revlist)
+ self.assertEqual(resp.status_code, 200)
+ self.assertIn(new_repo_path, resp.json['repo_url'])
def test_repo_delete(self):
self.app.post_json('/repo', {'repo_path': self.repo_path})
=== modified file 'turnip/api/tests/test_helpers.py'
--- turnip/api/tests/test_helpers.py 2015-03-13 03:44:09 +0000
+++ turnip/api/tests/test_helpers.py 2015-03-24 21:30:45 +0000
@@ -8,10 +8,21 @@
GIT_OBJ_COMMIT,
GIT_SORT_TIME,
IndexEntry,
+ Repository,
Signature,
)
+def get_revlist(repo):
+ """Return revlist for a given pygit2 repo object."""
+ return [commit.oid.hex for commit in repo.walk(repo.head.target)]
+
+
+def open_repo(repo_path):
+ """Return a pygit2 repo object for a given path."""
+ return Repository(repo_path)
+
+
class RepoFactory():
"""Builds a git repository in a user defined state."""
=== modified file 'turnip/api/views.py'
--- turnip/api/views.py 2015-03-13 03:44:09 +0000
+++ turnip/api/views.py 2015-03-24 21:30:45 +0000
@@ -11,6 +11,11 @@
from turnip.api import store
+def is_valid_path(repo_store, repo_path):
+ """Ensure path in within repo root and has not been subverted."""
+ return os.path.realpath(repo_path).startswith(os.path.realpath(repo_store))
+
+
def repo_path(func):
"""Decorator builds repo path from request name and repo_store."""
def repo_path_decorator(self):
@@ -19,10 +24,10 @@
self.request.errors.add('body', 'name', 'repo name is missing')
return
repo_path = os.path.join(self.repo_store, name)
- if not os.path.realpath(repo_path).startswith(
- os.path.realpath(self.repo_store)):
+ if not is_valid_path(self.repo_store, repo_path):
self.request.errors.add('body', 'name', 'invalid path.')
raise exc.HTTPInternalServerError()
+
return func(self, repo_path)
return repo_path_decorator
@@ -42,15 +47,28 @@
self.request = request
def collection_post(self):
- """Initialise a new git repository."""
+ """Initialise a new git repository, or clone from an existing repo."""
repo_path = extract_json_data(self.request).get('repo_path')
+ clone_path = extract_json_data(self.request).get('clone_path')
+
if not repo_path:
self.request.errors.add('body', 'repo_path',
'repo_path is missing')
return
repo = os.path.join(self.repo_store, repo_path)
+ if not is_valid_path(self.repo_store, repo):
+ self.request.errors.add('body', 'name', 'invalid path.')
+ raise exc.HTTPInternalServerError()
+
+ if clone_path:
+ repo_clone = os.path.join(self.repo_store, clone_path)
+ else:
+ repo_clone = None
+
try:
- store.init_repo(repo)
+ new_repo_path = store.init_repo(repo, repo_clone)
+ repo_name = os.path.basename(os.path.normpath(new_repo_path))
+ return {'repo_url': '/'.join([self.request.url, repo_name])}
except GitError:
return exc.HTTPConflict() # 409
Follow ups