diff options
author | Sebastian Thiel <byronimo@gmail.com> | 2010-11-24 15:56:49 +0100 |
---|---|---|
committer | Sebastian Thiel <byronimo@gmail.com> | 2010-11-24 15:56:49 +0100 |
commit | ec0657cf5de9aeb5629cc4f4f38b36f48490493e (patch) | |
tree | ae7f08094c6fdcddad194783f3e18b5a724c0197 | |
parent | a17c43d0662bab137903075f2cff34bcabc7e1d1 (diff) | |
download | gitpython-ec0657cf5de9aeb5629cc4f4f38b36f48490493e.tar.gz |
Unified object and commit handling which should make the reflog handling much easier. There is some bug in it though, it still needs fixing
-rw-r--r-- | refs/log.py | 9 | ||||
-rw-r--r-- | refs/reference.py | 37 | ||||
-rw-r--r-- | refs/symbolic.py | 122 | ||||
-rw-r--r-- | test/test_reflog.py | 3 | ||||
-rw-r--r-- | test/test_refs.py | 18 | ||||
-rw-r--r-- | test/test_remote.py | 2 |
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 |