From 58e2157ad3aa9d75ef4abb90eb2d1f01fba0ba2b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 22 Oct 2009 23:20:16 +0200 Subject: Added SymbolicReference and HEAD type to better represent these special types of references and allow special handling Head.reset now is an instance method of HEAD type Concatenated all reference specific tests into test_refs started to fix tests breaking now because of changed interface --- CHANGES | 9 ++- TODO | 14 ++-- lib/git/refs.py | 169 +++++++++++++++++++++++++++++++++++++++--------- lib/git/repo.py | 12 ++-- test/git/test_base.py | 46 +------------ test/git/test_commit.py | 2 +- test/git/test_head.py | 24 ------- test/git/test_refs.py | 84 ++++++++++++++++++++++++ test/git/test_tag.py | 33 ---------- 9 files changed, 239 insertions(+), 154 deletions(-) delete mode 100644 test/git/test_head.py create mode 100644 test/git/test_refs.py delete mode 100644 test/git/test_tag.py diff --git a/CHANGES b/CHANGES index 3b42095e..8a529e6b 100644 --- a/CHANGES +++ b/CHANGES @@ -87,9 +87,12 @@ Index * A new Index class allows to read and write index files directly, and to perform simple two and three way merges based on an arbitrary index. -Refs ----- -* Will dynmically retrieve their object at the time of query to assure the information +Referernces +------------ +* References are object that point to a Commit +* SymbolicReference are a pointer to a Reference Object, which itself points to a specific + Commit +* They will dynmically retrieve their object at the time of query to assure the information is actual. Recently objects would be cached, hence ref object not be safely kept persistent. diff --git a/TODO b/TODO index 3e743e65..5ec71dc3 100644 --- a/TODO +++ b/TODO @@ -58,14 +58,12 @@ Index creating several tree objects, so in the end it might be slower. Hmm, probably its okay to use the command unless we go c(++) - -Head.reset ----------- -* Should better be an instance method. Problem was that there is no class specifying - the HEAD - in a way reset would always effect the active branch. - Probably it would be okay to have a special type called SymbolicReference - which represents items like HEAD. These could naturally carry the reset - instance method. +Refs +----- +* If the HEAD is detached as it points to a specific commit, its not technically + a symbolic reference anymore. Currently, we cannot handle this that well + as we do not check for this case. This should be added though as it is + valid to have a detached head in some cases. Remote ------ diff --git a/lib/git/refs.py b/lib/git/refs.py index 9a03b6f5..97d0a6eb 100644 --- a/lib/git/refs.py +++ b/lib/git/refs.py @@ -6,6 +6,7 @@ """ Module containing all ref based objects """ +import os from objects.base import Object from objects.utils import get_object_type_by_name from utils import LazyMixin, Iterable @@ -31,6 +32,9 @@ class Reference(LazyMixin, Iterable): ``object`` Object instance, will be retrieved on demand if None """ + if not path.startswith(self._common_path_default): + raise ValueError("Cannot instantiate %s Reference from path %s" % ( self.__class__.__name__, path )) + self.repo = repo self.path = path if object is not None: @@ -75,6 +79,17 @@ class Reference(LazyMixin, Iterable): # have to be dynamic here as we may be a tag which can point to anything # Our path will be resolved to the hexsha which will be used accordingly return Object.new(self.repo, self.path) + + @property + def commit(self): + """ + Returns + Commit object the head points to + """ + commit = self.object + if commit.type != "commit": + raise TypeError("Object of reference %s did not point to a commit" % self) + return commit @classmethod def iter_items(cls, repo, common_path = None, **kwargs): @@ -112,6 +127,29 @@ class Reference(LazyMixin, Iterable): output = repo.git.for_each_ref(common_path, **options) return cls._iter_from_stream(repo, iter(output.splitlines())) + + @classmethod + def from_path(cls, repo, path): + """ + Return + Instance of type Reference, Head, Tag, SymbolicReference or HEAD + depending on the given path + """ + if path == 'HEAD': + return HEAD(repo, path) + + if '/' not in path: + return SymbolicReference(repo, path) + + for ref_type in (Head, RemoteReference, TagReference, Reference): + try: + return ref_type(repo, path) + except ValueError: + pass + # END exception handling + # END for each type to try + raise ValueError("Could not find reference type suitable to handle path %r" % path) + @classmethod def _iter_from_stream(cls, repo, stream): @@ -145,47 +183,91 @@ class Reference(LazyMixin, Iterable): # return cls(repo, full_path, obj) -class Head(Reference): +class SymbolicReference(object): """ - A Head is a named reference to a Commit. Every Head instance contains a name - and a Commit object. - - Examples:: - - >>> repo = Repo("/path/to/repo") - >>> head = repo.heads[0] - - >>> head.name - 'master' - - >>> head.commit - - - >>> head.commit.id - '1c09f116cbc2cb4100fb6935bb162daa4723f455' + Represents a special case of a reference such that this reference is symbolic. + It does not point to a specific commit, but to another Head, which itself + specifies a commit. + + A typical example for a symbolic reference is HEAD. """ - _common_path_default = "refs/heads" + __slots__ = ("repo", "name") + def __init__(self, repo, name): + if '/' in name: + raise ValueError("SymbolicReferences are not located within a directory, got %s" % name) + self.repo = repo + self.name = name + + def __str__(self): + return self.name + + def __repr__(self): + return '' % (self.__class__.__name__, self.name) + + def __eq__(self, other): + return self.name == other.name + + def __ne__(self, other): + return not ( self == other ) + + def __hash__(self): + return hash(self.name) + @property - def commit(self): + def reference(self): """ Returns - Commit object the head points to + Reference Object we point to """ - return self.object + fp = open(os.path.join(self.repo.path, self.name), 'r') + try: + tokens = fp.readline().split(' ') + if tokens[0] != 'ref:': + raise TypeError("%s is a detached symbolic reference as it points to %r" % tokens[0]) + return Reference.from_path(self.repo, tokens[1]) + finally: + fp.close() - @classmethod - def reset(cls, repo, commit='HEAD', index=True, working_tree = False, + # alias + ref = reference + + @property + def is_detached(self): + """ + Returns + True if we are a detached reference, hence we point to a specific commit + instead to another reference + """ + try: + self.reference + return False + except TypeError: + return True + + +class HEAD(SymbolicReference): + """ + Special case of a Symbolic Reference as it represents the repository's + HEAD reference. + """ + __slots__ = tuple() + + def __init__(self, repo, name): + if name != 'HEAD': + raise ValueError("HEAD instance must point to 'HEAD', got %s" % name) + super(HEAD, self).__init__(repo, name) + + + def reset(self, commit='HEAD', index=True, working_tree = False, paths=None, **kwargs): """ - Reset the current head to the given commit optionally synchronizing + Reset our HEAD to the given commit optionally synchronizing the index and working tree. - ``repo`` - Repository containing commit - ``commit`` - Commit object, Reference Object or string identifying a revision + Commit object, Reference Object or string identifying a revision we + should reset HEAD to. ``index`` If True, the index will be set to match the given commit. Otherwise @@ -204,7 +286,7 @@ class Head(Reference): Additional arguments passed to git-reset. Returns - Head pointing to the specified commit + self """ mode = "--soft" if index: @@ -219,9 +301,32 @@ class Head(Reference): repo.git.reset(mode, commit, paths, **kwargs) # we always point to the active branch as it is the one changing - return repo.active_branch + self + + +class Head(Reference): + """ + A Head is a named reference to a Commit. Every Head instance contains a name + and a Commit object. + + Examples:: + + >>> repo = Repo("/path/to/repo") + >>> head = repo.heads[0] + + >>> head.name + 'master' + + >>> head.commit + + + >>> head.commit.id + '1c09f116cbc2cb4100fb6935bb162daa4723f455' + """ + _common_path_default = "refs/heads" + -class TagReference(Head): +class TagReference(Reference): """ Class representing a lightweight tag reference which either points to a commit or to a tag object. In the latter case additional information, like the signature @@ -230,7 +335,7 @@ class TagReference(Head): This tag object will always point to a commit object, but may carray additional information in a tag object:: - tagref = TagRef.list_items(repo)[0] + tagref = TagReference.list_items(repo)[0] print tagref.commit.message if tagref.tag is not None: print tagref.tag.message diff --git a/lib/git/repo.py b/lib/git/repo.py index b6624d8b..94555a31 100644 --- a/lib/git/repo.py +++ b/lib/git/repo.py @@ -147,15 +147,12 @@ class Repo(object): branches = heads @property - def head(self,path="HEAD"): + def head(self): """ Return - Head Object, reference pointing to commit - - ``path`` - path to the head or its name, i.e. master or heads/master + HEAD Object pointing to the current head reference """ - return Head(self,path) + return HEAD(self,'HEAD') @property def remotes(self): @@ -486,8 +483,7 @@ class Repo(object): Returns Head to the active branch """ - return Head( self, self.git.symbolic_ref('HEAD').strip() ) - + return self.head.reference def blame(self, rev, file): """ diff --git a/test/git/test_base.py b/test/git/test_base.py index 3472608e..1b78786a 100644 --- a/test/git/test_base.py +++ b/test/git/test_base.py @@ -66,50 +66,6 @@ class TestBase(TestBase): assert len(s|s) == num_objs assert num_index_objs == 2 - - def test_tags(self): - # tag refs can point to tag objects or to commits - s = set() - ref_count = 0 - for ref in chain(self.rorepo.tags, self.rorepo.heads): - ref_count += 1 - assert isinstance(ref, refs.Reference) - assert str(ref) == ref.name - assert repr(ref) - assert ref == ref - assert not ref != ref - s.add(ref) - # END for each ref - assert len(s) == ref_count - assert len(s|s) == ref_count - - def test_heads(self): - # see how it dynmically updates its object - for head in self.rorepo.heads: - head.name - head.path - prev_object = head.object - cur_object = head.object - assert prev_object == cur_object # represent the same git object - assert prev_object is not cur_object # but are different instances - # END for each head - - @with_rw_repo('0.1.6') - def test_head_reset(self, rw_repo): - cur_head = rw_repo.head - new_head_commit = cur_head.commit.parents[0] - reset_head = Head.reset(rw_repo, new_head_commit, index=True) # index only - assert reset_head.commit == new_head_commit - - self.failUnlessRaises(ValueError, Head.reset, rw_repo, new_head_commit, index=False, working_tree=True) - new_head_commit = new_head_commit.parents[0] - reset_head = Head.reset(rw_repo, new_head_commit, index=True, working_tree=True) # index + wt - assert reset_head.commit == new_head_commit - - # paths - Head.reset(rw_repo, new_head_commit, paths = "lib") - - def test_get_object_type_by_name(self): for tname in base.Object.TYPES: assert base.Object in get_object_type_by_name(tname).mro() @@ -119,7 +75,7 @@ class TestBase(TestBase): def test_object_resolution(self): # objects must be resolved to shas so they compare equal - assert self.rorepo.head.object == self.rorepo.active_branch.object + assert self.rorepo.head.reference.object == self.rorepo.active_branch.object @with_bare_rw_repo def test_with_bare_rw_repo(self, bare_rw_repo): diff --git a/test/git/test_commit.py b/test/git/test_commit.py index 1a74593d..c4ed4b72 100644 --- a/test/git/test_commit.py +++ b/test/git/test_commit.py @@ -64,7 +64,7 @@ class TestCommit(TestBase): assert_equal(sha1, commit.id) def test_count(self): - assert self.rorepo.tag('0.1.5').commit.count( ) == 141 + assert self.rorepo.tag('refs/tags/0.1.5').commit.count( ) == 141 def test_list(self): assert isinstance(Commit.list_items(self.rorepo, '0.1.5', max_count=5)['5117c9c8a4d3af19a9958677e45cda9269de1541'], Commit) diff --git a/test/git/test_head.py b/test/git/test_head.py deleted file mode 100644 index 9b18ad7c..00000000 --- a/test/git/test_head.py +++ /dev/null @@ -1,24 +0,0 @@ -# test_head.py -# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors -# -# This module is part of GitPython and is released under -# the BSD License: http://www.opensource.org/licenses/bsd-license.php - -from test.testlib import * -from git import * - -class TestHead(TestBase): - - def test_base(self): - for head in self.rorepo.heads: - assert head.name - assert "refs/heads" in head.path - # END for each head - - @patch_object(Git, '_call_process') - def test_ref_with_path_component(self, git): - git.return_value = fixture('for_each_ref_with_path_component') - head = self.rorepo.heads[0] - - assert_equal('refactoring/feature1', head.name) - assert_true(git.called) diff --git a/test/git/test_refs.py b/test/git/test_refs.py new file mode 100644 index 00000000..ece6bf3e --- /dev/null +++ b/test/git/test_refs.py @@ -0,0 +1,84 @@ +# test_refs.py +# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors +# +# This module is part of GitPython and is released under +# the BSD License: http://www.opensource.org/licenses/bsd-license.php + +from mock import * +from test.testlib import * +from git import * +import git.refs as refs +from git.objects.tag import TagObject +from itertools import chain + +class TestRefs(TestBase): + + def test_tag_base(self): + tag_object_refs = list() + for tag in self.rorepo.tags: + assert "refs/tags" in tag.path + assert tag.name + assert isinstance( tag.commit, Commit ) + if tag.tag is not None: + tag_object_refs.append( tag ) + tagobj = tag.tag + assert isinstance( tagobj, TagObject ) + assert tagobj.tag == tag.name + assert isinstance( tagobj.tagger, Actor ) + assert isinstance( tagobj.tagged_date, int ) + assert tagobj.message + # END if we have a tag object + # END for tag in repo-tags + assert tag_object_refs + assert isinstance(self.rorepo.tags['0.1.5'], TagReference) + + @patch_object(Git, '_call_process') + def test_ref_with_path_component(self, git): + git.return_value = fixture('for_each_ref_with_path_component') + head = self.rorepo.heads[0] + + assert_equal('refactoring/feature1', head.name) + assert_true(git.called) + + + def test_tags(self): + # tag refs can point to tag objects or to commits + s = set() + ref_count = 0 + for ref in chain(self.rorepo.tags, self.rorepo.heads): + ref_count += 1 + assert isinstance(ref, refs.Reference) + assert str(ref) == ref.name + assert repr(ref) + assert ref == ref + assert not ref != ref + s.add(ref) + # END for each ref + assert len(s) == ref_count + assert len(s|s) == ref_count + + def test_heads(self): + for head in self.rorepo.heads: + assert head.name + assert head.path + assert "refs/heads" in head.path + prev_object = head.object + cur_object = head.object + assert prev_object == cur_object # represent the same git object + assert prev_object is not cur_object # but are different instances + # END for each head + + @with_rw_repo('0.1.6') + def test_head_reset(self, rw_repo): + cur_head = rw_repo.head + new_head_commit = cur_head.ref.commit.parents[0] + reset_head = Head.reset(rw_repo, new_head_commit, index=True) # index only + assert reset_head.commit == new_head_commit + + self.failUnlessRaises(ValueError, Head.reset, rw_repo, new_head_commit, index=False, working_tree=True) + new_head_commit = new_head_commit.parents[0] + reset_head = Head.reset(rw_repo, new_head_commit, index=True, working_tree=True) # index + wt + assert reset_head.commit == new_head_commit + + # paths + Head.reset(rw_repo, new_head_commit, paths = "lib") diff --git a/test/git/test_tag.py b/test/git/test_tag.py deleted file mode 100644 index 97e0acd1..00000000 --- a/test/git/test_tag.py +++ /dev/null @@ -1,33 +0,0 @@ -# test_tag.py -# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors -# -# This module is part of GitPython and is released under -# the BSD License: http://www.opensource.org/licenses/bsd-license.php - -from mock import * -from test.testlib import * -from git import * -from git.objects.tag import TagObject - -class TestTag(TestBase): - - def test_tag_base(self): - tag_object_refs = list() - for tag in self.rorepo.tags: - assert "refs/tags" in tag.path - assert tag.name - assert isinstance( tag.commit, Commit ) - if tag.tag is not None: - tag_object_refs.append( tag ) - tagobj = tag.tag - assert isinstance( tagobj, TagObject ) - assert tagobj.tag == tag.name - assert isinstance( tagobj.tagger, Actor ) - assert isinstance( tagobj.tagged_date, int ) - assert tagobj.message - # END if we have a tag object - # END for tag in repo-tags - assert tag_object_refs - assert isinstance(self.rorepo.tags['0.1.5'], TagReference) - - -- cgit v1.2.1 From b7a5c05875a760c0bf83af6617c68061bda6cfc5 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 22 Oct 2009 23:31:26 +0200 Subject: Adjusted tests to deal with API changes --- TODO | 2 ++ lib/git/refs.py | 7 +++---- test/git/test_index.py | 4 ++-- test/git/test_refs.py | 12 ++++++------ test/git/test_repo.py | 10 ++-------- 5 files changed, 15 insertions(+), 20 deletions(-) diff --git a/TODO b/TODO index 5ec71dc3..c202fcb8 100644 --- a/TODO +++ b/TODO @@ -64,6 +64,8 @@ Refs a symbolic reference anymore. Currently, we cannot handle this that well as we do not check for this case. This should be added though as it is valid to have a detached head in some cases. +* Allow to set SymbolicReferences to a specific Head Reference. This is like + a git-reset --soft Remote ------ diff --git a/lib/git/refs.py b/lib/git/refs.py index 97d0a6eb..aeee0da1 100644 --- a/lib/git/refs.py +++ b/lib/git/refs.py @@ -222,7 +222,7 @@ class SymbolicReference(object): """ fp = open(os.path.join(self.repo.path, self.name), 'r') try: - tokens = fp.readline().split(' ') + tokens = fp.readline().rstrip().split(' ') if tokens[0] != 'ref:': raise TypeError("%s is a detached symbolic reference as it points to %r" % tokens[0]) return Reference.from_path(self.repo, tokens[1]) @@ -298,10 +298,9 @@ class HEAD(SymbolicReference): raise ValueError( "Cannot reset the working tree if the index is not reset as well") # END working tree handling - repo.git.reset(mode, commit, paths, **kwargs) + self.repo.git.reset(mode, commit, paths, **kwargs) - # we always point to the active branch as it is the one changing - self + return self class Head(Reference): diff --git a/test/git/test_index.py b/test/git/test_index.py index 257acf10..7bc2ad7e 100644 --- a/test/git/test_index.py +++ b/test/git/test_index.py @@ -116,8 +116,8 @@ class TestTree(TestBase): # test diff # resetting the head will leave the index in a different state, and the # diff will yield a few changes - cur_head_commit = rw_repo.head.commit - ref = rw_repo.head.reset(rw_repo, 'HEAD~6', index=True, working_tree=False) + cur_head_commit = rw_repo.head.reference.commit + ref = rw_repo.head.reset('HEAD~6', index=True, working_tree=False) # diff against same index is 0 diff = index.diff() diff --git a/test/git/test_refs.py b/test/git/test_refs.py index ece6bf3e..0954d42d 100644 --- a/test/git/test_refs.py +++ b/test/git/test_refs.py @@ -72,13 +72,13 @@ class TestRefs(TestBase): def test_head_reset(self, rw_repo): cur_head = rw_repo.head new_head_commit = cur_head.ref.commit.parents[0] - reset_head = Head.reset(rw_repo, new_head_commit, index=True) # index only - assert reset_head.commit == new_head_commit + cur_head.reset(new_head_commit, index=True) # index only + assert cur_head.reference.commit == new_head_commit - self.failUnlessRaises(ValueError, Head.reset, rw_repo, new_head_commit, index=False, working_tree=True) + self.failUnlessRaises(ValueError, cur_head.reset, new_head_commit, index=False, working_tree=True) new_head_commit = new_head_commit.parents[0] - reset_head = Head.reset(rw_repo, new_head_commit, index=True, working_tree=True) # index + wt - assert reset_head.commit == new_head_commit + cur_head.reset(new_head_commit, index=True, working_tree=True) # index + wt + assert cur_head.reference.commit == new_head_commit # paths - Head.reset(rw_repo, new_head_commit, paths = "lib") + cur_head.reset(new_head_commit, paths = "lib") diff --git a/test/git/test_repo.py b/test/git/test_repo.py index 02eea7de..b02610f4 100644 --- a/test/git/test_repo.py +++ b/test/git/test_repo.py @@ -168,17 +168,11 @@ class TestRepo(TestBase): self.rorepo._bare = True assert self.rorepo.is_dirty == False - @patch_object(Git, '_call_process') - def test_active_branch(self, git): - git.return_value = 'refs/heads/major-refactoring' - assert_equal(self.rorepo.active_branch.name, 'major-refactoring') - assert_equal(git.call_args, (('symbolic_ref', 'HEAD'), {})) - def test_head(self): - assert self.rorepo.head.object == self.rorepo.active_branch.object + assert self.rorepo.head.reference.object == self.rorepo.active_branch.object def test_tag(self): - assert self.rorepo.tag('0.1.5').commit + assert self.rorepo.tag('refs/tags/0.1.5').commit def test_archive(self): tmpfile = os.tmpfile() -- cgit v1.2.1 From d1bd99c0a376dec63f0f050aeb0c40664260da16 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 23 Oct 2009 00:13:09 +0200 Subject: SymbolicReferences can now change they references safely as I think and well controlled, including test. Added commit method which will return the commit for detached symbolic refs or for normal symbolic refs which is quite convenient --- TODO | 2 -- lib/git/refs.py | 71 ++++++++++++++++++++++++++++++++++++++++++++++----- test/git/test_refs.py | 26 +++++++++++++++++++ 3 files changed, 91 insertions(+), 8 deletions(-) diff --git a/TODO b/TODO index c202fcb8..5ec71dc3 100644 --- a/TODO +++ b/TODO @@ -64,8 +64,6 @@ Refs a symbolic reference anymore. Currently, we cannot handle this that well as we do not check for this case. This should be added though as it is valid to have a detached head in some cases. -* Allow to set SymbolicReferences to a specific Head Reference. This is like - a git-reset --soft Remote ------ diff --git a/lib/git/refs.py b/lib/git/refs.py index aeee0da1..a83628ce 100644 --- a/lib/git/refs.py +++ b/lib/git/refs.py @@ -7,7 +7,7 @@ Module containing all ref based objects """ import os -from objects.base import Object +from objects import Object, Commit from objects.utils import get_object_type_by_name from utils import LazyMixin, Iterable @@ -214,13 +214,36 @@ class SymbolicReference(object): def __hash__(self): return hash(self.name) + def _get_path(self): + return os.path.join(self.repo.path, self.name) + @property - def reference(self): + def commit(self): + """ + Returns: + Commit object we point to, works for detached and non-detached + SymbolicReferences + """ + # we partially reimplement it to prevent unnecessary file access + fp = open(self._get_path(), 'r') + value = fp.read().rstrip() + fp.close() + tokens = value.split(" ") + + # it is a detached reference + if len(tokens) == 1 and len(tokens[0]) == 40: + return Commit(self.repo, tokens[0]) + + # must be a head ! Git does not allow symbol refs to other things than heads + # Otherwise it would have detached it + return Head(self.repo, tokens[1]).commit + + def _get_reference(self): """ Returns Reference Object we point to """ - fp = open(os.path.join(self.repo.path, self.name), 'r') + fp = open(self._get_path(), 'r') try: tokens = fp.readline().rstrip().split(' ') if tokens[0] != 'ref:': @@ -229,6 +252,41 @@ class SymbolicReference(object): finally: fp.close() + def _set_reference(self, ref): + """ + Set ourselves to the given ref. It will stay a symbol if the ref is a Head. + Otherwise we try to get a commit from it using our interface. + + Strings are allowed but will be checked to be sure we have a commit + """ + write_value = None + if isinstance(ref, Head): + write_value = "ref: %s" % ref.path + elif isinstance(ref, Commit): + write_value = ref.id + else: + try: + write_value = ref.commit.id + except AttributeError: + sha = str(ref) + try: + obj = Object.new(self.repo, sha) + if obj.type != "commit": + raise TypeError("Invalid object type behind sha: %s" % sha) + write_value = obj.id + except Exception: + raise ValueError("Could not extract object from %s" % ref) + # END end try string + # END try commit attribute + fp = open(self._get_path(), "w") + try: + fp.write(write_value) + finally: + fp.close() + # END writing + + reference = property(_get_reference, _set_reference, doc="Returns the Reference we point to") + # alias ref = reference @@ -251,11 +309,12 @@ class HEAD(SymbolicReference): Special case of a Symbolic Reference as it represents the repository's HEAD reference. """ + _HEAD_NAME = 'HEAD' __slots__ = tuple() - def __init__(self, repo, name): - if name != 'HEAD': - raise ValueError("HEAD instance must point to 'HEAD', got %s" % name) + def __init__(self, repo, name=_HEAD_NAME): + if name != self._HEAD_NAME: + raise ValueError("HEAD instance must point to %r, got %r" % (self._HEAD_NAME, name)) super(HEAD, self).__init__(repo, name) diff --git a/test/git/test_refs.py b/test/git/test_refs.py index 0954d42d..1562310a 100644 --- a/test/git/test_refs.py +++ b/test/git/test_refs.py @@ -82,3 +82,29 @@ class TestRefs(TestBase): # paths cur_head.reset(new_head_commit, paths = "lib") + + + # now that we have a write write repo, change the HEAD reference - its + # like git-reset --soft + heads = rw_repo.heads + assert heads + for head in heads: + cur_head.reference = head + assert cur_head.reference == head + assert cur_head.commit == head.commit + assert not cur_head.is_detached + # END for each head + + # detach + cur_head.reference = heads[0].commit + assert cur_head.commit == heads[0].commit + assert cur_head.is_detached + self.failUnlessRaises(TypeError, getattr, cur_head, "reference") + + some_tag = rw_repo.tags[0] + cur_head.reference = some_tag + assert cur_head.is_detached + assert cur_head.commit == some_tag.commit + + # type check + self.failUnlessRaises(ValueError, setattr, cur_head, "reference", "that") -- cgit v1.2.1