From bbaff28dd0522423990c98a5455b55c0891cf425 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Thu, 6 Sep 2018 15:18:07 -0700 Subject: [PATCH 1/5] Add support for git_graph_descendant_of Added as Repository.descendant_of --- src/repository.c | 30 ++++++++++++++++++++++++++++++ test/test_repository.py | 14 ++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/repository.c b/src/repository.c index 484772adb..7d697a3f1 100644 --- a/src/repository.c +++ b/src/repository.c @@ -564,6 +564,35 @@ Repository_workdir__set__(Repository *self, PyObject *py_workdir) return 0; } +PyDoc_STRVAR(Repository_descendant_of__doc__, + "descendant_of(oid, oid) -> bool\n" + "\n" + "Determine if the second commit is a descendant of the first commit.\n" + "Note that a commit is not considered a descendant of itself."); + +PyObject * +Repository_descendant_of(Repository *self, PyObject *args) +{ + PyObject *value1; + PyObject *value2; + git_oid oid1; + git_oid oid2; + int err; + + if (!PyArg_ParseTuple(args, "OO", &value1, &value2)) + return NULL; + + err = py_oid_to_git_oid_expand(self->repo, value1, &oid1); + if (err < 0) + return NULL; + + err = py_oid_to_git_oid_expand(self->repo, value2, &oid2); + if (err < 0) + return NULL; + + return PyBool_FromLong(git_graph_descendant_of(self->repo, &oid1, &oid2)); +} + PyDoc_STRVAR(Repository_merge_base__doc__, "merge_base(oid, oid) -> Oid\n" "\n" @@ -1884,6 +1913,7 @@ PyMethodDef Repository_methods[] = { METHOD(Repository, create_tag, METH_VARARGS), METHOD(Repository, TreeBuilder, METH_VARARGS), METHOD(Repository, walk, METH_VARARGS), + METHOD(Repository, descendant_of, METH_VARARGS), METHOD(Repository, merge_base, METH_VARARGS), METHOD(Repository, merge_analysis, METH_O), METHOD(Repository, merge, METH_O), diff --git a/test/test_repository.py b/test/test_repository.py index ff62ce45b..5d527f45c 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -348,6 +348,20 @@ def test_merge_base(self): self.assertEqual(None, self.repo.merge_base(indep, commit)) + def test_descendent_of(self): + self.assertFalse(self.repo.descendant_of( + '5ebeeebb320790caf276b9fc8b24546d63316533', + '4ec4389a8068641da2d6578db0419484972284c8')) + self.assertFalse(self.repo.descendant_of( + '5ebeeebb320790caf276b9fc8b24546d63316533', + '5ebeeebb320790caf276b9fc8b24546d63316533')) + self.assertTrue(self.repo.descendant_of( + '5ebeeebb320790caf276b9fc8b24546d63316533', + 'acecd5ea2924a4b900e7e149496e1f4b57976e51')) + self.assertFalse(self.repo.descendant_of( + 'acecd5ea2924a4b900e7e149496e1f4b57976e51', + '5ebeeebb320790caf276b9fc8b24546d63316533')) + def test_ahead_behind(self): ahead, behind = self.repo.ahead_behind('5ebeeebb320790caf276b9fc8b24546d63316533', '4ec4389a8068641da2d6578db0419484972284c8') From b263b1637fc397a64379c008c6200c3986270f93 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Fri, 7 Sep 2018 11:51:00 -0700 Subject: [PATCH 2/5] Add docs --- docs/repository.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/repository.rst b/docs/repository.rst index 26e64ba41..41f384e2f 100644 --- a/docs/repository.rst +++ b/docs/repository.rst @@ -68,6 +68,7 @@ Below there are some general attributes and methods: .. automethod:: pygit2.Repository.read .. automethod:: pygit2.Repository.write .. automethod:: pygit2.Repository.ahead_behind +.. automethod:: pygit2.Repository.descendant_of .. automethod:: pygit2.Repository.create_reference .. automethod:: pygit2.Repository.describe .. automethod:: pygit2.Repository.path_is_ignored From 3fd46e29455b02606856b20e5db6c42f27706146 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Fri, 7 Sep 2018 11:51:55 -0700 Subject: [PATCH 3/5] Add sketch of Branches.with_commit() --- pygit2/repository.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/pygit2/repository.py b/pygit2/repository.py index 5e2af99f3..454831ff8 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -1130,13 +1130,14 @@ def revert_commit(self, revert_commit, our_commit, mainline=0): class Branches(object): - def __init__(self, repository, flag=GIT_BRANCH_ALL): + def __init__(self, repository, flag=GIT_BRANCH_ALL, commit=None): self._repository = repository self._flag = flag + self._commit = commit if flag == GIT_BRANCH_ALL: - self.local = Branches(repository, flag=GIT_BRANCH_LOCAL) - self.remote = Branches(repository, flag=GIT_BRANCH_REMOTE) + self.local = Branches(repository, flag=GIT_BRANCH_LOCAL, commit=commit) + self.remote = Branches(repository, flag=GIT_BRANCH_REMOTE, commit=commit) def __getitem__(self, name): branch = None @@ -1146,7 +1147,7 @@ def __getitem__(self, name): if branch is None and self._flag & GIT_BRANCH_REMOTE: branch = self._repository.lookup_branch(name, GIT_BRANCH_REMOTE) - if branch is None: + if branch is None or not self._valid(branch): raise KeyError('Branch not found: {}'.format(name)) return branch @@ -1159,7 +1160,8 @@ def get(self, key): def __iter__(self): for branch_name in self._repository.listall_branches(self._flag): - yield branch_name + if self._valid(self[branch_name]): + yield branch_name def create(self, name, commit, force=False): return self._repository.create_branch(name, commit, force) @@ -1167,6 +1169,14 @@ def create(self, name, commit, force=False): def delete(self, name): self[name].delete() + def _valid(self, branch): + return (self._commit is None or branch.target == self._commit or + self._repository.descendant_of(branch.target, self._commit)) + + def with_commit(self, commit): + assert self._commit is None + return Branches(self._repository, self._flag, commit) + def __contains__(self, name): return self.get(name) is not None From b4dbf84e8d1321fdad1f30d1c42ca4408b46548e Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Fri, 7 Sep 2018 14:11:43 -0700 Subject: [PATCH 4/5] Skip branch lookup when commit is None --- pygit2/repository.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygit2/repository.py b/pygit2/repository.py index 454831ff8..2ce24fd9a 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -1160,7 +1160,7 @@ def get(self, key): def __iter__(self): for branch_name in self._repository.listall_branches(self._flag): - if self._valid(self[branch_name]): + if self._commit is None or self._valid(self[branch_name]): yield branch_name def create(self, name, commit, force=False): From 2b852c7237cdc7832a44cf478c8ca411b4b2aa7e Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Fri, 14 Sep 2018 09:46:28 -0700 Subject: [PATCH 5/5] Add tests for Branches.with_commit and address issues --- pygit2/repository.py | 7 ++++++- test/test_branch.py | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/pygit2/repository.py b/pygit2/repository.py index 2ce24fd9a..16301d380 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -1133,6 +1133,11 @@ class Branches(object): def __init__(self, repository, flag=GIT_BRANCH_ALL, commit=None): self._repository = repository self._flag = flag + if commit is not None: + if isinstance(commit, Commit): + commit = commit.id + elif not isinstance(commit, Oid): + commit = self._repository.expand_id(commit) self._commit = commit if flag == GIT_BRANCH_ALL: @@ -1160,7 +1165,7 @@ def get(self, key): def __iter__(self): for branch_name in self._repository.listall_branches(self._flag): - if self._commit is None or self._valid(self[branch_name]): + if self._commit is None or self.get(branch_name) is not None: yield branch_name def create(self, name, commit, force=False): diff --git a/test/test_branch.py b/test/test_branch.py index 508336fe6..8c1770c53 100644 --- a/test/test_branch.py +++ b/test/test_branch.py @@ -37,6 +37,8 @@ LAST_COMMIT = '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98' I18N_LAST_COMMIT = '5470a671a80ac3789f1a6a8cefbcf43ce7af0563' ORIGIN_MASTER_COMMIT = '784855caf26449a1914d2cf62d12b9374d76ae78' +EXCLUSIVE_MASTER_COMMIT = '5ebeeebb320790caf276b9fc8b24546d63316533' +SHARED_COMMIT = '4ec4389a8068641da2d6578db0419484972284c8' class BranchesObjectTestCase(utils.RepoTestCase): @@ -116,6 +118,24 @@ def test_branch_name(self): self.assertEqual(branch.branch_name, 'i18n') self.assertEqual(branch.name, 'refs/heads/i18n') + def test_with_commit(self): + branches = self.repo.branches.with_commit(EXCLUSIVE_MASTER_COMMIT) + self.assertEqual(sorted(branches), ['master']) + self.assertTrue(branches.get('i18n') is None) + self.assertEqual(branches['master'].branch_name, 'master') + + branches = self.repo.branches.with_commit(SHARED_COMMIT) + self.assertEqual(sorted(branches), ['i18n', 'master']) + + branches = self.repo.branches.with_commit(LAST_COMMIT) + self.assertEqual(sorted(branches), ['master']) + + branches = self.repo.branches.with_commit(self.repo[LAST_COMMIT]) + self.assertEqual(sorted(branches), ['master']) + + branches = self.repo.branches.remote.with_commit(LAST_COMMIT) + self.assertEqual(sorted(branches), []) + class BranchesObjectEmptyRepoTestCase(utils.EmptyRepoTestCase): def setUp(self):