diff options
author | Sebastian Thiel <byronimo@gmail.com> | 2010-11-24 12:30:51 +0100 |
---|---|---|
committer | Sebastian Thiel <byronimo@gmail.com> | 2010-11-24 12:30:51 +0100 |
commit | a17c43d0662bab137903075f2cff34bcabc7e1d1 (patch) | |
tree | f757e77c85213da7c53d129476430745465935b0 | |
parent | 8dd51f1d63fa5ee704c2bdf4cb607bb6a71817d2 (diff) | |
download | gitpython-a17c43d0662bab137903075f2cff34bcabc7e1d1.tar.gz |
Made previously protected methods public to introduce a method with reflog support which cannot be exposed using the respective property. Ref-Creation is now fully implemented in python. For details, see doc/source/changes.rst
-rw-r--r-- | doc/source/changes.rst | 33 | ||||
-rw-r--r-- | refs/head.py | 33 | ||||
-rw-r--r-- | refs/reference.py | 27 | ||||
-rw-r--r-- | refs/remote.py | 5 | ||||
-rw-r--r-- | refs/symbolic.py | 114 | ||||
-rw-r--r-- | test/test_refs.py | 17 |
6 files changed, 118 insertions, 111 deletions
diff --git a/doc/source/changes.rst b/doc/source/changes.rst index 725dc931..5d6848eb 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -2,21 +2,44 @@ Changelog ========= -0.3.2 Beta 2 +0.3.1 Beta 2 ============ -* Added reflog support ( reading and writing ) +* Added **reflog support** ( reading and writing ) + + * New types: ``RefLog`` and ``RefLogEntry`` + * Reflog is maintained automatically when creating references and deleting them + * Non-intrusive changes to ``SymbolicReference``, these don't require your code to change. They allow to append messages to the reflog. + + * ``abspath`` property added, similar to ``abspath`` of Object instances + * ``log()`` method added + * ``log_append(...)`` method added + * ``set_reference(...)`` method added (reflog support) + * ``set_commit(...)`` method added (reflog support) + + * Intrusive Changes to ``Head`` type + + * ``create(...)`` method now supports the reflog, but will not raise ``GitCommandError`` anymore as it is a pure python implementation now. Instead, it raises ``OSError``. + * Repo.rev_parse now supports the [ref]@{n} syntax, where n is the number of steps to look into the reference's past -0.3.2 Beta 1 -============ -* Flattened directory structure to make development more convenient. +* **BugFixes** + + * Removed incorrect ORIG_HEAD handling + +* **Flattened directory** structure to make development more convenient. * .. note:: This alters the way projects using git-python as a submodule have to adjust their sys.path to be able to import git-python successfully. + * Misc smaller changes and bugfixes 0.3.1 Beta 1 ============ * Full Submodule-Support * Added unicode support for author names. Commit.author.name is now unicode instead of string. +* Head Type changes + + * config_reader() & config_writer() methods added for access to head specific options. + * tracking_branch() & set_tracking_branch() methods addded for easy configuration of tracking branches. + 0.3.0 Beta 2 ============ diff --git a/refs/head.py b/refs/head.py index 08ad581d..d8729434 100644 --- a/refs/head.py +++ b/refs/head.py @@ -113,38 +113,6 @@ class Head(Reference): k_config_remote_ref = "merge" # branch to merge from remote @classmethod - def create(cls, repo, path, commit='HEAD', force=False, **kwargs): - """Create a new head. - :param repo: Repository to create the head in - :param path: - The name or path of the head, i.e. 'new_branch' or - feature/feature1. The prefix refs/heads is implied. - - :param commit: - Commit to which the new head should point, defaults to the - current HEAD - - :param force: - if True, force creation even if branch with that name already exists. - - :param kwargs: - Additional keyword arguments to be passed to git-branch, i.e. - track, no-track, l - - :return: Newly created Head - :note: This does not alter the current HEAD, index or Working Tree""" - if cls is not Head: - raise TypeError("Only Heads can be created explicitly, not objects of type %s" % cls.__name__) - - args = ( path, commit ) - if force: - kwargs['f'] = True - - repo.git.branch(*args, **kwargs) - return cls(repo, "%s/%s" % ( cls._common_path_default, path)) - - - @classmethod def delete(cls, repo, *heads, **kwargs): """Delete the given heads :param force: @@ -157,7 +125,6 @@ class Head(Reference): flag = "-D" repo.git.branch(flag, *heads) - def set_tracking_branch(self, remote_reference): """ Configure this branch to track the given remote reference. This will alter diff --git a/refs/reference.py b/refs/reference.py index a76e2d5d..446196c3 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() + _resolve_ref_on_create = True _common_path_default = "refs" def __init__(self, repo, path): @@ -52,7 +53,7 @@ class Reference(SymbolicReference, LazyMixin, Iterable): :note: TypeChecking is done by the git command""" - abs_path = self._abs_path() + abs_path = self.abspath existed = True if not isfile(abs_path): existed = False @@ -81,31 +82,7 @@ class Reference(SymbolicReference, LazyMixin, Iterable): return self.path # could be refs/HEAD return '/'.join(tokens[2:]) - @classmethod - def create(cls, repo, path, commit='HEAD', force=False ): - """Create a new reference. - - :param repo: Repository to create the reference in - :param path: - The relative path of the reference, i.e. 'new_branch' or - feature/feature1. The path prefix 'refs/' is implied if not - given explicitly - - :param commit: - Commit to which the new reference should point, defaults to the - current HEAD - - :param force: - if True, force creation even if a reference with that name already exists. - Raise OSError otherwise - - :return: Newly created Reference - - :note: This does not alter the current HEAD, index or Working Tree""" - return cls._create(repo, path, True, commit, force) - - @classmethod def iter_items(cls, repo, common_path = None): """Equivalent to SymbolicReference.iter_items, but will return non-detached references as well.""" diff --git a/refs/remote.py b/refs/remote.py index 85dc0f1e..b7b07d4b 100644 --- a/refs/remote.py +++ b/refs/remote.py @@ -56,3 +56,8 @@ class RemoteReference(Head): except OSError: pass # END for each ref + + @classmethod + def create(cls, *args, **kwargs): + """Used to disable this method""" + raise TypeError("Cannot explicitly create remote references") diff --git a/refs/symbolic.py b/refs/symbolic.py index 0d8fdfd1..90fef8d5 100644 --- a/refs/symbolic.py +++ b/refs/symbolic.py @@ -3,7 +3,8 @@ from git.objects import Commit from git.util import ( join_path, join_path_native, - to_native_path_linux + to_native_path_linux, + assure_directory_exists ) from gitdb.util import ( @@ -28,6 +29,7 @@ class SymbolicReference(object): A typical example for a symbolic reference is HEAD.""" __slots__ = ("repo", "path") + _resolve_ref_on_create = False _common_path_default = "" _id_attribute_ = "name" @@ -58,7 +60,8 @@ class SymbolicReference(object): is the path itself.""" return self.path - def _abs_path(self): + @property + def abspath(self): return join_path_native(self.repo.git_dir, self.path) @classmethod @@ -116,7 +119,7 @@ class SymbolicReference(object): point to, or None""" tokens = None try: - fp = open(self._abs_path(), 'r') + fp = open(self.abspath, 'r') value = fp.read().rstrip() fp.close() tokens = value.split(" ") @@ -158,37 +161,48 @@ class SymbolicReference(object): return self.from_path(self.repo, target_ref_path).commit - def _set_commit(self, commit): + def set_commit(self, commit, msg = None): """Set our commit, possibly dereference our symbolic reference first. - If the reference does not exist, it will be created""" + If the reference does not exist, it will be created + + :param msg: If not None, the message will be used in the reflog entry to be + written. Otherwise the reflog is not altered""" is_detached = True try: is_detached = self.is_detached except ValueError: pass # END handle non-existing ones + if is_detached: - return self._set_reference(commit) + return self.set_reference(commit, msg) # set the commit on our reference - self._get_reference().commit = commit + self._get_reference().set_commit(commit, msg) - commit = property(_get_commit, _set_commit, doc="Query or set commits directly") + commit = property(_get_commit, set_commit, doc="Query or set commits directly") def _get_reference(self): - """:return: Reference Object we point to""" + """: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() 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): + 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 we try to get a commit from it using our interface. + Otherwise a commmit, given as Commit object or refspec, is assumed and if valid, + will be set which effectively detaches the refererence if it was a purely + symbolic one. - Strings are allowed but will be checked to be sure we have a commit + :param ref: SymbolicReference instance, Commit instance or refspec string :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""" + 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()""" write_value = None if isinstance(ref, SymbolicReference): write_value = "ref: %s" % ref.path @@ -207,33 +221,31 @@ class SymbolicReference(object): raise ValueError("Could not extract object from %s" % ref) # END end try string # END try commit attribute + oldbinsha = None + if msg is not None: + try: + oldhexsha = self.commit.binsha + except ValueError: + oldbinsha = Commit.NULL_BIN_SHA + #END handle non-existing + #END retrieve old hexsha + + fpath = self.abspath + assure_directory_exists(fpath, is_file=True) + + lfd = LockedFD(fpath) + fd = lfd.open(write=True, stream=True) + fd.write(write_value) + lfd.commit() + + # Adjust the reflog + if msg is not None: + self.log_append(oldbinsha, msg) + #END handle reflog - # if we are writing a ref, use symbolic ref to get the reflog and more - # checking - # Otherwise we detach it and have to do it manually. Besides, this works - # recursively automaitcally, but should be replaced with a python implementation - # soon - if write_value.startswith('ref:'): - self.repo.git.symbolic_ref(self.path, write_value[5:]) - return - # END non-detached handling - - path = self._abs_path() - directory = dirname(path) - if not isdir(directory): - os.makedirs(directory) - - # TODO: Write using LockedFD - fp = open(path, "wb") - try: - fp.write(write_value) - finally: - fp.close() - # END writing - # aliased reference - reference = property(_get_reference, _set_reference, doc="Returns the Reference we point to") + reference = property(_get_reference, set_reference, doc="Returns the Reference we point to") ref = reference def is_valid(self): @@ -255,7 +267,7 @@ class SymbolicReference(object): True if we are a detached reference, hence we point to a specific commit instead to another reference""" try: - self.reference + self.ref return False except TypeError: return True @@ -343,11 +355,18 @@ class SymbolicReference(object): open(pack_file_path, 'w').writelines(new_lines) # END open exception handling # END handle deletion + + # delete the reflog + reflog_path = RefLog.path(cls(repo, full_ref_path)) + if os.path.isfile(reflog_path): + os.remove(reflog_path) + #END remove reflog + @classmethod - def _create(cls, repo, path, resolve, reference, force): + def _create(cls, repo, path, resolve, reference, force, msg=None): """internal method used to create a new symbolic reference. - If resolve is False,, the reference will be taken as is, creating + If resolve is False, the reference will be taken as is, creating a proper symbolic reference. Otherwise it will be resolved to the corresponding object and a detached symbolic reference will be created instead""" @@ -365,16 +384,17 @@ class SymbolicReference(object): target_data = target.path if not resolve: target_data = "ref: " + target_data - if open(abs_ref_path, 'rb').read().strip() != target_data: - raise OSError("Reference at %s does already exist" % full_ref_path) + existing_data = open(abs_ref_path, 'rb').read().strip() + if existing_data != target_data: + raise OSError("Reference at %r does already exist, pointing to %r, requested was %r" % (full_ref_path, existing_data, target_data)) # END no force handling ref = cls(repo, full_ref_path) - ref.reference = target + ref.set_reference(target, msg) return ref @classmethod - def create(cls, repo, path, reference='HEAD', force=False ): + def create(cls, repo, path, reference='HEAD', force=False, msg=None): """Create a new symbolic reference, hence a reference pointing to another reference. :param repo: @@ -391,6 +411,10 @@ class SymbolicReference(object): if True, force creation even if a symbolic reference with that name already exists. Raise OSError otherwise + :param msg: + If not None, the message to append to the reflog. Otherwise no reflog + entry is written. + :return: Newly created symbolic Reference :raise OSError: @@ -398,7 +422,7 @@ class SymbolicReference(object): already exists. :note: This does not alter the current HEAD, index or Working Tree""" - return cls._create(repo, path, False, reference, force) + return cls._create(repo, path, cls._resolve_ref_on_create, reference, force, msg) def rename(self, new_path, force=False): """Rename self to a new path diff --git a/test/test_refs.py b/test/test_refs.py index 0c0caaf2..3b7ad9e7 100644 --- a/test/test_refs.py +++ b/test/test_refs.py @@ -205,10 +205,14 @@ class TestRefs(TestBase): for count, new_name in enumerate(("my_new_head", "feature/feature1")): actual_commit = commit+"^"*count new_head = Head.create(rw_repo, new_name, actual_commit) + assert new_head.is_detached assert cur_head.commit == prev_head_commit assert isinstance(new_head, Head) - # already exists - self.failUnlessRaises(GitCommandError, Head.create, rw_repo, new_name) + # already exists, but has the same value, so its fine + Head.create(rw_repo, new_name, new_head.commit) + + # its not fine with a different value + self.failUnlessRaises(OSError, Head.create, rw_repo, new_name, new_head.commit.parents[0]) # force it new_head = Head.create(rw_repo, new_name, actual_commit, force=True) @@ -230,7 +234,7 @@ class TestRefs(TestBase): assert tmp_head not in heads and new_head not in heads # force on deletion testing would be missing here, code looks okay though ;) # END for each new head name - self.failUnlessRaises(TypeError, RemoteReference.create, rw_repo, "some_name") + self.failUnlessRaises(TypeError, RemoteReference.create, rw_repo, "some_name") # tag ref tag_name = "1.0.2" @@ -495,3 +499,10 @@ class TestRefs(TestBase): def test_reflog(self): assert isinstance(self.rorepo.heads.master.log(), RefLog) + + + 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 + self.fail() |