summaryrefslogtreecommitdiff
path: root/lib/git
diff options
context:
space:
mode:
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):
"""