Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/mcp_server_git/git/_commit_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,11 @@ def git_reflog(
%gP (reflog parent selector) yields a reflog selector like 'HEAD@{1}',
not a commit SHA. old_sha is instead taken from entry[i+1].new_sha,
which is always the commit HEAD pointed to before entry[i]'s operation.

old_sha is provided only for single-ref reflogs (all=False). Under
all=True, entries from different refs are interleaved so the adjacent
entry may belong to another ref, making derivation unreliable. old_sha
is therefore omitted entirely when all=True.
"""
try:
# %H = new (post-move) full SHA, %gD = reflog selector (HEAD@{0}),
Expand Down Expand Up @@ -364,8 +369,11 @@ def git_reflog(
})

# old_sha for entry[i] = entry[i+1].new_sha (reflog is newest-first).
for i in range(len(entries) - 1):
entries[i]["old_sha"] = entries[i + 1]["new_sha"]
# Only valid for single-ref reflogs; under all=True entries from different
# refs are interleaved so skip derivation entirely.
if not all: # noqa: A002
for i in range(len(entries) - 1):
entries[i]["old_sha"] = entries[i + 1]["new_sha"]

# Warn on large output, mirroring git_show's 50KB threshold.
serialized_size = len(str(entries))
Expand Down
23 changes: 23 additions & 0 deletions tests/unit/git/test_git_reflog.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,29 @@ def test_git_reflog_all_false_does_not_pass_all_flag(self):
args = mock_repo.git.reflog.call_args[0]
assert "--all" not in args

def test_git_reflog_all_true_omits_old_sha_from_every_entry(self):
"""Under all=True, no entry should contain old_sha (interleaved refs make
adjacent-entry derivation unreliable); new_sha/label/action must still be present."""
mock_repo = Mock()
sha0 = "c" * 40 # HEAD@{0} — could be from refs/heads/main
sha1 = "b" * 40 # HEAD@{1} — could be from refs/heads/feature (different ref)
sha2 = "a" * 40 # HEAD@{2}
mock_repo.git.reflog.return_value = _make_raw(
(sha0, "refs/heads/main@{0}", "commit: update main"),
(sha1, "refs/heads/feature@{0}", "commit: add feature"),
(sha2, "refs/heads/main@{1}", "commit: initial"),
)

result = git_reflog(mock_repo, all=True)

assert isinstance(result, list)
assert len(result) == 3
for entry in result:
assert "old_sha" not in entry, f"old_sha must be absent under all=True, got: {entry}"
assert "new_sha" in entry
assert "label" in entry
assert "action" in entry


class TestGitReflogMultipleEntries:
"""Test parsing of multiple reflog entries."""
Expand Down
Loading