Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6e5aae2

Browse files
committed
Initial interface including some of the implementation of the RefLog. TestCase scetched out for now
tests: Added tests to verify that objects don't have a dict. Previously, due to a missing __slots__ member in Serializable, most objects would indeed have a dict, although the opposite was intended
1 parent 739fa14 commit 6e5aae2

File tree

8 files changed

+255
-48
lines changed

8 files changed

+255
-48
lines changed

objects/util.py

Lines changed: 42 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def get_user_id():
7171

7272
def utctz_to_altz(utctz):
7373
"""we convert utctz to the timezone in seconds, it is the format time.altzone
74-
returns. Git stores it as UTC timezon which has the opposite sign as well,
74+
returns. Git stores it as UTC timezone which has the opposite sign as well,
7575
which explains the -1 * ( that was made explicit here )
7676
:param utctz: git utc timezone string, i.e. +0200"""
7777
return -1 * int(float(utctz)/100*3600)
@@ -195,53 +195,55 @@ def parse_actor_and_date(line):
195195
#{ Classes
196196

197197
class Actor(object):
198-
"""Actors hold information about a person acting on the repository. They
199-
can be committers and authors or anything with a name and an email as
200-
mentioned in the git log entries."""
201-
# precompiled regex
202-
name_only_regex = re.compile( r'<(.+)>' )
203-
name_email_regex = re.compile( r'(.*) <(.+?)>' )
204-
205-
def __init__(self, name, email):
206-
self.name = name
207-
self.email = email
198+
"""Actors hold information about a person acting on the repository. They
199+
can be committers and authors or anything with a name and an email as
200+
mentioned in the git log entries."""
201+
# precompiled regex
202+
name_only_regex = re.compile( r'<(.+)>' )
203+
name_email_regex = re.compile( r'(.*) <(.+?)>' )
204+
205+
__slots__ = ('name', 'email')
206+
207+
def __init__(self, name, email):
208+
self.name = name
209+
self.email = email
208210

209-
def __eq__(self, other):
210-
return self.name == other.name and self.email == other.email
211-
212-
def __ne__(self, other):
213-
return not (self == other)
214-
215-
def __hash__(self):
216-
return hash((self.name, self.email))
211+
def __eq__(self, other):
212+
return self.name == other.name and self.email == other.email
213+
214+
def __ne__(self, other):
215+
return not (self == other)
216+
217+
def __hash__(self):
218+
return hash((self.name, self.email))
217219

218-
def __str__(self):
219-
return self.name
220+
def __str__(self):
221+
return self.name
220222

221-
def __repr__(self):
222-
return '<git.Actor "%s <%s>">' % (self.name, self.email)
223+
def __repr__(self):
224+
return '<git.Actor "%s <%s>">' % (self.name, self.email)
223225

224-
@classmethod
225-
def _from_string(cls, string):
226-
"""Create an Actor from a string.
226+
@classmethod
227+
def _from_string(cls, string):
228+
"""Create an Actor from a string.
227229
:param string: is the string, which is expected to be in regular git format
228230
229231
John Doe <[email protected]>
230232
231233
:return: Actor """
232-
m = cls.name_email_regex.search(string)
233-
if m:
234-
name, email = m.groups()
235-
return Actor(name, email)
236-
else:
237-
m = cls.name_only_regex.search(string)
238-
if m:
239-
return Actor(m.group(1), None)
240-
else:
241-
# assume best and use the whole string as name
242-
return Actor(string, None)
243-
# END special case name
244-
# END handle name/email matching
234+
m = cls.name_email_regex.search(string)
235+
if m:
236+
name, email = m.groups()
237+
return Actor(name, email)
238+
else:
239+
m = cls.name_only_regex.search(string)
240+
if m:
241+
return Actor(m.group(1), None)
242+
else:
243+
# assume best and use the whole string as name
244+
return Actor(string, None)
245+
# END special case name
246+
# END handle name/email matching
245247

246248

247249
class ProcessStreamAdapter(object):
@@ -359,6 +361,7 @@ def addToStack( stack, item, branch_first, depth ):
359361

360362
class Serializable(object):
361363
"""Defines methods to serialize and deserialize objects from and into a data stream"""
364+
__slots__ = tuple()
362365

363366
def _serialize(self, stream):
364367
"""Serialize the data of this object into the given data stream

refs/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@
1616
for item in (HEAD, Head, RemoteReference, TagReference, Reference, SymbolicReference):
1717
setattr(symbolic, item.__name__, item)
1818
del(symbolic)
19-
# git.objects.Commit -> symbolic
20-
# git.config.SectionConstraint -> head
19+
20+
21+
from log import *

