Skip to content

Commit a17c43d

Browse files
committed
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
1 parent 8dd51f1 commit a17c43d

File tree

6 files changed

+118
-111
lines changed

6 files changed

+118
-111
lines changed

doc/source/changes.rst

+28-5
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,44 @@
22
Changelog
33
=========
44

5-
0.3.2 Beta 2
5+
0.3.1 Beta 2
66
============
7-
* Added reflog support ( reading and writing )
7+
* Added **reflog support** ( reading and writing )
8+
9+
* New types: ``RefLog`` and ``RefLogEntry``
10+
* Reflog is maintained automatically when creating references and deleting them
11+
* Non-intrusive changes to ``SymbolicReference``, these don't require your code to change. They allow to append messages to the reflog.
12+
13+
* ``abspath`` property added, similar to ``abspath`` of Object instances
14+
* ``log()`` method added
15+
* ``log_append(...)`` method added
16+
* ``set_reference(...)`` method added (reflog support)
17+
* ``set_commit(...)`` method added (reflog support)
18+
19+
* Intrusive Changes to ``Head`` type
20+
21+
* ``create(...)`` method now supports the reflog, but will not raise ``GitCommandError`` anymore as it is a pure python implementation now. Instead, it raises ``OSError``.
22+
823
* Repo.rev_parse now supports the [ref]@{n} syntax, where n is the number of steps to look into the reference's past
924

10-
0.3.2 Beta 1
11-
============
12-
* Flattened directory structure to make development more convenient.
25+
* **BugFixes**
26+
27+
* Removed incorrect ORIG_HEAD handling
28+
29+
* **Flattened directory** structure to make development more convenient.
1330

1431
* .. 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.
32+
* Misc smaller changes and bugfixes
1533

1634
0.3.1 Beta 1
1735
============
1836
* Full Submodule-Support
1937
* Added unicode support for author names. Commit.author.name is now unicode instead of string.
38+
* Head Type changes
39+
40+
* config_reader() & config_writer() methods added for access to head specific options.
41+
* tracking_branch() & set_tracking_branch() methods addded for easy configuration of tracking branches.
42+
2043

2144
0.3.0 Beta 2
2245
============

refs/head.py

-33
Original file line numberDiff line numberDiff line change
@@ -112,38 +112,6 @@ class Head(Reference):
112112
k_config_remote = "remote"
113113
k_config_remote_ref = "merge" # branch to merge from remote
114114

115-
@classmethod
116-
def create(cls, repo, path, commit='HEAD', force=False, **kwargs):
117-
"""Create a new head.
118-
:param repo: Repository to create the head in
119-
:param path:
120-
The name or path of the head, i.e. 'new_branch' or
121-
feature/feature1. The prefix refs/heads is implied.
122-
123-
:param commit:
124-
Commit to which the new head should point, defaults to the
125-
current HEAD
126-
127-
:param force:
128-
if True, force creation even if branch with that name already exists.
129-
130-
:param kwargs:
131-
Additional keyword arguments to be passed to git-branch, i.e.
132-
track, no-track, l
133-
134-
:return: Newly created Head
135-
:note: This does not alter the current HEAD, index or Working Tree"""
136-
if cls is not Head:
137-
raise TypeError("Only Heads can be created explicitly, not objects of type %s" % cls.__name__)
138-
139-
args = ( path, commit )
140-
if force:
141-
kwargs['f'] = True
142-
143-
repo.git.branch(*args, **kwargs)
144-
return cls(repo, "%s/%s" % ( cls._common_path_default, path))
145-
146-
147115
@classmethod
148116
def delete(cls, repo, *heads, **kwargs):
149117
"""Delete the given heads
@@ -157,7 +125,6 @@ def delete(cls, repo, *heads, **kwargs):
157125
flag = "-D"
158126
repo.git.branch(flag, *heads)
159127

160-
161128
def set_tracking_branch(self, remote_reference):
162129
"""
163130
Configure this branch to track the given remote reference. This will alter

refs/reference.py

+2-25
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class Reference(SymbolicReference, LazyMixin, Iterable):
1818
"""Represents a named reference to any object. Subclasses may apply restrictions though,
1919
i.e. Heads can only point to commits."""
2020
__slots__ = tuple()
21+
_resolve_ref_on_create = True
2122
_common_path_default = "refs"
2223

