Skip to content

Commit

Permalink
Merge branch 'stash-drop' of git://github.com/pmrowla/dulwich
Browse files Browse the repository at this point in the history
  • Loading branch information
jelmer committed Feb 21, 2021
2 parents 5ba0d2b + 1039e7c commit 65b0905
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 6 deletions.
59 changes: 59 additions & 0 deletions dulwich/reflog.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,62 @@ def read_reflog(f):
"""
for line in f:
yield parse_reflog_line(line)


def drop_reflog_entry(f, index, rewrite=False):
"""Drop the specified reflog entry.
Args:
f: File-like object
index: Reflog entry index (in Git reflog reverse 0-indexed order)
rewrite: If a reflog entry's predecessor is removed, set its
old SHA to the new SHA of the entry that now precedes it
"""
if index < 0:
raise ValueError("Invalid reflog index %d" % index)

log = []
offset = f.tell()
for line in f:
log.append((offset, parse_reflog_line(line)))
offset = f.tell()

inverse_index = len(log) - index - 1
write_offset = log[inverse_index][0]
f.seek(write_offset)

if index == 0:
f.truncate()
return

del log[inverse_index]
if rewrite and index > 0 and log:
if inverse_index == 0:
previous_new = ZERO_SHA
else:
previous_new = log[inverse_index - 1][1].new_sha
offset, entry = log[inverse_index]
log[inverse_index] = (
offset,
Entry(
previous_new,
entry.new_sha,
entry.committer,
entry.timestamp,
entry.timezone,
entry.message,
),
)

for _, entry in log[inverse_index:]:
f.write(
format_reflog_line(
entry.old_sha,
entry.new_sha,
entry.committer,
entry.timestamp,
entry.timezone,
entry.message,
)
)
f.truncate()
22 changes: 16 additions & 6 deletions dulwich/stash.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
commit_tree,
iter_fresh_objects,
)
from dulwich.reflog import read_reflog
from dulwich.reflog import drop_reflog_entry, read_reflog


DEFAULT_STASH_REF = b"refs/stash"
Expand All @@ -45,12 +45,15 @@ def __init__(self, repo, ref=DEFAULT_STASH_REF):
self._ref = ref
self._repo = repo

def stashes(self):
reflog_path = os.path.join(
@property
def _reflog_path(self):
return os.path.join(
self._repo.commondir(), "logs", os.fsdecode(self._ref)
)

def stashes(self):
try:
with GitFile(reflog_path, "rb") as f:
with GitFile(self._reflog_path, "rb") as f:
return reversed(list(read_reflog(f)))
except FileNotFoundError:
return []
Expand All @@ -62,10 +65,17 @@ def from_repo(cls, repo):

def drop(self, index):
"""Drop entry with specified index."""
raise NotImplementedError(self.drop)
with open(self._reflog_path, "rb+") as f:
drop_reflog_entry(f, index, rewrite=True)
if len(self) == 0:
os.remove(self._reflog_path)
del self._repo.refs[self._ref]
return
if index == 0:
self._repo.refs[self._ref] = self[0].new_sha

def pop(self, index):
raise NotImplementedError(self.drop)
raise NotImplementedError(self.pop)

def push(self, committer=None, author=None, message=None):
"""Create a new stash.
Expand Down
62 changes: 62 additions & 0 deletions dulwich/tests/test_reflog.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@

"""Tests for dulwich.reflog."""

from io import BytesIO

from dulwich.objects import ZERO_SHA
from dulwich.reflog import (
drop_reflog_entry,
format_reflog_line,
parse_reflog_line,
read_reflog,
)

from dulwich.tests import (
Expand Down Expand Up @@ -82,3 +86,61 @@ def test_parse(self):
),
parse_reflog_line(reflog_line),
)


_TEST_REFLOG = (
b"0000000000000000000000000000000000000000 "
b"49030649db3dfec5a9bc03e5dde4255a14499f16 Jelmer Vernooij "
b"<[email protected]> 1446552482 +0000 "
b"clone: from git://jelmer.uk/samba\n"
b"49030649db3dfec5a9bc03e5dde4255a14499f16 "
b"42d06bd4b77fed026b154d16493e5deab78f02ec Jelmer Vernooij "
b"<[email protected]> 1446552483 +0000 "
b"clone: from git://jelmer.uk/samba\n"
b"42d06bd4b77fed026b154d16493e5deab78f02ec "
b"df6800012397fb85c56e7418dd4eb9405dee075c Jelmer Vernooij "
b"<[email protected]> 1446552484 +0000 "
b"clone: from git://jelmer.uk/samba\n"
)


class ReflogDropTests(TestCase):
def setUp(self):
TestCase.setUp(self)
self.f = BytesIO(_TEST_REFLOG)
self.original_log = list(read_reflog(self.f))
self.f.seek(0)

def _read_log(self):
self.f.seek(0)
return list(read_reflog(self.f))

def test_invalid(self):
self.assertRaises(ValueError, drop_reflog_entry, self.f, -1)

def test_drop_entry(self):
drop_reflog_entry(self.f, 0)
log = self._read_log()
self.assertEqual(len(log), 2)
self.assertEqual(self.original_log[0:2], log)

self.f.seek(0)
drop_reflog_entry(self.f, 1)
log = self._read_log()
self.assertEqual(len(log), 1)
self.assertEqual(self.original_log[1], log[0])

def test_drop_entry_with_rewrite(self):
drop_reflog_entry(self.f, 1, True)
log = self._read_log()
self.assertEqual(len(log), 2)
self.assertEqual(self.original_log[0], log[0])
self.assertEqual(self.original_log[0].new_sha, log[1].old_sha)
self.assertEqual(self.original_log[2].new_sha, log[1].new_sha)

self.f.seek(0)
drop_reflog_entry(self.f, 1, True)
log = self._read_log()
self.assertEqual(len(log), 1)
self.assertEqual(ZERO_SHA, log[0].old_sha)
self.assertEqual(self.original_log[2].new_sha, log[0].new_sha)

0 comments on commit 65b0905

Please sign in to comment.