Deskaid
by ezyang
- e2e
#!/usr/bin/env python3
"""Tests for whitespace alignment in Git commit messages when amending."""
import os
import re
import unittest
from codemcp.testing import MCPEndToEndTestCase
class GitAmendWhitespaceTest(MCPEndToEndTestCase):
"""Test the whitespace alignment when HEAD is replaced with commit hash."""
async def test_whitespace_alignment_on_amend(self):
"""Test that whitespace is aligned correctly when HEAD is replaced with a commit hash."""
# Create a file to edit multiple times
test_file_path = os.path.join(self.temp_dir.name, "whitespace_test.txt")
initial_content = "Initial content for whitespace test"
# Create the file
with open(test_file_path, "w") as f:
f.write(initial_content)
# Add it to git
await self.git_run(["add", test_file_path])
# Commit it
await self.git_run(["commit", "-m", "Add file for whitespace test"])
async with self.create_client_session() as session:
# Define a chat_id for our test
chat_id = await self.get_chat_id(session)
# First edit with our chat_id
await self.call_tool_assert_success(
session,
"codemcp",
{
"subtool": "EditFile",
"path": test_file_path,
"old_string": "Initial content for whitespace test",
"new_string": "Modified content for whitespace test - edit 1",
"description": "First whitespace test edit",
"chat_id": chat_id,
},
)
# Get the commit hash for the first edit
first_commit_hash = await self.git_run(
["rev-parse", "--short", "HEAD"], capture_output=True, text=True
)
# Second edit with the same chat_id
await self.call_tool_assert_success(
session,
"codemcp",
{
"subtool": "EditFile",
"path": test_file_path,
"old_string": "Modified content for whitespace test - edit 1",
"new_string": "Modified content for whitespace test - edit 2",
"description": "Second whitespace test edit",
"chat_id": chat_id,
},
)
# Get the commit message with hash and HEAD
commit_msg = await self.git_run(
["log", "-1", "--pretty=%B"], capture_output=True, text=True
)
# Print the commit message for debugging
print(f"Commit message after second edit:\n{commit_msg}")
# Extract lines with hash and HEAD
hash_line = None
head_line = None
for line in commit_msg.splitlines():
if first_commit_hash in line:
hash_line = line
elif "HEAD" in line:
head_line = line
self.assertIsNotNone(
hash_line, "Could not find commit hash line in message"
)
self.assertIsNotNone(head_line, "Could not find HEAD line in message")
# Instead of trying to find the descriptive text (which won't work for base revision),
# let's check the alignment by verifying the spacing before the hash and HEAD
# Extract the prefix before commit hash (should be consistent)
hash_prefix = hash_line.split(first_commit_hash)[0]
# Extract the prefix before "HEAD"
head_prefix = head_line.split("HEAD")[0]
# Spacing after the hash and HEAD should be consistent
hash_spacing_after = hash_line[
len(hash_prefix) + len(first_commit_hash) :
].split("(Base")[0]
head_spacing_after = head_line[len(head_prefix) + 4 :].split("Second")[
0
] # 4 is length of "HEAD"
print(f"Hash prefix: '{hash_prefix}', HEAD prefix: '{head_prefix}'")
print(
f"Hash spacing after: '{hash_spacing_after}', HEAD spacing after: '{head_spacing_after}'"
)
# Verify HEAD has proper padding to align with hash
# The prefixes should be the same (same starting column)
self.assertEqual(
hash_prefix,
head_prefix,
f"Hash and HEAD are not aligned at the start. Hash prefix: '{hash_prefix}', HEAD prefix: '{head_prefix}'",
)
# Third edit to check multiple aligned entries
await self.call_tool_assert_success(
session,
"codemcp",
{
"subtool": "EditFile",
"path": test_file_path,
"old_string": "Modified content for whitespace test - edit 2",
"new_string": "Modified content for whitespace test - edit 3",
"description": "Third whitespace test edit",
"chat_id": chat_id,
},
)
# Get the second commit hash
await self.git_run(
["rev-parse", "--short", "HEAD"], capture_output=True, text=True
)
# Get the updated commit message with multiple entries
final_commit_msg = await self.git_run(
["log", "-1", "--pretty=%B"], capture_output=True, text=True
)
print(f"Commit message after third edit:\n{final_commit_msg}")
# Verify alignment using regex to extract positions
lines = final_commit_msg.splitlines()
# Get all lines containing either a commit hash or HEAD
hash_lines = [line for line in lines if re.search(r"[0-9a-f]{7}", line)]
head_line = next((line for line in lines if "HEAD" in line), None)
# Check that we have at least two hash lines (base revision and second edit) and one HEAD line
self.assertGreaterEqual(
len(hash_lines), 2, "Expected at least two hash lines in the message"
)
self.assertIsNotNone(head_line, "Could not find HEAD line in message")
# Extract positions where descriptions start
desc_positions = []
for line in hash_lines:
# Find where the description or "(Base revision)" starts
if "(Base revision)" in line:
desc_positions.append(line.find("(Base"))
else:
# Find the first alphabetic character after the hash
match = re.search(r"[0-9a-f]{7}\s+([A-Za-z])", line)
if match:
desc_positions.append(line.find(match.group(1)))
# Find where the description starts in the HEAD line
head_desc_pos = None
match = re.search(r"HEAD\s+([A-Za-z])", head_line)
if match:
head_desc_pos = head_line.find(match.group(1))
self.assertIsNotNone(
head_desc_pos, "Could not find description position in HEAD line"
)
# Verify all description positions are aligned
for i, pos in enumerate(desc_positions):
self.assertEqual(
pos,
head_desc_pos,
f"Description alignment mismatch at position {i}. Expected {head_desc_pos}, got {pos}",
)
if __name__ == "__main__":
unittest.main()