Skip to content

Add support for ref-ish and do-what-i-mean reference lookups #923

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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