refs/log.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
from head import Head
2+
from git.util import join_path
3+
from gitdb.util import (
4+
join,
5+
file_contents_ro_filepath
6+
)
7+
8+
from git.objects.util import (
9+
Actor,
10+
parse_actor_and_date,
11+
Serializable,
12+
utctz_to_altz,
13+
altz_to_utctz_str,
14+
)
15+
16+
import os
17+
18+
19+
__all__ = ["RefLog", "RefLogEntry"]
20+
21+
22+
class RefLogEntry(tuple):
23+
"""Named tuple allowing easy access to the revlog data fields"""
24+
_fmt = "%s %s %s <%s> %i %s\t%s"
25+
__slots__ = tuple()
26+
27+
def __repr__(self):
28+
"""Representation of ourselves in git reflog format"""
29+
act = self.actor
30+
time = self.time
31+
return self._fmt % (self.oldhexsha, self.newhexsha, act.name, act.email,
32+
time[0], altz_to_utctz_str(time[1]), self.message)
33+
34+
@property
35+
def oldhexsha(self):
36+
"""The hexsha to the commit the ref pointed to before the change"""
37+
return self[0]
38+
39+
@property
40+
def newhexsha(self):
41+
"""The hexsha to the commit the ref now points to, after the change"""
42+
return self[1]
43+
44+
@property
45+
def actor(self):
46+
"""Actor instance, providing access"""
47+
return self[2]
48+
49+
@property
50+
def time(self):
51+
"""time as tuple:
52+
53+
* [0] = int(time)
54+
* [1] = int(timezone_offset) in time.altzone format """
55+
return self[3]
56+
57+
@property
58+
def message(self):
59+
"""Message describing the operation that acted on the reference"""
60+
return self[4]
61+
62+
@classmethod
63+
def new(self, oldhexsha, newhexsha, actor, time, tz_offset, message):
64+
""":return: New instance of a RefLogEntry"""
65+
if not isinstance(actor, Actor):
66+
raise ValueError("Need actor instance, got %s" % actor)
67+
# END check types
68+
return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), message))
69+
70+
@classmethod
71+
def from_line(self, line):
72+
""":return: New RefLogEntry instance from the given revlog line.
73+
:param line: line without trailing newline
74+
:raise ValueError: If line could not be parsed"""
75+
raise NotImplementedError("todo")
76+
77+
78+
class RefLog(list, Serializable):
79+
"""A reflog contains reflog entries, each of which defines a certain state
80+
of the head in question. Custom query methods allow to retrieve log entries
81+
by date or by other criteria.
82+
83+
Reflog entries are orded, the first added entry is first in the list, the last
84+
entry, i.e. the last change of the head or reference, is last in the list."""
85+
86+
__slots__ = tuple()
87+
88+
#{ Interface
89+
90+
@classmethod
91+
def from_file(cls, filepath):
92+
"""
93+
:return: a new RefLog instance containing all entries from the reflog
94+
at the given filepath
95+
:param filepath: path to reflog
96+
:raise ValueError: If the file could not be read or was corrupted in some way"""
97+
inst = cls()
98+
fmap = file_contents_ro_filepath(filepath, stream=False, allow_mmap=True)
99+
try:
100+
inst._deserialize(fmap)
101+
finally:
102+
fmap.close()
103+
#END handle closing of handle
104+
return inst
105+
106+
@classmethod
107+
def reflog_path(cls, ref):
108+
"""
109+
:return: string to absolute path at which the reflog of the given ref
110+
instance would be found. The path is not guaranteed to point to a valid
111+
file though.
112+
:param ref: SymbolicReference instance"""
113+
return join(ref.repo.git_dir, "logs", ref.path)
114+
115+
@classmethod
116+
def iter_entries(cls, stream):
117+
"""
118+
:return: Iterator yielding RefLogEntry instances, one for each line read
119+
sfrom the given stream.
120+
:param stream: file-like object containing the revlog in its native format
121+
or basestring instance pointing to a file to read"""
122+
new_entry = RefLogEntry.from_line
123+
if isinstance(stream, basestring):
124+
stream = file_contents_ro_filepath(stream)
125+
#END handle stream type
126+
return (new_entry(line.strip()) for line in stream)
127+
128+
def to_file(self, filepath):
129+
"""Write the contents of the reflog instance to a file at the given filepath.
130+
:param filepath: path to file, parent directories are assumed to exist"""
131+
fp = open(filepath, 'wb')
132+
try:
133+
self._serialize(fp)
134+
finally:
135+
fp.close()
136+
#END handle file streams
137+
138+
#} END interface
139+
140+
#{ Serializable Interface
141+
def _serialize(self, stream):
142+
lm1 = len(self) - 1
143+
write = stream.write()
144+
145+
# write all entries
146+
for i, e in self:
147+
s = repr(e)
148+
if i != lm1:
149+
s += "\n"
150+
#END handle line separator
151+
write(s)
152+
#END for each entry
153+
154+
def _deserialize(self, stream):
155+
new_entry = RefLogEntry.from_line
156+
append = self.append
157+
# NOTE: should use iter_entries, but this way it will be more direct and faster
158+
for line in stream:
159+
append(new_entry(line.strip()))
160+
#END handle deserializatoin
161+
#} END serializable interface

