Skip to content

Commit 36811a2

Browse files
committed
add replace method to git.Commit
This adds a replace method to git.Commit. The replace method returns a copy of the Commit object with attributes replaced from keyword arguments. For example: >>> old = repo.head.commit >>> new = old.replace(message='This is a test') closes #1123
1 parent e1cd58b commit 36811a2

File tree

2 files changed

+56
-7
lines changed

2 files changed

+56
-7
lines changed

git/objects/commit.py

+36-7
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,41 @@ def __init__(self, repo, binsha, tree=None, author=None, authored_date=None, aut
136136
def _get_intermediate_items(cls, commit):
137137
return commit.parents
138138

139+
@classmethod
140+
def _calculate_sha_(cls, repo, commit):
141+
'''Calculate the sha of a commit.
142+
143+
:param repo: Repo object the commit should be part of
144+
:param commit: Commit object for which to generate the sha
145+
'''
146+
147+
stream = BytesIO()
148+
commit._serialize(stream)
149+
streamlen = stream.tell()
150+
stream.seek(0)
151+
152+
istream = repo.odb.store(IStream(cls.type, streamlen, stream))
153+
return istream.binsha
154+
155+
def replace(self, **kwargs):
156+
'''Create new commit object from existing commit object.
157+
158+
Any values provided as keyword arguments will replace the
159+
corresponding attribute in the new object.
160+
'''
161+
162+
attrs = {k: getattr(self, k) for k in self.__slots__}
163+
164+
for attrname in kwargs:
165+
if attrname not in self.__slots__:
166+
raise ValueError('invalid attribute name')
167+
168+
attrs.update(kwargs)
169+
new_commit = self.__class__(self.repo, self.NULL_BIN_SHA, **attrs)
170+
new_commit.binsha = self._calculate_sha_(self.repo, new_commit)
171+
172+
return new_commit
173+
139174
def _set_cache_(self, attr):
140175
if attr in Commit.__slots__:
141176
# read the data in a chunk, its faster - then provide a file wrapper
@@ -375,13 +410,7 @@ def create_from_tree(cls, repo, tree, message, parent_commits=None, head=False,
375410
committer, committer_time, committer_offset,
376411
message, parent_commits, conf_encoding)
377412

378-
stream = BytesIO()
379-
new_commit._serialize(stream)
380-
streamlen = stream.tell()
381-
stream.seek(0)
382-
383-
istream = repo.odb.store(IStream(cls.type, streamlen, stream))
384-
new_commit.binsha = istream.binsha
413+
new_commit.binsha = cls._calculate_sha_(repo, new_commit)
385414

386415
if head:
387416
# need late import here, importing git at the very beginning throws

test/test_commit.py

+20
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,26 @@ def test_bake(self):
101101
assert isinstance(commit.author_tz_offset, int) and isinstance(commit.committer_tz_offset, int)
102102
self.assertEqual(commit.message, "Added missing information to docstrings of commit and stats module\n")
103103

104+
def test_replace_no_changes(self):
105+
old_commit = self.rorepo.commit('2454ae89983a4496a445ce347d7a41c0bb0ea7ae')
106+
new_commit = old_commit.replace()
107+
108+
for attr in old_commit.__slots__:
109+
assert getattr(new_commit, attr) == getattr(old_commit, attr)
110+
111+
def test_replace_new_sha(self):
112+
commit = self.rorepo.commit('2454ae89983a4496a445ce347d7a41c0bb0ea7ae')
113+
new_commit = commit.replace(message='Added replace method')
114+
115+
assert new_commit.hexsha == 'fc84cbecac1bd4ba4deaac07c1044889edd536e6'
116+
assert new_commit.message == 'Added replace method'
117+
118+
def test_replace_invalid_attribute(self):
119+
commit = self.rorepo.commit('2454ae89983a4496a445ce347d7a41c0bb0ea7ae')
120+
121+
with self.assertRaises(ValueError):
122+
commit.replace(badattr='This will never work')
123+
104124
def test_stats(self):
105125
commit = self.rorepo.commit('33ebe7acec14b25c5f84f35a664803fcab2f7781')
106126
stats = commit.stats

0 commit comments

Comments
 (0)