Skip to content

Commit ec0657c

Browse files
committed
Unified object and commit handling which should make the reflog handling much easier. There is some bug in it though, it still needs fixing
1 parent a17c43d commit ec0657c

File tree

6 files changed

+97
-94
lines changed

6 files changed

+97
-94
lines changed

‎refs/log.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,11 @@ def to_file(self, filepath):
191191
#END handle change
192192

193193
@classmethod
194-
def append_entry(cls, filepath, oldbinsha, newbinsha, message):
195-
"""Append a new log entry to the revlog at filepath.
194+
def append_entry(cls, config_reader, filepath, oldbinsha, newbinsha, message):
195+
"""Append a new log entry to the revlog at filepath.
196+
:param config_reader: configuration reader of the repository - used to obtain
197+
user information. May be None
198+
:param filepath: full path to the log file
196199
:param oldbinsha: binary sha of the previous commit
197200
:param newbinsha: binary sha of the current commit
198201
:param message: message describing the change to the reference
@@ -205,7 +208,7 @@ def append_entry(cls, filepath, oldbinsha, newbinsha, message):
205208
raise ValueError("Shas need to be given in binary format")
206209
#END handle sha type
207210
assure_directory_exists(filepath, is_file=True)
208-
entry = RefLogEntry((bin_to_hex(oldbinsha), bin_to_hex(newbinsha), Actor.committer(), (int(time.time()), time.altzone), message))
211+
entry = RefLogEntry((bin_to_hex(oldbinsha), bin_to_hex(newbinsha), Actor.committer(config_reader), (int(time.time()), time.altzone), message))
209212

210213
lf = LockFile(filepath)
211214
lf._obtain_lock_or_raise()

‎refs/reference.py

+1-36
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+
_points_to_commits_only = False
2122
_resolve_ref_on_create = True
2223
_common_path_default = "refs"
2324

@@ -36,42 +37,6 @@ def __init__(self, repo, path):
3637
def __str__(self):
3738
return self.name
3839

39-
def _get_object(self):
40-
"""
41-
:return:
42-
The object our ref currently refers to. Refs can be cached, they will
43-
always point to the actual object as it gets re-created on each query"""
44-
# have to be dynamic here as we may be a tag which can point to anything
45-
# Our path will be resolved to the hexsha which will be used accordingly
46-
return Object.new_from_sha(self.repo, hex_to_bin(self.dereference_recursive(self.repo, self.path)))
47-
48-
def _set_object(self, ref):
49-
"""
50-
Set our reference to point to the given ref. It will be converted
51-
to a specific hexsha.
52-
If the reference does not exist, it will be created.
53-
54-
:note:
55-
TypeChecking is done by the git command"""
56-
abs_path = self.abspath
57-
existed = True
58-
if not isfile(abs_path):
59-
existed = False
60-
open(abs_path, 'wb').write(Object.NULL_HEX_SHA)
61-
# END quick create
62-
63-
# do it safely by specifying the old value
64-
try:
65-
self.repo.git.update_ref(self.path, ref, (existed and self._get_object().hexsha) or None)
66-
except:
67-
if not existed:
68-
os.remove(abs_path)
69-
# END remove file on error if it didn't exist before
70-
raise
71-
# END exception handling
72-
73-
object = property(_get_object, _set_object, doc="Return the object our ref currently refers to")
74-
7540
@property
7641
def name(self):
7742
""":return: (shortest) Name of this reference - it may contain path components"""

‎refs/symbolic.py

