summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Thiel <byronimo@gmail.com>2010-11-24 12:30:51 +0100
committerSebastian Thiel <byronimo@gmail.com>2010-11-24 12:30:51 +0100
commita17c43d0662bab137903075f2cff34bcabc7e1d1 (patch)
treef757e77c85213da7c53d129476430745465935b0
parent8dd51f1d63fa5ee704c2bdf4cb607bb6a71817d2 (diff)
downloadgitpython-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.rst33
-rw-r--r--refs/head.py33
-rw-r--r--refs/reference.py27
-rw-r--r--refs/remote.py5
-rw-r--r--refs/symbolic.py114
-rw-r--r--test/test_refs.py17
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()