test/test_blob.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@
99
from gitdb.util import hex_to_bin
1010

1111
class TestBlob(TestBase):
12-
13-
def test_mime_type_should_return_mime_type_for_known_types(self):
14-
blob = Blob(self.rorepo, **{'binsha': Blob.NULL_BIN_SHA, 'path': 'foo.png'})
15-
assert_equal("image/png", blob.mime_type)
12+
13+
def test_mime_type_should_return_mime_type_for_known_types(self):
14+
blob = Blob(self.rorepo, **{'binsha': Blob.NULL_BIN_SHA, 'path': 'foo.png'})
15+
assert_equal("image/png", blob.mime_type)
1616

17-
def test_mime_type_should_return_text_plain_for_unknown_types(self):
18-
blob = Blob(self.rorepo, **{'binsha': Blob.NULL_BIN_SHA,'path': 'something'})
19-
assert_equal("text/plain", blob.mime_type)
17+
def test_mime_type_should_return_text_plain_for_unknown_types(self):
18+
blob = Blob(self.rorepo, **{'binsha': Blob.NULL_BIN_SHA,'path': 'something'})
19+
assert_equal("text/plain", blob.mime_type)
2020

21+
def test_nodict(self):
22+
self.failUnlessRaises(AttributeError, setattr, self.rorepo.tree()['AUTHORS'], 'someattr', 2)
23+

test/test_commit.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ class TestCommit(TestBase):
7070
def test_bake(self):
7171

7272
commit = self.rorepo.commit('2454ae89983a4496a445ce347d7a41c0bb0ea7ae')
73+
# commits have no dict
74+
self.failUnlessRaises(AttributeError, setattr, commit, 'someattr', 1)
7375
commit.author # bake
7476

7577
assert_equal("Sebastian Thiel", commit.author.name)

test/test_reflog.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from git.test.lib import *
2+
from git.objects import IndexObject, Actor
3+
from git.refs import *
4+
5+
class TestRefLog(TestBase):
6+
7+
def test_reflogentry(self):
8+
nullhexsha = IndexObject.NULL_HEX_SHA
9+
hexsha = 'F' * 40
10+
actor = Actor('name', 'email')
11+
msg = "message"
12+
13+
self.failUnlessRaises(ValueError, RefLogEntry.new, nullhexsha, hexsha, 'noactor', 0, 0, "")
14+
e = RefLogEntry.new(nullhexsha, hexsha, actor, 0, 1, msg)
15+
16+
assert e.oldhexsha == nullhexsha
17+
assert e.newhexsha == hexsha
18+
assert e.actor == actor
19+
assert e.time[0] == 0
20+
assert e.time[1] == 1
21+
assert e.message == msg
22+
23+
# check representation (roughly)
24+
assert repr(e).startswith(nullhexsha)
25+
26+
def test_base(self):
27+
pass
28+
# raise on invalid revlog
29+
# TODO: Try multiple corrupted ones !
30+
31+
32+
# test serialize and deserialize - results must match exactly

test/test_refs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ def test_tag_base(self):
3333
if tag.tag is not None:
3434
tag_object_refs.append( tag )
3535
tagobj = tag.tag
36+
# have no dict
37+
self.failUnlessRaises(AttributeError, setattr, tagobj, 'someattr', 1)
3638
assert isinstance( tagobj, TagObject )
3739
assert tagobj.tag == tag.name
3840
assert isinstance( tagobj.tagger, Actor )

test/test_tree.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ def test_serializable(self):
2323
continue
2424
# END skip non-trees
2525
tree = item
26+
# trees have no dict
27+
self.failUnlessRaises(AttributeError, setattr, tree, 'someattr', 1)
28+
2629
orig_data = tree.data_stream.read()
2730
orig_cache = tree._cache
2831

0 commit comments

Comments
 (0)