Deskaid

  • codemcp
#!/usr/bin/env python3 import logging import subprocess from .git_parse_message import parse_message __all__ = [ "append_metadata_to_message", "update_commit_message_with_description", ] log = logging.getLogger(__name__) def append_metadata_to_message(message: str, metadata: dict) -> str: """Append trailers to Git commit message Args: message: The original Git commit message metadata: Dictionary containing trailers; only codemcp-id is used Returns: The updated commit message with trailers added """ return subprocess.check_output( [ "git", "interpret-trailers", *[f"--trailer={k}: {v}" for k, v in metadata.items()], ], input=message.encode("utf-8"), ).decode("utf-8") def update_commit_message_with_description( current_commit_message: str, description: str, commit_hash: str, ) -> str: """Update a commit message with a new description. This function handles the message munging logic for commit messages: - Updates the message by adding the description as a new entry - Uses marker tokens ```git-revs and ``` to demarcate commit list - Handles base revision markers and HEAD alignment Args: current_commit_message: The current Git commit message to update description: Description of the amend we're doing commit_hash: The current commit hash (used for updating HEAD entries and base revision) chat_id: The chat ID to ensure is in the metadata Returns: The updated commit message with the new description """ # Define marker tokens for the commit list START_MARKER = "```git-revs" END_MARKER = "```" subject, main_message, trailers = parse_message(current_commit_message) # Extract the content parts (before, between, and after markers) start_marker_pos = main_message.find(START_MARKER) end_marker_pos = ( main_message.find(END_MARKER, start_marker_pos + len(START_MARKER)) if start_marker_pos != -1 else -1 ) if start_marker_pos != -1 and end_marker_pos != -1: # Markers exist, extract the parts message_before = main_message[:start_marker_pos] rev_list_content = main_message[ start_marker_pos + len(START_MARKER) : end_marker_pos ].strip() message_after = main_message[end_marker_pos + len(END_MARKER) :] # Parse the revision list rev_entries = [] if rev_list_content: rev_entries = [ line.strip() for line in rev_list_content.splitlines() if line.strip() ] # Process rev_entries: replace any HEAD entries with actual commit hash has_base_revision = False new_rev_entries = [] for entry in rev_entries: if "(Base revision)" in entry: has_base_revision = True if entry.startswith("HEAD"): if commit_hash: # Replace HEAD with commit hash head_pos = entry.find("HEAD") head_len = len("HEAD") # Calculate the difference in length len_diff = len(commit_hash) - head_len # Replace HEAD with commit hash prefix = entry[:head_pos] suffix = entry[head_pos + head_len :] # Adjust spacing if needed if len_diff > 0 and suffix.startswith(" " * len_diff): suffix = suffix[len_diff:] new_entry = prefix + commit_hash + suffix new_rev_entries.append(new_entry) else: new_rev_entries.append(entry) # Determine if we need to add a base revision marker if not has_base_revision and commit_hash: new_rev_entries.append(f"{commit_hash} (Base revision)") # Add new HEAD entry if we have a description if description and commit_hash: # Calculate padding to align with commit hash head_padding = " " * (len(commit_hash) - 4) # 4 is length of "HEAD" new_rev_entries.append(f"HEAD{head_padding} {description}") # Reconstruct the rev list content formatted_rev_list = "\n".join(new_rev_entries) # Reconstruct the full message with markers main_message = f"{message_before}{START_MARKER}\n{formatted_rev_list}\n{END_MARKER}{message_after}" else: # Check for old format without markers - handle HEAD entries in the message lines = main_message.splitlines() has_base_revision = any("(Base revision)" in line for line in lines) has_head_entry = any(line.strip().startswith("HEAD") for line in lines) if has_head_entry or has_base_revision: # Old format detected, convert to new format with markers new_rev_entries = [] filtered_lines = [] for line in lines: if "(Base revision)" in line or line.strip().startswith("HEAD"): # This is a revision entry, add to our rev entries if line.strip(): if line.strip().startswith("HEAD") and commit_hash: # Replace HEAD with commit hash head_pos = line.find("HEAD") head_len = len("HEAD") len_diff = len(commit_hash) - head_len prefix = line[:head_pos] suffix = line[head_pos + head_len :] if len_diff > 0 and suffix.startswith(" " * len_diff): suffix = suffix[len_diff:] new_line = prefix + commit_hash + suffix new_rev_entries.append(new_line.strip()) else: new_rev_entries.append(line.strip()) else: # Regular line, keep in message filtered_lines.append(line) # Determine if we need to add a base revision marker if not has_base_revision and commit_hash: new_rev_entries.append(f"{commit_hash} (Base revision)") # Add new HEAD entry if we have a description if description and commit_hash: head_padding = " " * (len(commit_hash) - 4) new_rev_entries.append(f"HEAD{head_padding} {description}") # Format regular message without revision entries filtered_message = "\n".join(filtered_lines) # Format revision list formatted_rev_list = "\n".join(new_rev_entries) # Create new message with markers if filtered_message: if filtered_message.endswith("\n\n"): main_message = f"{filtered_message}{START_MARKER}\n{formatted_rev_list}\n{END_MARKER}" elif filtered_message.endswith("\n"): main_message = f"{filtered_message}\n{START_MARKER}\n{formatted_rev_list}\n{END_MARKER}" else: main_message = f"{filtered_message}\n\n{START_MARKER}\n{formatted_rev_list}\n{END_MARKER}" else: main_message = f"{START_MARKER}\n{formatted_rev_list}\n{END_MARKER}" else: # No markers or HEAD entries, handle as normal if description: rev_entries = [] # If we have a commit hash, initialize with base revision if commit_hash: rev_entries.append(f"{commit_hash} (Base revision)") # Add HEAD entry with description head_padding = " " * (len(commit_hash) - 4) # 4 is length of "HEAD" rev_entries.append(f"HEAD{head_padding} {description}") else: # No commit hash, just add description without revision list if main_message and not main_message.endswith("\n"): main_message += "\n" main_message += description return main_message # Format the revision list with markers formatted_rev_list = "\n".join(rev_entries) # Add formatted revision list to the message if main_message: # Ensure proper spacing if main_message.endswith("\n\n"): main_message += ( f"{START_MARKER}\n{formatted_rev_list}\n{END_MARKER}" ) elif main_message.endswith("\n"): main_message += ( f"\n{START_MARKER}\n{formatted_rev_list}\n{END_MARKER}" ) else: main_message += ( f"\n\n{START_MARKER}\n{formatted_rev_list}\n{END_MARKER}" ) else: main_message = f"{START_MARKER}\n{formatted_rev_list}\n{END_MARKER}" return subject + "\n\n" + main_message + "\n\n" + trailers