summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--refs/log.py9
-rw-r--r--refs/reference.py37
-rw-r--r--refs/symbolic.py122
-rw-r--r--test/test_reflog.py3
-rw-r--r--test/test_refs.py18
-rw-r--r--test/test_remote.py2
6 files changed, 97 insertions, 94 deletions
diff --git a/refs/log.py b/refs/log.py
index 9728911a..129803b4 100644
--- a/refs/log.py
+++ b/refs/log.py
@@ -191,8 +191,11 @@ class RefLog(list, Serializable):
#END handle change
@classmethod
- def append_entry(cls, filepath, oldbinsha, newbinsha, message):
- """Append a new log entry to the revlog at filepath.
+ def append_entry(cls, config_reader, filepath, oldbinsha, newbinsha, message):
+ """Append a new log entry to the revlog at filepath.
+ :param config_reader: configuration reader of the repository - used to obtain
+ user information. May be None
+ :param filepath: full path to the log file
:param oldbinsha: binary sha of the previous commit
:param newbinsha: binary sha of the current commit
:param message: message describing the change to the reference
@@ -205,7 +208,7 @@ class RefLog(list, Serializable):
raise ValueError("Shas need to be given in binary format")
#END handle sha type
assure_directory_exists(filepath, is_file=True)
- entry = RefLogEntry((bin_to_hex(oldbinsha), bin_to_hex(newbinsha), Actor.committer(), (int(time.time()), time.altzone), message))
+ entry = RefLogEntry((bin_to_hex(oldbinsha), bin_to_hex(newbinsha), Actor.committer(config_reader), (int(time.time()), time.altzone), message))
lf = LockFile(filepath)
lf._obtain_lock_or_raise()
diff --git a/refs/reference.py b/refs/reference.py
index 446196c3..e7cdfdee 100644
--- a/refs/reference.py
+++ b/refs/reference.py
@@ -18,6 +18,7 @@ class Reference(SymbolicReference, LazyMixin, Iterable):
"""Represents a named reference to any object. Subclasses may apply restrictions though,
i.e. Heads can only point to commits."""
__slots__ = tuple()
+ _points_to_commits_only = False
_resolve_ref_on_create = True
_common_path_default = "refs"
@@ -36,42 +37,6 @@ class Reference(SymbolicReference, LazyMixin, Iterable):
def __str__(self):
return self.name
- def _get_object(self):
- """
- :return:
- The object our ref currently refers to. Refs can be cached, they will
- always point to the actual object as it gets re-created on each query"""
- # 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_from_sha(self.repo, hex_to_bin(self.dereference_recursive(self.repo, self.path)))
-
- def _set_object(self, ref):
- """
- Set our reference to point to the given ref. It will be converted
- to a specific hexsha.
- If the reference does not exist, it will be created.
-
- :note:
- TypeChecking is done by the git command"""
- abs_path = self.abspath
- existed = True
- if not isfile(abs_path):
- existed = False
- open(abs_path, 'wb').write(Object.NULL_HEX_SHA)
- # END quick create
-
- # do it safely by specifying the old value
- try:
- self.repo.git.update_ref(self.path, ref, (existed and self._get_object().hexsha) or None)
- except:
- if not existed:
- os.remove(abs_path)
- # END remove file on error if it didn't exist before
- raise
- # END exception handling
-
- object = property(_get_object, _set_object, doc="Return the object our ref currently refers to")
-
@property
def name(self):
""":return: (shortest) Name of this reference - it may contain path components"""
diff --git a/refs/symbolic.py b/refs/symbolic.py
index 90fef8d5..2ecdee4a 100644
--- a/refs/symbolic.py
+++ b/refs/symbolic.py
@@ -1,5 +1,5 @@
import os
-from git.objects import Commit
+from git.objects import Object, Commit
from git.util import (
join_path,
join_path_native,
@@ -30,6 +30,7 @@ class SymbolicReference(object):
A typical example for a symbolic reference is HEAD."""
__slots__ = ("repo", "path")
_resolve_ref_on_create = False
+ _points_to_commits_only = True
_common_path_default = ""
_id_attribute_ = "name"
@@ -107,19 +108,19 @@ class SymbolicReference(object):
intermediate references as required
:param repo: the repository containing the reference at ref_path"""
while True:
- ref = cls(repo, ref_path)
- hexsha, ref_path = ref._get_ref_info()
+ hexsha, ref_path = cls._get_ref_info(repo, ref_path)
if hexsha is not None:
return hexsha
# END recursive dereferencing
- def _get_ref_info(self):
+ @classmethod
+ def _get_ref_info(cls, repo, path):
"""Return: (sha, target_ref_path) if available, the sha the file at
rela_path points to, or None. target_ref_path is the reference we
point to, or None"""
tokens = None
try:
- fp = open(self.abspath, 'r')
+ fp = open(join(repo.git_dir, path), 'r')
value = fp.read().rstrip()
fp.close()
tokens = value.split(" ")
@@ -127,46 +128,69 @@ class SymbolicReference(object):
# Probably we are just packed, find our entry in the packed refs file
# NOTE: We are not a symbolic ref if we are in a packed file, as these
# are excluded explictly
- for sha, path in self._iter_packed_refs(self.repo):
- if path != self.path: continue
+ for sha, path in cls._iter_packed_refs(repo):
+ if path != path: continue
tokens = (sha, path)
break
# END for each packed ref
# END handle packed refs
if tokens is None:
- raise ValueError("Reference at %r does not exist" % self.path)
+ raise ValueError("Reference at %r does not exist" % path)
# is it a reference ?
if tokens[0] == 'ref:':
return (None, tokens[1])
# its a commit
- if self.repo.re_hexsha_only.match(tokens[0]):
+ if repo.re_hexsha_only.match(tokens[0]):
return (tokens[0], None)
- raise ValueError("Failed to parse reference information from %r" % self.path)
-
+ raise ValueError("Failed to parse reference information from %r" % path)
+
+ def _get_object(self):
+ """
+ :return:
+ The object our ref currently refers to. Refs can be cached, they will
+ always point to the actual object as it gets re-created on each query"""
+ # 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_from_sha(self.repo, hex_to_bin(self.dereference_recursive(self.repo, self.path)))
+
def _get_commit(self):
"""
:return:
Commit object we point to, works for detached and non-detached
- SymbolicReferences"""
- # we partially reimplement it to prevent unnecessary file access
- hexsha, target_ref_path = self._get_ref_info()
-
- # it is a detached reference
- if hexsha:
- return Commit(self.repo, hex_to_bin(hexsha))
-
- return self.from_path(self.repo, target_ref_path).commit
+ SymbolicReferences. The symbolic reference will be dereferenced recursively."""
+ obj = self._get_object()
+ if obj.type != Commit.type:
+ raise TypeError("Symbolic Reference pointed to object %r, commit was required" % obj)
+ #END handle type
+ return obj
def set_commit(self, commit, msg = None):
- """Set our commit, possibly dereference our symbolic reference first.
+ """As set_object, but restricts the type of object to be a Commit
+ :note: To save cycles, we do not yet check whether the given Object
+ is actually referring to a commit - for now it may be any of our
+ Object or Reference types, as well as a refspec"""
+ # may have to check the type ... this is costly as we would have to use
+ # revparse
+ self.set_object(commit, msg)
+
+
+ def set_object(self, object, msg = None):
+ """Set the object we point to, possibly dereference our symbolic reference first.
If the reference does not exist, it will be created
+ :param object: a refspec, a SymbolicReference or an Object instance. SymbolicReferences
+ will be dereferenced beforehand to obtain the object they point to
:param msg: If not None, the message will be used in the reflog entry to be
- written. Otherwise the reflog is not altered"""
+ written. Otherwise the reflog is not altered
+ :note: plain SymbolicReferences may not actually point to objects by convention"""
+ if isinstance(object, SymbolicReference):
+ object = object.object
+ #END resolve references
+
is_detached = True
try:
is_detached = self.is_detached
@@ -175,56 +199,66 @@ class SymbolicReference(object):
# END handle non-existing ones
if is_detached:
- return self.set_reference(commit, msg)
+ return self.set_reference(object, msg)
# set the commit on our reference
- self._get_reference().set_commit(commit, msg)
+ self._get_reference().set_object(object, msg)
commit = property(_get_commit, set_commit, doc="Query or set commits directly")
+ object = property(_get_object, set_object, doc="Return the object our ref currently refers to")
def _get_reference(self):
""":return: Reference Object we point to
:raise TypeError: If this symbolic reference is detached, hence it doesn't point
to a reference, but to a commit"""
- sha, target_ref_path = self._get_ref_info()
+ sha, target_ref_path = self._get_ref_info(self.repo, self.path)
if target_ref_path is None:
raise TypeError("%s is a detached symbolic reference as it points to %r" % (self, sha))
return self.from_path(self.repo, target_ref_path)
def set_reference(self, ref, msg = None):
"""Set ourselves to the given ref. It will stay a symbol if the ref is a Reference.
- Otherwise a commmit, given as Commit object or refspec, is assumed and if valid,
+ Otherwise an Object, given as Object instance or refspec, is assumed and if valid,
will be set which effectively detaches the refererence if it was a purely
symbolic one.
- :param ref: SymbolicReference instance, Commit instance or refspec string
+ :param ref: SymbolicReference instance, Object instance or refspec string
+ Only if the ref is a SymbolicRef instance, we will point to it. Everthiny
+ else is dereferenced to obtain the actual object.
:param msg: If set to a string, the message will be used in the reflog.
Otherwise, a reflog entry is not written for the changed reference.
The previous commit of the entry will be the commit we point to now.
- See also: log_append()"""
+ See also: log_append()
+ :note: This symbolic reference will not be dereferenced. For that, see
+ ``set_object(...)``"""
write_value = None
+ obj = None
if isinstance(ref, SymbolicReference):
write_value = "ref: %s" % ref.path
- elif isinstance(ref, Commit):
+ elif isinstance(ref, Object):
+ obj = ref
write_value = ref.hexsha
- else:
+ elif isinstance(ref, basestring):
try:
- write_value = ref.commit.hexsha
- except AttributeError:
- try:
- obj = self.repo.rev_parse(ref+"^{}") # optionally deref tags
- if obj.type != "commit":
- raise TypeError("Invalid object type behind sha: %s" % sha)
- write_value = obj.hexsha
- except Exception:
- raise ValueError("Could not extract object from %s" % ref)
- # END end try string
+ obj = self.repo.rev_parse(ref+"^{}") # optionally deref tags
+ write_value = obj.hexsha
+ except Exception:
+ raise ValueError("Could not extract object from %s" % ref)
+ # END end try string
+ else:
+ raise ValueError("Unrecognized Value: %r" % ref)
# END try commit attribute
+
+ # typecheck
+ if obj is not None and self._points_to_commits_only and obj.type != Commit.type:
+ raise TypeError("Require commit, got %r" % obj)
+ #END verify type
+
oldbinsha = None
if msg is not None:
try:
- oldhexsha = self.commit.binsha
+ oldbinsha = self.commit.binsha
except ValueError:
oldbinsha = Commit.NULL_BIN_SHA
#END handle non-existing
@@ -247,14 +281,14 @@ class SymbolicReference(object):
# aliased reference
reference = property(_get_reference, set_reference, doc="Returns the Reference we point to")
ref = reference
-
+
def is_valid(self):
"""
:return:
True if the reference is valid, hence it can be read and points to
a valid object or reference."""
try:
- self.commit
+ self.object
except (OSError, ValueError):
return False
else:
@@ -288,7 +322,7 @@ class SymbolicReference(object):
:param newbinsha: The sha the ref points to now. If None, our current commit sha
will be used
:return: added RefLogEntry instance"""
- return RefLog.append_entry(RefLog.path(self), oldbinsha,
+ return RefLog.append_entry(self.repo.config_reader(), RefLog.path(self), oldbinsha,
(newbinsha is None and self.commit.binsha) or newbinsha,
message)
diff --git a/test/test_reflog.py b/test/test_reflog.py
index e99e5ba5..0c8e538b 100644
--- a/test/test_reflog.py
+++ b/test/test_reflog.py
@@ -60,6 +60,7 @@ class TestRefLog(TestBase):
# test serialize and deserialize - results must match exactly
binsha = chr(255)*20
msg = "my reflog message"
+ cr = repo.config_reader()
for rlp in (rlp_head, rlp_master):
reflog = RefLog.from_file(rlp)
tfile = os.path.join(tdir, os.path.basename(rlp))
@@ -73,7 +74,7 @@ class TestRefLog(TestBase):
assert open(tfile).read() == open(rlp).read()
# append an entry
- entry = RefLog.append_entry(tfile, IndexObject.NULL_BIN_SHA, binsha, msg)
+ entry = RefLog.append_entry(cr, tfile, IndexObject.NULL_BIN_SHA, binsha, msg)
assert entry.oldhexsha == IndexObject.NULL_HEX_SHA
assert entry.newhexsha == 'f'*40
assert entry.message == msg
diff --git a/test/test_refs.py b/test/test_refs.py
index 3b7ad9e7..f0d648f1 100644
--- a/test/test_refs.py
+++ b/test/test_refs.py
@@ -15,7 +15,7 @@ import os
class TestRefs(TestBase):
- def test_from_path(self):
+ def _test_from_path(self):
# should be able to create any reference directly
for ref_type in ( Reference, Head, TagReference, RemoteReference ):
for name in ('rela_name', 'path/rela_name'):
@@ -25,7 +25,7 @@ class TestRefs(TestBase):
# END for each name
# END for each type
- def test_tag_base(self):
+ def _test_tag_base(self):
tag_object_refs = list()
for tag in self.rorepo.tags:
assert "refs/tags" in tag.path
@@ -50,7 +50,7 @@ class TestRefs(TestBase):
assert tag_object_refs
assert isinstance(self.rorepo.tags['0.1.5'], TagReference)
- def test_tags(self):
+ def _test_tags(self):
# tag refs can point to tag objects or to commits
s = set()
ref_count = 0
@@ -67,7 +67,7 @@ class TestRefs(TestBase):
assert len(s|s) == ref_count
@with_rw_repo('HEAD', bare=False)
- def test_heads(self, rwrepo):
+ def _test_heads(self, rwrepo):
for head in rwrepo.heads:
assert head.name
assert head.path
@@ -129,7 +129,7 @@ class TestRefs(TestBase):
# TODO: Need changing a ref changes HEAD reflog as well if it pointed to it
- def test_refs(self):
+ def _test_refs(self):
types_found = set()
for ref in self.rorepo.refs:
types_found.add(type(ref))
@@ -142,7 +142,7 @@ class TestRefs(TestBase):
assert SymbolicReference(self.rorepo, 'hellothere').is_valid() == False
@with_rw_repo('0.1.6')
- def test_head_reset(self, rw_repo):
+ def _test_head_reset(self, rw_repo):
cur_head = rw_repo.head
old_head_commit = cur_head.commit
new_head_commit = cur_head.ref.commit.parents[0]
@@ -493,15 +493,15 @@ class TestRefs(TestBase):
# END for each path
- def test_dereference_recursive(self):
+ def _test_dereference_recursive(self):
# for now, just test the HEAD
assert SymbolicReference.dereference_recursive(self.rorepo, 'HEAD')
- def test_reflog(self):
+ def _test_reflog(self):
assert isinstance(self.rorepo.heads.master.log(), RefLog)
- def test_todo(self):
+ def _test_todo(self):
# delete deletes the reflog
# create creates a new entry
# set_reference and set_commit and set_object use the reflog if message is given
diff --git a/test/test_remote.py b/test/test_remote.py
index 108712a5..af6915a3 100644
--- a/test/test_remote.py
+++ b/test/test_remote.py
@@ -208,7 +208,7 @@ class TestRemote(TestBase):
assert tinfo.flags & tinfo.NEW_TAG
# adjust tag commit
- Reference._set_object(rtag, rhead.commit.parents[0].parents[0])
+ Reference.set_object(rtag, rhead.commit.parents[0].parents[0])
res = fetch_and_test(remote, tags=True)
tinfo = res[str(rtag)]
assert tinfo.commit == rtag.commit