diff --git a/dulwich/objectspec.py b/dulwich/objectspec.py index ebe63bce..630ca122 100644 --- a/dulwich/objectspec.py +++ b/dulwich/objectspec.py @@ -1,63 +1,136 @@ # objectspec.py -- Object specification # Copyright (C) 2014 Jelmer Vernooij # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; version 2 # of the License or (at your option) a later version of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. """Object specification.""" +def to_bytes(text): + if getattr(text, "encode", None) is not None: + text = text.encode('ascii') + return text + + def parse_object(repo, objectish): """Parse a string referring to an object. :param repo: A `Repo` object :param objectish: A string referring to an object :return: A git object :raise KeyError: If the object can not be found """ - if getattr(objectish, "encode", None) is not None: - objectish = objectish.encode('ascii') + objectish = to_bytes(objectish) return repo[objectish] -def parse_refspec(container, refspec): - """Parse a string referring to an reference. +def parse_ref(container, refspec): + """Parse a string referring to a reference. :param container: A RefsContainer object :param refspec: A string referring to a ref :return: A ref :raise KeyError: If the ref can not be found """ - if getattr(refspec, "encode", None) is not None: - refspec = refspec.encode('ascii') + refspec = to_bytes(refspec) for ref in [refspec, b"refs/heads/" + refspec]: if ref in container: return ref else: raise KeyError(refspec) +def parse_reftuple(lh_container, rh_container, refspec): + """Parse a reftuple spec. + + :param lh_container: A RefsContainer object + :param hh_container: A RefsContainer object + :param refspec: A string + :return: A tuple with left and right ref + :raise KeyError: If one of the refs can not be found + """ + if refspec.startswith("+"): + force = True + refspec = refspec[1:] + else: + force = False + refspec = to_bytes(refspec) + if b":" in refspec: + (lh, rh) = refspec.split(b":") + else: + lh = rh = refspec + if rh == "": + lh = None + else: + lh = parse_ref(lh_container, lh) + if rh == "": + rh = None + else: + try: + rh = parse_ref(rh_container, rh) + except KeyError: + # TODO: check force? + if not b"/" in rh: + rh = b"refs/heads/" + rh + return (lh, rh, force) + + +def parse_reftuples(lh_container, rh_container, refspecs): + """Parse a list of reftuple specs to a list of reftuples. + + :param lh_container: A RefsContainer object + :param hh_container: A RefsContainer object + :param refspecs: A list of refspecs or a string + :return: A list of refs + :raise KeyError: If one of the refs can not be found + """ + if not isinstance(refspecs, list): + refspecs = [refspecs] + ret = [] + # TODO: Support * in refspecs + for refspec in refspecs: + ret.append(parse_reftuple(lh_container, rh_container, refspec)) + return ret + + +def parse_refs(container, refspecs): + """Parse a list of refspecs to a list of refs. + + :param container: A RefsContainer object + :param refspecs: A list of refspecs or a string + :return: A list of refs + :raise KeyError: If one of the refs can not be found + """ + # TODO: Support * in refspecs + if not isinstance(refspecs, list): + refspecs = [refspecs] + ret = [] + for refspec in refspecs: + ret.append(parse_ref(container, refspec)) + return ret + + def parse_commit_range(repo, committishs): """Parse a string referring to a range of commits. :param repo: A `Repo` object :param committishs: A string referring to a range of commits. :return: An iterator over `Commit` objects :raise KeyError: When the reference commits can not be found :raise ValueError: If the range can not be parsed """ - if getattr(committishs, "encode", None) is not None: - committishs = committishs.encode('ascii') + committishs = to_bytes(committishs) return iter([repo[committishs]]) diff --git a/dulwich/tests/test_objectspec.py b/dulwich/tests/test_objectspec.py index 11e5c3a0..13e063ab 100644 --- a/dulwich/tests/test_objectspec.py +++ b/dulwich/tests/test_objectspec.py @@ -1,82 +1,139 @@ # test_objectspec.py -- tests for objectspec.py # Copyright (C) 2014 Jelmer Vernooij # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; version 2 # of the License or (at your option) any later version of # the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. """Tests for revision spec parsing.""" # TODO: Round-trip parse-serialize-parse and serialize-parse-serialize tests. from dulwich.objects import ( Blob, ) from dulwich.objectspec import ( parse_object, parse_commit_range, - parse_refspec, + parse_ref, + parse_refs, + parse_reftuple, + parse_reftuples, ) from dulwich.repo import MemoryRepo from dulwich.tests import ( TestCase, ) from dulwich.tests.utils import ( build_commit_graph, ) class ParseObjectTests(TestCase): """Test parse_object.""" def test_nonexistent(self): r = MemoryRepo() self.assertRaises(KeyError, parse_object, r, "thisdoesnotexist") def test_blob_by_sha(self): r = MemoryRepo() b = Blob.from_string(b"Blah") r.object_store.add_object(b) self.assertEqual(b, parse_object(r, b.id)) class ParseCommitRangeTests(TestCase): """Test parse_commit_range.""" def test_nonexistent(self): r = MemoryRepo() self.assertRaises(KeyError, parse_commit_range, r, "thisdoesnotexist") def test_commit_by_sha(self): r = MemoryRepo() c1, c2, c3 = build_commit_graph(r.object_store, [[1], [2, 1], [3, 1, 2]]) self.assertEqual([c1], list(parse_commit_range(r, c1.id))) -class ParseRefspecTests(TestCase): +class ParseRefTests(TestCase): def test_nonexistent(self): r = {} - self.assertRaises(KeyError, parse_refspec, r, "thisdoesnotexist") + self.assertRaises(KeyError, parse_ref, r, "thisdoesnotexist") def test_head(self): r = {"refs/heads/foo": "bla"} - self.assertEquals("refs/heads/foo", parse_refspec(r, "foo")) + self.assertEquals("refs/heads/foo", parse_ref(r, "foo")) def test_full(self): r = {"refs/heads/foo": "bla"} - self.assertEquals("refs/heads/foo", parse_refspec(r, "refs/heads/foo")) + self.assertEquals("refs/heads/foo", parse_ref(r, "refs/heads/foo")) + + +class ParseRefsTests(TestCase): + + def test_nonexistent(self): + r = {} + self.assertRaises(KeyError, parse_refs, r, ["thisdoesnotexist"]) + + def test_head(self): + r = {"refs/heads/foo": "bla"} + self.assertEquals(["refs/heads/foo"], parse_refs(r, ["foo"])) + + def test_full(self): + r = {"refs/heads/foo": "bla"} + self.assertEquals(["refs/heads/foo"], parse_refs(r, "refs/heads/foo")) + + +class ParseReftupleTests(TestCase): + + def test_nonexistent(self): + r = {} + self.assertRaises(KeyError, parse_reftuple, r, r, "thisdoesnotexist") + + def test_head(self): + r = {"refs/heads/foo": "bla"} + self.assertEquals(("refs/heads/foo", "refs/heads/foo", False), + parse_reftuple(r, r, "foo")) + self.assertEquals(("refs/heads/foo", "refs/heads/foo", True), + parse_reftuple(r, r, "+foo")) + self.assertEquals(("refs/heads/foo", "refs/heads/foo", True), + parse_reftuple(r, {}, "+foo")) + + def test_full(self): + r = {"refs/heads/foo": "bla"} + self.assertEquals(("refs/heads/foo", "refs/heads/foo", False), + parse_reftuple(r, r, "refs/heads/foo")) + + +class ParseReftuplesTests(TestCase): + + def test_nonexistent(self): + r = {} + self.assertRaises(KeyError, parse_reftuples, r, r, + ["thisdoesnotexist"]) + + def test_head(self): + r = {"refs/heads/foo": "bla"} + self.assertEquals([("refs/heads/foo", "refs/heads/foo", False)], + parse_reftuples(r, r, ["foo"])) + + def test_full(self): + r = {"refs/heads/foo": "bla"} + self.assertEquals([("refs/heads/foo", "refs/heads/foo", False)], + parse_reftuples(r, r, "refs/heads/foo"))