2324
def __init__(self, repo, path):
@@ -52,7 +53,7 @@ def _set_object(self, ref):
5253
5354
:note:
5455
TypeChecking is done by the git command"""
55-
abs_path = self._abs_path()
56+
abs_path = self.abspath
5657
existed = True
5758
if not isfile(abs_path):
5859
existed = False
@@ -81,31 +82,7 @@ def name(self):
8182
return self.path # could be refs/HEAD
8283
return '/'.join(tokens[2:])
8384

84-
8585
@classmethod
86-
def create(cls, repo, path, commit='HEAD', force=False ):
87-
"""Create a new reference.
88-
89-
:param repo: Repository to create the reference in
90-
:param path:
91-
The relative path of the reference, i.e. 'new_branch' or
92-
feature/feature1. The path prefix 'refs/' is implied if not
93-
given explicitly
94-
95-
:param commit:
96-
Commit to which the new reference should point, defaults to the
97-
current HEAD
98-
99-
:param force:
100-
if True, force creation even if a reference with that name already exists.
101-
Raise OSError otherwise
102-
103-
:return: Newly created Reference
104-
105-
:note: This does not alter the current HEAD, index or Working Tree"""
106-
return cls._create(repo, path, True, commit, force)
107-
108-
@classmethod
10986
def iter_items(cls, repo, common_path = None):
11087
"""Equivalent to SymbolicReference.iter_items, but will return non-detached
11188
references as well."""

refs/remote.py

+5
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,8 @@ def delete(cls, repo, *refs, **kwargs):
5656
except OSError:
5757
pass
5858
# END for each ref
59+
60+
@classmethod
61+
def create(cls, *args, **kwargs):
62+
"""Used to disable this method"""
63+
raise TypeError("Cannot explicitly create remote references")

refs/symbolic.py

+69-45
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
from git.util import (
44
join_path,
55
join_path_native,
6-
to_native_path_linux
6+
to_native_path_linux,
7+
assure_directory_exists
78
)
89

910
from gitdb.util import (
@@ -28,6 +29,7 @@ class SymbolicReference(object):
2829
2930
A typical example for a symbolic reference is HEAD."""
3031
__slots__ = ("repo", "path")
32+
_resolve_ref_on_create = False
3133
_common_path_default = ""
3234
_id_attribute_ = "name"
3335

@@ -58,7 +60,8 @@ def name(self):
5860
is the path itself."""
5961
return self.path
6062

61-
def _abs_path(self):
63+
@property
64+
def abspath(self):
6265
return join_path_native(self.repo.git_dir, self.path)
6366

6467
@classmethod
@@ -116,7 +119,7 @@ def _get_ref_info(self):
116119
point to, or None"""
117120
tokens = None
118121
try:
119-
fp = open(self._abs_path(), 'r')
122+
fp = open(self.abspath, 'r')
120123
value = fp.read().rstrip()
121124
fp.close()
122125
tokens = value.split(" ")
@@ -158,37 +161,48 @@ def _get_commit(self):
158161

159162
return self.from_path(self.repo, target_ref_path).commit
160163

161-
def _set_commit(self, commit):
164+
def set_commit(self, commit, msg = None):
162165
"""Set our commit, possibly dereference our symbolic reference first.
163-
If the reference does not exist, it will be created"""
166+
If the reference does not exist, it will be created
167+
168+
:param msg: If not None, the message will be used in the reflog entry to be
169+
written. Otherwise the reflog is not altered"""
164170
is_detached = True
165171
try:
166172
is_detached = self.is_detached
167173
except ValueError:
168174
pass
169175
# END handle non-existing ones
176+
170177
if is_detached:
171-
return self._set_reference(commit)
178+
return self.set_reference(commit, msg)
172179

173180
# set the commit on our reference
174-
self._get_reference().commit = commit
181+
self._get_reference().set_commit(commit, msg)
175182

176-
commit = property(_get_commit, _set_commit, doc="Query or set commits directly")
183+
commit = property(_get_commit, set_commit, doc="Query or set commits directly")
177184

178185
def _get_reference(self):
179-
""":return: Reference Object we point to"""
186+
""":return: Reference Object we point to
187+
:raise TypeError: If this symbolic reference is detached, hence it doesn't point
188+
to a reference, but to a commit"""
180189
sha, target_ref_path = self._get_ref_info()
181190
if target_ref_path is None:
182191
raise TypeError("%s is a detached symbolic reference as it points to %r" % (self, sha))
183192
return self.from_path(self.repo, target_ref_path)
184193

185-
def _set_reference(self, ref, msg = None):
194+
def set_reference(self, ref, msg = None):
186195
"""Set ourselves to the given ref. It will stay a symbol if the ref is a Reference.
187-
Otherwise we try to get a commit from it using our interface.
196+
Otherwise a commmit, given as Commit object or refspec, is assumed and if valid,
197+
will be set which effectively detaches the refererence if it was a purely
198+
symbolic one.
188199
189-
Strings are allowed but will be checked to be sure we have a commit
200+
:param ref: SymbolicReference instance, Commit instance or refspec string
190201
:param msg: If set to a string, the message will be used in the reflog.
191-
Otherwise, a reflog entry is not written for the changed reference"""
202+
Otherwise, a reflog entry is not written for the changed reference.
203+
The previous commit of the entry will be the commit we point to now.
204+
205+
See also: log_append()"""
192206
write_value = None
193207
if isinstance(ref, SymbolicReference):
194208
write_value = "ref: %s" % ref.path
@@ -207,33 +221,31 @@ def _set_reference(self, ref, msg = None):
207221
raise ValueError("Could not extract object from %s" % ref)
208222
# END end try string
209223
# END try commit attribute
224+
oldbinsha = None
225+
if msg is not None:
226+
try:
227+
oldhexsha = self.commit.binsha
228+
except ValueError:
229+
oldbinsha = Commit.NULL_BIN_SHA
230+
#END handle non-existing
231+
#END retrieve old hexsha
232+
233+
fpath = self.abspath
234+
assure_directory_exists(fpath, is_file=True)
235+
236+
lfd = LockedFD(fpath)
237+
fd = lfd.open(write=True, stream=True)
238+
fd.write(write_value)
239+
lfd.commit()
240+
241+
# Adjust the reflog
242+
if msg is not None:
243+
self.log_append(oldbinsha, msg)
244+
#END handle reflog
210245

