diff --git a/docs/repository.rst b/docs/repository.rst index ce8af39..5f5850a 100644 --- a/docs/repository.rst +++ b/docs/repository.rst @@ -69,4 +69,8 @@ Below there are some general attributes and methods: .. automethod:: pygit2.Repository.write .. automethod:: pygit2.Repository.reset .. automethod:: pygit2.Repository.state_cleanup +<<<<<<< HEAD .. automethod:: pygit2.Repository.write_archive +======= +.. automethod:: pygit2.Repository.ahead_behind +>>>>>>> Add Repsitory.ahead_behind() diff --git a/pygit2/decl.h b/pygit2/decl.h index c628e66..5c0dda4 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -535,6 +535,7 @@ int git_repository_init_ext( int git_repository_set_head(git_repository *repo, const char *refname, const git_signature *signature, const char *log_message); int git_repository_set_head_detached(git_repository *repo, const git_oid *commitish, const git_signature *signature, const char *log_message); +int git_graph_ahead_behind(size_t *ahead, size_t *behind, git_repository *repo, const git_oid *local, const git_oid *upstream); /* * git_index diff --git a/pygit2/repository.py b/pygit2/repository.py index 3dd14f1..064ebb9 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -625,3 +625,41 @@ class Repository(_Repository): info.mode = 0o777 # symlinks get placeholder archive.addfile(info, StringIO(content)) + + # + # Ahead-behind, which mostly lives on its own namespace + # + def ahead_behind(self, local, upstream): + """ahead_behind(local, upstream) -> (int, int) + + Calculate how many different commits are in the non-common parts + of the history between the two given ids. + + Ahead is how many commits are in the ancestry of the 'local' + commit which are not in the 'upstream' commit. Behind is the + opposite. + + Arguments + + local + The commit which is considered the local or current state + upstream + The commit which is considered the upstream + + Returns a tuple with the number of commits ahead and behind respectively. + """ + + if not isinstance(local, Oid): + local = self.expand_id(local) + + if not isinstance(upstream, Oid): + upstream = self.expand_id(upstream) + + ahead, behind = ffi.new('size_t*'), ffi.new('size_t*') + oid1, oid2 = ffi.new('git_oid *'), ffi.new('git_oid *') + ffi.buffer(oid1)[:] = local.raw[:] + ffi.buffer(oid2)[:] = upstream.raw[:] + err = C.git_graph_ahead_behind(ahead, behind, self._repo, oid1, oid2) + check_error(err) + + return int(ahead[0]), int(behind[0]) diff --git a/src/repository.c b/src/repository.c index 9eabbb9..ba71030 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1433,6 +1433,24 @@ Repository_reset(Repository *self, PyObject* args) Py_RETURN_NONE; } +PyDoc_STRVAR(Repository_expand_id__doc__, + "expand_id(hex) -> Oid\n" + "\n" + "Expand a string into a full Oid according to the objects in this repsitory.\n"); + +PyObject * +Repository_expand_id(Repository *self, PyObject *py_hex) +{ + git_oid oid; + int err; + + err = py_oid_to_git_oid_expand(self->repo, py_hex, &oid); + if (err < 0) + return NULL; + + return git_oid_to_python(&oid); +} + PyMethodDef Repository_methods[] = { METHOD(Repository, create_blob, METH_VARARGS), METHOD(Repository, create_blob_fromworkdir, METH_VARARGS), @@ -1461,6 +1479,7 @@ PyMethodDef Repository_methods[] = { METHOD(Repository, listall_branches, METH_VARARGS), METHOD(Repository, create_branch, METH_VARARGS), METHOD(Repository, reset, METH_VARARGS), + METHOD(Repository, expand_id, METH_O), METHOD(Repository, _from_c, METH_VARARGS), METHOD(Repository, _disown, METH_NOARGS), {NULL} diff --git a/test/data/testrepo b/test/data/testrepo new file mode 160000 index 0000000..2be5719 --- /dev/null +++ b/test/data/testrepo @@ -0,0 +1 @@ +Subproject commit 2be5719152d4f82c7302b1c0932d8e5f0a4a0e98 diff --git a/test/test_repository.py b/test/test_repository.py index 7640f39..88fbb04 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -156,6 +156,11 @@ class RepositoryTest(utils.BareRepoTestCase): commit.message) self.assertRaises(ValueError, self.repo.__getitem__, too_short_prefix) + def test_expand_id(self): + commit_sha = '5fe808e8953c12735680c257f56600cb0de44b10' + expanded = self.repo.expand_id(commit_sha[:7]) + self.assertEqual(commit_sha, expanded.hex) + @unittest.skipIf(__pypy__ is not None, "skip refcounts checks in pypy") def test_lookup_commit_refcount(self): start = sys.getrefcount(self.repo) @@ -295,6 +300,17 @@ class RepositoryTest_II(utils.RepoTestCase): self.assertEqual(commit.hex, 'acecd5ea2924a4b900e7e149496e1f4b57976e51') + def test_ahead_behind(self): + ahead, behind = self.repo.ahead_behind('5ebeeebb320790caf276b9fc8b24546d63316533', + '4ec4389a8068641da2d6578db0419484972284c8') + self.assertEqual(1, ahead) + self.assertEqual(2, behind) + + ahead, behind = self.repo.ahead_behind('4ec4389a8068641da2d6578db0419484972284c8', + '5ebeeebb320790caf276b9fc8b24546d63316533') + self.assertEqual(2, ahead) + self.assertEqual(1, behind) + def test_reset_hard(self): ref = "5ebeeebb320790caf276b9fc8b24546d63316533" with open(os.path.join(self.repo.workdir, "hello.txt")) as f: