summaryrefslogtreecommitdiff
path: root/lib/git
diff options
context:
space:
mode:
authorSebastian Thiel <byronimo@gmail.com>2009-10-23 00:14:24 +0200
committerSebastian Thiel <byronimo@gmail.com>2009-10-23 00:14:24 +0200
commit00499d994fea6fb55a33c788f069782f917dbdd4 (patch)
treef3de7739d34003a7356bd72769f212a47ebc8993 /lib/git
parentb2a14e4b96a0ffc5353733b50266b477539ef899 (diff)
parentd1bd99c0a376dec63f0f050aeb0c40664260da16 (diff)
downloadgitpython-00499d994fea6fb55a33c788f069782f917dbdd4.tar.gz
Merge branch 'symbolic_ref' into improvements
* symbolic_ref: SymbolicReferences can now change they references safely as I think and well controlled, including test. Adjusted tests to deal with API changes Added SymbolicReference and HEAD type to better represent these special types of references and allow special handling
Diffstat (limited to 'lib/git')
-rw-r--r--lib/git/refs.py231
-rw-r--r--lib/git/repo.py12
2 files changed, 201 insertions, 42 deletions
diff --git a/lib/git/refs.py b/lib/git/refs.py
index 9a03b6f5..a83628ce 100644
--- a/lib/git/refs.py
+++ b/lib/git/refs.py
@@ -6,7 +6,8 @@
"""
Module containing all ref based objects
"""
-from objects.base import Object
+import os
+from objects import Object, Commit
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,150 @@ 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
- <git.Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">
-
- >>> 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 '<git.%s "%s">' % (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)
+
+ def _get_path(self):
+ return os.path.join(self.repo.path, self.name)
+
@property
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
- Commit object the head points to
+ Reference Object we point to
"""
- return self.object
+ fp = open(self._get_path(), 'r')
+ try:
+ 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])
+ finally:
+ fp.close()
- @classmethod
- def reset(cls, repo, commit='HEAD', index=True, working_tree = False,
+ 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
+
+ @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.
+ """
+ _HEAD_NAME = 'HEAD'
+ __slots__ = tuple()
+
+ 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)
+
+
+ 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 +345,7 @@ class Head(Reference):
Additional arguments passed to git-reset.
Returns
- Head pointing to the specified commit
+ self
"""
mode = "--soft"
if index:
@@ -216,12 +357,34 @@ class Head(Reference):
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
- return repo.active_branch
+ return 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
+ <git.Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">
+
+ >>> 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 +393,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):
"""