Skip to content

Commit

Permalink
Add support for refish & dwim reference lookups
Browse files Browse the repository at this point in the history
- Repository.lookup_reference_dwim('master') uses shorthand branch names, remote branches, tags to find references
- Repository.resolve_refish('master') additionally supports HEAD/HEAD~1/etc, and returns a (commit,reference) pair.
  • Loading branch information
rcoup committed Jul 1, 2019
1 parent 231a8e1 commit fd9d9d3
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 1 deletion.
25 changes: 25 additions & 0 deletions pygit2/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
from _pygit2 import GIT_BRANCH_LOCAL, GIT_BRANCH_REMOTE, GIT_BRANCH_ALL
from _pygit2 import GIT_REF_SYMBOLIC
from _pygit2 import Reference, Tree, Commit, Blob
from _pygit2 import InvalidSpecError

from .config import Config
from .errors import check_error
Expand Down Expand Up @@ -201,6 +202,30 @@ def create_reference(self, name, target, force=False):

return self.create_reference_symbolic(name, target, force)

def resolve_refish(self, refish):
"""Convert a reference-like short name "ref-ish" to a valid
(commit, reference) pair.
If ref-ish points to a commit, the reference element of the result
will be None.
Examples::
repo.resolve_refish('mybranch')
repo.resolve_refish('sometag')
repo.resolve_refish('origin/master')
repo.resolve_refish('bbb78a9')
"""
try:
reference = self.lookup_reference_dwim(refish)
except (KeyError, InvalidSpecError):
reference = None
commit = self.revparse_single(refish)
else:
commit = reference.peel(Commit)

return (commit, reference)

#
# Checkout
#
Expand Down
31 changes: 31 additions & 0 deletions src/repository.c
Original file line number Diff line number Diff line change
Expand Up @@ -1455,6 +1455,36 @@ Repository_lookup_reference(Repository *self, PyObject *py_name)
return wrap_reference(c_reference, self);
}

PyDoc_STRVAR(Repository_lookup_reference_dwim__doc__,
"lookup_reference_dwim(name) -> Reference\n"
"\n"
"Lookup a reference by doing-what-i-mean'ing its short name.");

PyObject *
Repository_lookup_reference_dwim(Repository *self, PyObject *py_name)
{
git_reference *c_reference;
char *c_name;
int err;

/* 1- Get the C name */
c_name = py_path_to_c_str(py_name);
if (c_name == NULL)
return NULL;

/* 2- Lookup */
err = git_reference_dwim(&c_reference, self->repo, c_name);
if (err < 0) {
PyObject *err_obj = Error_set_str(err, c_name);
free(c_name);
return err_obj;
}
free(c_name);

/* 3- Make an instance of Reference and return it */
return wrap_reference(c_reference, self);
}

PyDoc_STRVAR(Repository_create_reference_direct__doc__,
"create_reference_direct(name, target, force)\n"
"\n"
Expand Down Expand Up @@ -1991,6 +2021,7 @@ PyMethodDef Repository_methods[] = {
METHOD(Repository, listall_submodules, METH_NOARGS),
METHOD(Repository, init_submodules, METH_VARARGS | METH_KEYWORDS),
METHOD(Repository, lookup_reference, METH_O),
METHOD(Repository, lookup_reference_dwim, METH_O),
METHOD(Repository, revparse_single, METH_O),
METHOD(Repository, status, METH_NOARGS),
METHOD(Repository, status_file, METH_O),
Expand Down
69 changes: 68 additions & 1 deletion test/test_refs.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,73 @@ def test_lookup_reference(self):
reference = repo.lookup_reference('refs/heads/master')
assert reference.name == 'refs/heads/master'

def test_lookup_reference_dwim(self):
repo = self.repo

# remote ref
reference = self.repo.create_reference('refs/remotes/origin/master', LAST_COMMIT)
assert reference.shorthand == 'origin/master'
# tag
repo.create_reference('refs/tags/version1', LAST_COMMIT)

# Test dwim lookups

# Raise KeyError ?
with pytest.raises(KeyError): repo.lookup_reference_dwim('foo')
with pytest.raises(KeyError): repo.lookup_reference_dwim('refs/foo')

reference = repo.lookup_reference_dwim('refs/heads/master')
assert reference.name == 'refs/heads/master'

reference = repo.lookup_reference_dwim('master')
assert reference.name == 'refs/heads/master'

reference = repo.lookup_reference_dwim('origin/master')
assert reference.name == 'refs/remotes/origin/master'

reference = repo.lookup_reference_dwim('version1')
assert reference.name == 'refs/tags/version1'

def test_resolve_refish(self):
repo = self.repo

# remote ref
reference = self.repo.create_reference('refs/remotes/origin/master', LAST_COMMIT)
assert reference.shorthand == 'origin/master'
# tag
repo.create_reference('refs/tags/version1', LAST_COMMIT)

# Test dwim lookups

# Raise KeyError ?
with pytest.raises(KeyError): repo.resolve_refish('foo')
with pytest.raises(KeyError): repo.resolve_refish('refs/foo')

commit, ref = repo.resolve_refish('refs/heads/i18n')
assert ref.name == 'refs/heads/i18n'
assert commit.hex == '5470a671a80ac3789f1a6a8cefbcf43ce7af0563'

commit, ref = repo.resolve_refish('master')
assert ref.name == 'refs/heads/master'
assert commit.hex == LAST_COMMIT

commit, ref = repo.resolve_refish('origin/master')
assert ref.name == 'refs/remotes/origin/master'
assert commit.hex == LAST_COMMIT

commit, ref = repo.resolve_refish('version1')
assert ref.name == 'refs/tags/version1'
assert commit.hex == LAST_COMMIT

commit, ref = repo.resolve_refish(LAST_COMMIT)
assert ref is None
assert commit.hex == LAST_COMMIT

commit, ref = repo.resolve_refish('HEAD~1')
assert ref is None
assert commit.hex == '5ebeeebb320790caf276b9fc8b24546d63316533'


def test_reference_get_sha(self):
reference = self.repo.lookup_reference('refs/heads/master')
assert reference.target.hex == LAST_COMMIT
Expand Down Expand Up @@ -390,7 +457,7 @@ def test_create_reference(self):
with pytest.raises(AlreadyExistsError) as error:
self.repo.create_reference('refs/tags/version1', LAST_COMMIT)
assert isinstance(error.value, ValueError)

# Clear error
del error

Expand Down

0 comments on commit fd9d9d3

Please sign in to comment.