+78-44
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import os
2-
from git.objects import Commit
2+
from git.objects import Object, Commit
33
from git.util import (
44
join_path,
55
join_path_native,
@@ -30,6 +30,7 @@ class SymbolicReference(object):
3030
A typical example for a symbolic reference is HEAD."""
3131
__slots__ = ("repo", "path")
3232
_resolve_ref_on_create = False
33+
_points_to_commits_only = True
3334
_common_path_default = ""
3435
_id_attribute_ = "name"
3536

@@ -107,66 +108,89 @@ def dereference_recursive(cls, repo, ref_path):
107108
intermediate references as required
108109
:param repo: the repository containing the reference at ref_path"""
109110
while True:
110-
ref = cls(repo, ref_path)
111-
hexsha, ref_path = ref._get_ref_info()
111+
hexsha, ref_path = cls._get_ref_info(repo, ref_path)
112112
if hexsha is not None:
113113
return hexsha
114114
# END recursive dereferencing
115115

116-
def _get_ref_info(self):
116+
@classmethod
117+
def _get_ref_info(cls, repo, path):
117118
"""Return: (sha, target_ref_path) if available, the sha the file at
118119
rela_path points to, or None. target_ref_path is the reference we
119120
point to, or None"""
120121
tokens = None
121122
try:
122-
fp = open(self.abspath, 'r')
123+
fp = open(join(repo.git_dir, path), 'r')
123124
value = fp.read().rstrip()
124125
fp.close()
125126
tokens = value.split(" ")
126127
except (OSError,IOError):
127128
# Probably we are just packed, find our entry in the packed refs file
128129
# NOTE: We are not a symbolic ref if we are in a packed file, as these
129130
# are excluded explictly
130-
for sha, path in self._iter_packed_refs(self.repo):
131-
if path != self.path: continue
131+
for sha, path in cls._iter_packed_refs(repo):
132+
if path != path: continue
132133
tokens = (sha, path)
133134
break
134135
# END for each packed ref
135136
# END handle packed refs
136137

137138
if tokens is None:
138-
raise ValueError("Reference at %r does not exist" % self.path)
139+
raise ValueError("Reference at %r does not exist" % path)
139140

140141
# is it a reference ?
141142
if tokens[0] == 'ref:':
142143
return (None, tokens[1])
143144

144145
# its a commit
145-
if self.repo.re_hexsha_only.match(tokens[0]):
146+
if repo.re_hexsha_only.match(tokens[0]):
146147
return (tokens[0], None)
147148

148-
raise ValueError("Failed to parse reference information from %r" % self.path)
149-
149+
raise ValueError("Failed to parse reference information from %r" % path)
150+
151+
def _get_object(self):
152+
"""
153+
:return:
154+
The object our ref currently refers to. Refs can be cached, they will
155+
always point to the actual object as it gets re-created on each query"""
156+
# have to be dynamic here as we may be a tag which can point to anything
157+
# Our path will be resolved to the hexsha which will be used accordingly
158+
return Object.new_from_sha(self.repo, hex_to_bin(self.dereference_recursive(self.repo, self.path)))
159+
150160
def _get_commit(self):
151161
"""
152162
:return:
153163
Commit object we point to, works for detached and non-detached
154-
SymbolicReferences"""
155-
# we partially reimplement it to prevent unnecessary file access
156-
hexsha, target_ref_path = self._get_ref_info()
157-
158-
# it is a detached reference
159-
if hexsha:
160-
return Commit(self.repo, hex_to_bin(hexsha))
161-
162-
return self.from_path(self.repo, target_ref_path).commit
164+
SymbolicReferences. The symbolic reference will be dereferenced recursively."""
165+
obj = self._get_object()
166+
if obj.type != Commit.type:
167+
raise TypeError("Symbolic Reference pointed to object %r, commit was required" % obj)
168+
#END handle type
169+
return obj
163170

164171
def set_commit(self, commit, msg = None):
165-
"""Set our commit, possibly dereference our symbolic reference first.
172+
"""As set_object, but restricts the type of object to be a Commit
173+
:note: To save cycles, we do not yet check whether the given Object
174+
is actually referring to a commit - for now it may be any of our
175+
Object or Reference types, as well as a refspec"""
176+
# may have to check the type ... this is costly as we would have to use
177+
# revparse
178+
self.set_object(commit, msg)
179+
180+
181+
def set_object(self, object, msg = None):
182+
"""Set the object we point to, possibly dereference our symbolic reference first.
166183
If the reference does not exist, it will be created
167184
185+
:param object: a refspec, a SymbolicReference or an Object instance. SymbolicReferences
186+
will be dereferenced beforehand to obtain the object they point to
168187
:param msg: If not None, the message will be used in the reflog entry to be
169-
written. Otherwise the reflog is not altered"""
188+
written. Otherwise the reflog is not altered
189+
:note: plain SymbolicReferences may not actually point to objects by convention"""
190+
if isinstance(object, SymbolicReference):
191+
object = object.object
192+
#END resolve references
193+
170194
is_detached = True
171195
try:
172196
is_detached = self.is_detached
@@ -175,56 +199,66 @@ def set_commit(self, commit, msg = None):
175199
# END handle non-existing ones
176200

177201
if is_detached:
178-
return self.set_reference(commit, msg)
202+
return self.set_reference(object, msg)
179203

180204
# set the commit on our reference
181-
self._get_reference().set_commit(commit, msg)
205+
self._get_reference().set_object(object, msg)
182206

183207
commit = property(_get_commit, set_commit, doc="Query or set commits directly")
208+
object = property(_get_object, set_object, doc="Return the object our ref currently refers to")
184209

185210
def _get_reference(self):
186211
""":return: Reference Object we point to
187212
:raise TypeError: If this symbolic reference is detached, hence it doesn't point
188213
to a reference, but to a commit"""
189-
sha, target_ref_path = self._get_ref_info()
214+
sha, target_ref_path = self._get_ref_info(self.repo, self.path)
190215
if target_ref_path is None:
191216
raise TypeError("%s is a detached symbolic reference as it points to %r" % (self, sha))
192217
return self.from_path(self.repo, target_ref_path)
193218

194219
def set_reference(self, ref, msg = None):
195220
"""Set ourselves to the given ref. It will stay a symbol if the ref is a Reference.
196-
Otherwise a commmit, given as Commit object or refspec, is assumed and if valid,
221+
Otherwise an Object, given as Object instance or refspec, is assumed and if valid,
197222
will be set which effectively detaches the refererence if it was a purely
198223
symbolic one.
199224
200-
:param ref: SymbolicReference instance, Commit instance or refspec string
225+
:param ref: SymbolicReference instance, Object instance or refspec string
226+
Only if the ref is a SymbolicRef instance, we will point to it. Everthiny
227+
else is dereferenced to obtain the actual object.
201228
:param msg: If set to a string, the message will be used in the reflog.
202229
Otherwise, a reflog entry is not written for the changed reference.
203230
The previous commit of the entry will be the commit we point to now.
204231
205-
See also: log_append()"""
232+
See also: log_append()
233+
:note: This symbolic reference will not be dereferenced. For that, see
234+
``set_object(...)``"""
206235
write_value = None
236+
obj = None
207237
if isinstance(ref, SymbolicReference):
208238
write_value = "ref: %s" % ref.path
209-
elif isinstance(ref, Commit):
239+
elif isinstance(ref, Object):
240+
obj = ref
210241
write_value = ref.hexsha
211-
else:
242+
elif isinstance(ref, basestring):
212243
try:
213-
write_value = ref.commit.hexsha
214-
except AttributeError:
215-
try:
216-
obj = self.repo.rev_parse(ref+"^{}") # optionally deref tags
217-
if obj.type != "commit":
218-
raise TypeError("Invalid object type behind sha: %s" % sha)
219-
write_value = obj.hexsha
220-
except Exception:
221-
raise ValueError("Could not extract object from %s" % ref)
222-
# END end try string
244+
obj = self.repo.rev_parse(ref+"^{}") # optionally deref tags
245+
write_value = obj.hexsha
246+
except Exception:
247+
raise ValueError("Could not extract object from %s" % ref)
248+
# END end try string
249+
else:
250+
raise ValueError("Unrecognized Value: %r" % ref)
223251
# END try commit attribute
252+
253+
# typecheck
254+
if obj is not None and self._points_to_commits_only and obj.type != Commit.type:
255+
raise TypeError("Require commit, got %r" % obj)
256+
#END verify type
257+
224258
oldbinsha = None
225259
if msg is not None:
226260
try:
227-
oldhexsha = self.commit.binsha
261+
oldbinsha = self.commit.binsha
228262
except ValueError:
229263
oldbinsha = Commit.NULL_BIN_SHA
230264
#END handle non-existing
@@ -247,14 +281,14 @@ def set_reference(self, ref, msg = None):
247281
# aliased reference
248282
reference = property(_get_reference, set_reference, doc="Returns the Reference we point to")
249283
ref = reference
250-
284+
251285
def is_valid(self):
252286
"""
253287
:return:
254288
True if the reference is valid, hence it can be read and points to
255289
a valid object or reference."""
256290
try:
257-
self.commit
291+
self.object
258292
except (OSError, ValueError):
259293
return False
260294
else:
@@ -288,7 +322,7 @@ def log_append(self, oldbinsha, message, newbinsha=None):
288322
:param newbinsha: The sha the ref points to now. If None, our current commit sha
289323
will be used
290324
:return: added RefLogEntry instance"""
291-
return RefLog.append_entry(RefLog.path(self), oldbinsha,
325+
return RefLog.append_entry(self.repo.config_reader(), RefLog.path(self), oldbinsha,
292326
(newbinsha is None and self.commit.binsha) or newbinsha,
293327
message)
294328

‎test/test_reflog.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def test_base(self):
6060
# test serialize and deserialize - results must match exactly
6161
binsha = chr(255)*20
6262
msg = "my reflog message"
63+
cr = repo.config_reader()
6364
for rlp in (rlp_head, rlp_master):
6465
reflog = RefLog.from_file(rlp)
6566
tfile = os.path.join(tdir, os.path.basename(rlp))
@@ -73,7 +74,7 @@ def test_base(self):
7374
assert open(tfile).read() == open(rlp).read()
7475

7576
# append an entry
76-
entry = RefLog.append_entry(tfile, IndexObject.NULL_BIN_SHA, binsha, msg)
77+
entry = RefLog.append_entry(cr, tfile, IndexObject.NULL_BIN_SHA, binsha, msg)
7778
assert entry.oldhexsha == IndexObject.NULL_HEX_SHA
7879
assert entry.newhexsha == 'f'*40
7980
assert entry.message == msg

0 commit comments

Comments
 (0)