Skip to content

Commit fd9d9d3

Browse files
committed
Add support for refish & dwim reference lookups
- 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.
1 parent 231a8e1 commit fd9d9d3

File tree

3 files changed

+124
-1
lines changed

3 files changed

+124
-1
lines changed

pygit2/repository.py

+25
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
from _pygit2 import GIT_BRANCH_LOCAL, GIT_BRANCH_REMOTE, GIT_BRANCH_ALL
4949
from _pygit2 import GIT_REF_SYMBOLIC
5050
from _pygit2 import Reference, Tree, Commit, Blob
51+
from _pygit2 import InvalidSpecError
5152

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

202203
return self.create_reference_symbolic(name, target, force)
203204

205+
def resolve_refish(self, refish):
206+
"""Convert a reference-like short name "ref-ish" to a valid
207+
(commit, reference) pair.
208+
209+
If ref-ish points to a commit, the reference element of the result
210+
will be None.
211+
212+
Examples::
213+
214+
repo.resolve_refish('mybranch')
215+
repo.resolve_refish('sometag')
216+
repo.resolve_refish('origin/master')
217+
repo.resolve_refish('bbb78a9')
218+
"""
219+
try:
220+
reference = self.lookup_reference_dwim(refish)
221+
except (KeyError, InvalidSpecError):
222+
reference = None
223+
commit = self.revparse_single(refish)
224+
else:
225+
commit = reference.peel(Commit)
226+
227+
return (commit, reference)
228+
204229
#
205230
# Checkout
206231
#

src/repository.c

+31
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,36 @@ Repository_lookup_reference(Repository *self, PyObject *py_name)
14551455
return wrap_reference(c_reference, self);
14561456
}
14571457

1458+
PyDoc_STRVAR(Repository_lookup_reference_dwim__doc__,
1459+
"lookup_reference_dwim(name) -> Reference\n"
1460+
"\n"
1461+
"Lookup a reference by doing-what-i-mean'ing its short name.");
1462+
1463+
PyObject *
1464+
Repository_lookup_reference_dwim(Repository *self, PyObject *py_name)
1465+
{
1466+
git_reference *c_reference;
1467+
char *c_name;
1468+
int err;
1469+
1470+
/* 1- Get the C name */
1471+
c_name = py_path_to_c_str(py_name);
1472+
if (c_name == NULL)
1473+
return NULL;
1474+
1475+
/* 2- Lookup */
1476+
err = git_reference_dwim(&c_reference, self->repo, c_name);
1477+
if (err < 0) {
1478+
PyObject *err_obj = Error_set_str(err, c_name);
1479+
free(c_name);
1480+
return err_obj;
1481+
}
1482+
free(c_name);
1483+
1484+
/* 3- Make an instance of Reference and return it */
1485+
return wrap_reference(c_reference, self);
1486+
}
1487+
14581488
PyDoc_STRVAR(Repository_create_reference_direct__doc__,
14591489
"create_reference_direct(name, target, force)\n"
14601490
"\n"
@@ -1991,6 +2021,7 @@ PyMethodDef Repository_methods[] = {
19912021
METHOD(Repository, listall_submodules, METH_NOARGS),
19922022
METHOD(Repository, init_submodules, METH_VARARGS | METH_KEYWORDS),
19932023
METHOD(Repository, lookup_reference, METH_O),
2024+
METHOD(Repository, lookup_reference_dwim, METH_O),
19942025
METHOD(Repository, revparse_single, METH_O),
19952026
METHOD(Repository, status, METH_NOARGS),
19962027
METHOD(Repository, status_file, METH_O),

test/test_refs.py

+68-1
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,73 @@ def test_lookup_reference(self):
278278
reference = repo.lookup_reference('refs/heads/master')
279279
assert reference.name == 'refs/heads/master'
280280

281+
def test_lookup_reference_dwim(self):
282+
repo = self.repo
283+
284+
# remote ref
285+
reference = self.repo.create_reference('refs/remotes/origin/master', LAST_COMMIT)
286+
assert reference.shorthand == 'origin/master'
287+
# tag
288+
repo.create_reference('refs/tags/version1', LAST_COMMIT)
289+
290+
# Test dwim lookups
291+
292+
# Raise KeyError ?
293+
with pytest.raises(KeyError): repo.lookup_reference_dwim('foo')
294+
with pytest.raises(KeyError): repo.lookup_reference_dwim('refs/foo')
295+
296+
reference = repo.lookup_reference_dwim('refs/heads/master')
297+
assert reference.name == 'refs/heads/master'
298+
299+
reference = repo.lookup_reference_dwim('master')
300+
assert reference.name == 'refs/heads/master'
301+
302+
reference = repo.lookup_reference_dwim('origin/master')
303+
assert reference.name == 'refs/remotes/origin/master'
304+
305+
reference = repo.lookup_reference_dwim('version1')
306+
assert reference.name == 'refs/tags/version1'
307+
308+
def test_resolve_refish(self):
309+
repo = self.repo
310+
311+
# remote ref
312+
reference = self.repo.create_reference('refs/remotes/origin/master', LAST_COMMIT)
313+
assert reference.shorthand == 'origin/master'
314+
# tag
315+
repo.create_reference('refs/tags/version1', LAST_COMMIT)
316+
317+
# Test dwim lookups
318+
319+
# Raise KeyError ?
320+
with pytest.raises(KeyError): repo.resolve_refish('foo')
321+
with pytest.raises(KeyError): repo.resolve_refish('refs/foo')
322+
323+
commit, ref = repo.resolve_refish('refs/heads/i18n')
324+
assert ref.name == 'refs/heads/i18n'
325+
assert commit.hex == '5470a671a80ac3789f1a6a8cefbcf43ce7af0563'
326+
327+
commit, ref = repo.resolve_refish('master')
328+
assert ref.name == 'refs/heads/master'
329+
assert commit.hex == LAST_COMMIT
330+
331+
commit, ref = repo.resolve_refish('origin/master')
332+
assert ref.name == 'refs/remotes/origin/master'
333+
assert commit.hex == LAST_COMMIT
334+
335+
commit, ref = repo.resolve_refish('version1')
336+
assert ref.name == 'refs/tags/version1'
337+
assert commit.hex == LAST_COMMIT
338+
339+
commit, ref = repo.resolve_refish(LAST_COMMIT)
340+
assert ref is None
341+
assert commit.hex == LAST_COMMIT
342+
343+
commit, ref = repo.resolve_refish('HEAD~1')
344+
assert ref is None
345+
assert commit.hex == '5ebeeebb320790caf276b9fc8b24546d63316533'
346+
347+
281348
def test_reference_get_sha(self):
282349
reference = self.repo.lookup_reference('refs/heads/master')
283350
assert reference.target.hex == LAST_COMMIT
@@ -390,7 +457,7 @@ def test_create_reference(self):
390457
with pytest.raises(AlreadyExistsError) as error:
391458
self.repo.create_reference('refs/tags/version1', LAST_COMMIT)
392459
assert isinstance(error.value, ValueError)
393-
460+
394461
# Clear error
395462
del error
396463

0 commit comments

Comments
 (0)