211-
# if we are writing a ref, use symbolic ref to get the reflog and more
212-
# checking
213-
# Otherwise we detach it and have to do it manually. Besides, this works
214-
# recursively automaitcally, but should be replaced with a python implementation
215-
# soon
216-
if write_value.startswith('ref:'):
217-
self.repo.git.symbolic_ref(self.path, write_value[5:])
218-
return
219-
# END non-detached handling
220-
221-
path = self._abs_path()
222-
directory = dirname(path)
223-
if not isdir(directory):
224-
os.makedirs(directory)
225-
226-
# TODO: Write using LockedFD
227-
fp = open(path, "wb")
228-
try:
229-
fp.write(write_value)
230-
finally:
231-
fp.close()
232-
# END writing
233-
234246

235247
# aliased reference
236-
reference = property(_get_reference, _set_reference, doc="Returns the Reference we point to")
248+
reference = property(_get_reference, set_reference, doc="Returns the Reference we point to")
237249
ref = reference
238250

239251
def is_valid(self):
@@ -255,7 +267,7 @@ def is_detached(self):
255267
True if we are a detached reference, hence we point to a specific commit
256268
instead to another reference"""
257269
try:
258-
self.reference
270+
self.ref
259271
return False
260272
except TypeError:
261273
return True
@@ -343,11 +355,18 @@ def delete(cls, repo, path):
343355
open(pack_file_path, 'w').writelines(new_lines)
344356
# END open exception handling
345357
# END handle deletion
358+
359+
# delete the reflog
360+
reflog_path = RefLog.path(cls(repo, full_ref_path))
361+
if os.path.isfile(reflog_path):
362+
os.remove(reflog_path)
363+
#END remove reflog
364+
346365

347366
@classmethod
348-
def _create(cls, repo, path, resolve, reference, force):
367+
def _create(cls, repo, path, resolve, reference, force, msg=None):
349368
"""internal method used to create a new symbolic reference.
350-
If resolve is False,, the reference will be taken as is, creating
369+
If resolve is False, the reference will be taken as is, creating
351370
a proper symbolic reference. Otherwise it will be resolved to the
352371
corresponding object and a detached symbolic reference will be created
353372
instead"""
@@ -365,16 +384,17 @@ def _create(cls, repo, path, resolve, reference, force):
365384
target_data = target.path
366385
if not resolve:
367386
target_data = "ref: " + target_data
368-
if open(abs_ref_path, 'rb').read().strip() != target_data:
369-
raise OSError("Reference at %s does already exist" % full_ref_path)
387+
existing_data = open(abs_ref_path, 'rb').read().strip()
388+
if existing_data != target_data:
389+
raise OSError("Reference at %r does already exist, pointing to %r, requested was %r" % (full_ref_path, existing_data, target_data))
370390
# END no force handling
371391

372392
ref = cls(repo, full_ref_path)
373-
ref.reference = target
393+
ref.set_reference(target, msg)
374394
return ref
375395

376396
@classmethod
377-
def create(cls, repo, path, reference='HEAD', force=False ):
397+
def create(cls, repo, path, reference='HEAD', force=False, msg=None):
378398
"""Create a new symbolic reference, hence a reference pointing to another reference.
379399
380400
:param repo:
@@ -391,14 +411,18 @@ def create(cls, repo, path, reference='HEAD', force=False ):
391411
if True, force creation even if a symbolic reference with that name already exists.
392412
Raise OSError otherwise
393413
414+
:param msg:
415+
If not None, the message to append to the reflog. Otherwise no reflog
416+
entry is written.
417+
394418
:return: Newly created symbolic Reference
395419
396420
:raise OSError:
397421
If a (Symbolic)Reference with the same name but different contents
398422
already exists.
399423
400424
:note: This does not alter the current HEAD, index or Working Tree"""
401-
return cls._create(repo, path, False, reference, force)
425+
return cls._create(repo, path, cls._resolve_ref_on_create, reference, force, msg)
402426

403427
def rename(self, new_path, force=False):
404428
"""Rename self to a new path

0 commit comments

Comments
 (0)