Skip to content

Commit a21a9f6

Browse files
committed
Actor: Moved it from git.objects.util to git.util, adjusted all imports accordingly. Added methods to Actor to retrieve the global committer and author information
Reflog: implemented and tested append_entry method
1 parent 5bd7d44 commit a21a9f6

File tree

11 files changed

+210
-109
lines changed

11 files changed

+210
-109
lines changed

‎__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ def _init_externals():
3737
from git.util import (
3838
LockFile,
3939
BlockingLockFile,
40-
Stats
40+
Stats,
41+
Actor
4142
)
4243

4344
#} END imports

‎objects/__init__.py

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from blob import *
1616
from commit import *
1717
from tree import *
18-
from util import Actor
1918

2019
__all__ = [ name for name, obj in locals().items()
2120
if not (name.startswith('_') or inspect.ismodule(obj)) ]

‎objects/commit.py

+4-23
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
# This module is part of GitPython and is released under
55
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
66

7-
from git.util import (
7+
from git.util import (
8+
Actor,
89
Iterable,
910
Stats,
1011
)
@@ -20,9 +21,7 @@
2021
from util import (
2122
Traversable,
2223
Serializable,
23-
get_user_id,
2424
parse_date,
25-
Actor,
2625
altz_to_utctz_str,
2726
parse_actor_and_date
2827
)
@@ -43,17 +42,10 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
4342

4443
# ENVIRONMENT VARIABLES
4544
# read when creating new commits
46-
env_author_name = "GIT_AUTHOR_NAME"
47-
env_author_email = "GIT_AUTHOR_EMAIL"
4845
env_author_date = "GIT_AUTHOR_DATE"
49-
env_committer_name = "GIT_COMMITTER_NAME"
50-
env_committer_email = "GIT_COMMITTER_EMAIL"
5146
env_committer_date = "GIT_COMMITTER_DATE"
52-
env_email = "EMAIL"
5347

5448
# CONFIGURATION KEYS
55-
conf_name = 'name'
56-
conf_email = 'email'
5749
conf_encoding = 'i18n.commitencoding'
5850

5951
# INVARIANTS
@@ -306,17 +298,9 @@ def create_from_tree(cls, repo, tree, message, parent_commits=None, head=False):
306298
# COMMITER AND AUTHOR INFO
307299
cr = repo.config_reader()
308300
env = os.environ
309-
default_email = get_user_id()
310-
default_name = default_email.split('@')[0]
311301

312-
conf_name = cr.get_value('user', cls.conf_name, default_name)
313-
conf_email = cr.get_value('user', cls.conf_email, default_email)
314-
315-
author_name = env.get(cls.env_author_name, conf_name)
316-
author_email = env.get(cls.env_author_email, conf_email)
317-
318-
committer_name = env.get(cls.env_committer_name, conf_name)
319-
committer_email = env.get(cls.env_committer_email, conf_email)
302+
committer = Actor.committer(cr)
303+
author = Actor.author(cr)
320304

321305
# PARSE THE DATES
322306
unix_time = int(time())
@@ -340,9 +324,6 @@ def create_from_tree(cls, repo, tree, message, parent_commits=None, head=False):
340324
enc_section, enc_option = cls.conf_encoding.split('.')
341325
conf_encoding = cr.get_value(enc_section, enc_option, cls.default_encoding)
342326

343-
author = Actor(author_name, author_email)
344-
committer = Actor(committer_name, committer_email)
345-
346327

347328
# if the tree is no object, make sure we create one - otherwise
348329
# the created commit object is invalid

‎objects/util.py

+6-68
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,21 @@
44
# This module is part of GitPython and is released under
55
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
66
"""Module for general utility functions"""
7-
from git.util import IterableList
7+
from git.util import (
8+
IterableList,
9+
Actor
10+
)
811

912
import re
1013
from collections import deque as Deque
11-
import platform
1214

1315
from string import digits
1416
import time
1517
import os
1618

17-
__all__ = ('get_object_type_by_name', 'get_user_id', 'parse_date', 'parse_actor_and_date',
19+
__all__ = ('get_object_type_by_name', 'parse_date', 'parse_actor_and_date',
1820
'ProcessStreamAdapter', 'Traversable', 'altz_to_utctz_str', 'utctz_to_altz',
19-
'verify_utctz')
21+
'verify_utctz', 'Actor')
2022

2123
#{ Functions
2224

@@ -57,18 +59,6 @@ def get_object_type_by_name(object_type_name):
5759
else:
5860
raise ValueError("Cannot handle unknown object type: %s" % object_type_name)
5961

60-
61-
def get_user_id():
62-
""":return: string identifying the currently active system user as name@node
63-
:note: user can be set with the 'USER' environment variable, usually set on windows"""
64-
ukn = 'UNKNOWN'
65-
username = os.environ.get('USER', os.environ.get('USERNAME', ukn))
66-
if username == ukn and hasattr(os, 'getlogin'):
67-
username = os.getlogin()
68-
# END get username from login
69-
return "%s@%s" % (username, platform.node())
70-
71-
7262
def utctz_to_altz(utctz):
7363
"""we convert utctz to the timezone in seconds, it is the format time.altzone
7464
returns. Git stores it as UTC timezone which has the opposite sign as well,
@@ -193,58 +183,6 @@ def parse_actor_and_date(line):
193183

194184

195185
#{ Classes
196-
197-
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-
__slots__ = ('name', 'email')
206-
207-
def __init__(self, name, email):
208-
self.name = name
209-
self.email = email
210-
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))
219-
220-
def __str__(self):
221-
return self.name
222-
223-
def __repr__(self):
224-
return '<git.Actor "%s <%s>">' % (self.name, self.email)
225-
226-
@classmethod
227-
def _from_string(cls, string):
228-
"""Create an Actor from a string.
229-
:param string: is the string, which is expected to be in regular git format
230-
231-
John Doe <jdoe@example.com>
232-
233-
:return: Actor """
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
247-
248186

249187
class ProcessStreamAdapter(object):
250188
"""Class wireing all calls to the contained Process instance.

‎refs/log.py

+53-11
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1-
from git.util import join_path
1+
from git.util import (
2+
join_path,
3+
Actor,
4+
)
5+
26
from gitdb.util import (
7+
bin_to_hex,
38
join,
49
file_contents_ro_filepath
510
)
611

712
from git.objects.util import (
8-
Actor,
913
parse_date,
1014
Serializable,
1115
utctz_to_altz,
1216
altz_to_utctz_str,
1317
)
1418

19+
import time
1520
import os
1621
import re
1722

@@ -104,7 +109,28 @@ class RefLog(list, Serializable):
104109
Reflog entries are orded, the first added entry is first in the list, the last
105110
entry, i.e. the last change of the head or reference, is last in the list."""
106111

107-
__slots__ = tuple()
112+
__slots__ = ('_path', )
113+
114+
def __new__(cls, filepath=None):
115+
inst = super(RefLog, cls).__new__(cls)
116+
return inst
117+
118+
def __init__(self, filepath=None):
119+
"""Initialize this instance with an optional filepath, from which we will
120+
initialize our data. The path is also used to write changes back using
121+
the write() method"""
122+
self._path = filepath
123+
if filepath is not None:
124+
self._read_from_file()
125+
# END handle filepath
126+
127+
def _read_from_file(self):
128+
fmap = file_contents_ro_filepath(self._path, stream=False, allow_mmap=True)
129+
try:
130+
self._deserialize(fmap)
131+
finally:
132+
fmap.close()
133+
#END handle closing of handle
108134

109135
#{ Interface
110136

@@ -115,14 +141,7 @@ def from_file(cls, filepath):
115141
at the given filepath
116142
:param filepath: path to reflog
117143
:raise ValueError: If the file could not be read or was corrupted in some way"""
118-
inst = cls()
119-
fmap = file_contents_ro_filepath(filepath, stream=False, allow_mmap=True)
120-
try:
121-
inst._deserialize(fmap)
122-
finally:
123-
fmap.close()
124-
#END handle closing of handle
125-
return inst
144+
return cls(filepath)
126145

127146
@classmethod
128147
def path(cls, ref):
@@ -154,12 +173,35 @@ def iter_entries(cls, stream):
154173
def to_file(self, filepath):
155174
"""Write the contents of the reflog instance to a file at the given filepath.
156175
:param filepath: path to file, parent directories are assumed to exist"""
176+
# TODO: Use locked fd
157177
fp = open(filepath, 'wb')
158178
try:
159179
self._serialize(fp)
160180
finally:
161181
fp.close()
162182
#END handle file streams
183+
184+
def append_entry(self, oldbinsha, newbinsha, message, write=True):
185+
"""Append a new log entry to the revlog, changing it in place.
186+
:param oldbinsha: binary sha of the previous commit
187+
:param newbinsha: binary sha of the current commit
188+
:param message: message describing the change to the reference
189+
:param write: If True, the changes will be written right away. Otherwise
190+
the change will not be written
191+
:return: RefLogEntry objects which was appended to the log"""
192+
if len(oldbinsha) != 20 or len(newbinsha) != 20:
193+
raise ValueError("Shas need to be given in binary format")
194+
#END handle sha type
195+
entry = RefLogEntry((bin_to_hex(oldbinsha), bin_to_hex(newbinsha), Actor.committer(), (int(time.time()), time.altzone), message))
196+
self.append(entry)
197+
if write:
198+
self.write()
199+
#END handle auto-write
200+
return entry
201+
202+
def write(self):
203+
"""Write this instance's data to the file we are originating from"""
204+
return self.to_file(self._path)
163205

164206
#} END interface
165207

‎refs/symbolic.py

+1
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ def _set_reference(self, ref):
236236
if not isdir(directory):
237237
os.makedirs(directory)
238238

239+
# TODO: Write using LockedFD
239240
fp = open(path, "wb")
240241
try:
241242
fp.write(write_value)

‎repo/base.py

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from git.exc import InvalidGitRepositoryError, NoSuchPathError
88
from git.cmd import Git
9+
from git.util import Actor
910
from git.refs import *
1011
from git.index import IndexFile
1112
from git.objects import *

‎test/test_reflog.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from git.test.lib import *
2-
from git.objects import IndexObject, Actor
2+
from git.objects import IndexObject
33
from git.refs import *
4+
from git.util import Actor
45

56
import tempfile
67
import shutil
@@ -40,6 +41,7 @@ def test_base(self):
4041

4142
# simple read
4243
reflog = RefLog.from_file(rlp_master_ro)
44+
assert reflog._path is not None
4345
assert isinstance(reflog, RefLog)
4446
assert len(reflog)
4547

@@ -56,6 +58,8 @@ def test_base(self):
5658

5759

5860
# test serialize and deserialize - results must match exactly
61+
binsha = chr(255)*20
62+
msg = "my reflog message"
5963
for rlp in (rlp_head, rlp_master):
6064
reflog = RefLog.from_file(rlp)
6165
tfile = os.path.join(tdir, os.path.basename(rlp))
@@ -67,6 +71,18 @@ def test_base(self):
6771

6872
# ... as well as each bytes of the written stream
6973
assert open(tfile).read() == open(rlp).read()
74+
75+
# append an entry - it gets written automatically
76+
entry = treflog.append_entry(IndexObject.NULL_BIN_SHA, binsha, msg)
77+
assert entry.oldhexsha == IndexObject.NULL_HEX_SHA
78+
assert entry.newhexsha == 'f'*40
79+
assert entry.message == msg
80+
assert treflog == RefLog.from_file(tfile)
81+
82+
# but not this time
83+
treflog.append_entry(binsha, binsha, msg, write=False)
84+
assert treflog != RefLog.from_file(tfile)
85+
7086
# END for each reflog
7187

7288

‎test/test_refs.py

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from git.test.lib import *
99
from git import *
1010
import git.refs as refs
11+
from git.util import Actor
1112
from git.objects.tag import TagObject
1213
from itertools import chain
1314
import os

‎test/test_util.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import time
1717

1818

19-
class TestUtils(TestCase):
19+
class TestUtils(TestBase):
2020
def setup(self):
2121
self.testdict = {
2222
"string": "42",
@@ -102,4 +102,8 @@ def assert_rval(rval, veri_time, offset=0):
102102
self.failUnlessRaises(ValueError, parse_date, '123456789 -02000')
103103
self.failUnlessRaises(ValueError, parse_date, ' 123456789 -0200')
104104

105-
105+
def test_actor(self):
106+
for cr in (None, self.rorepo.config_reader()):
107+
assert isinstance(Actor.committer(cr), Actor)
108+
assert isinstance(Actor.author(cr), Actor)
109+
#END assure config reader is handled

0 commit comments

Comments
 (0)