Wednesday, 17 July 2024

Git fix the "missed" rename

In git, there are no renames. It uses "heuristics" to detect files been deleted-and-added that are "actually" renamed. The "good practice" is to never mix these "renames" with file modifications, else git will loose the track of "renames" and thus history navigation for these files will become a serious pain. But sometimes you "forget" about these "great" practices. The script to fix an accidental loss of real rename below is to the resque.

#!/bin/bash

# Check if all required arguments are provided
if [ $# -ne 4 ]; then
    echo "Usage: $0    "
    exit 1
fi

REPO_PATH="$1"
COMMIT_HASH="$2"
OLD_FILE="$3"
NEW_FILE="$4"

# Change to the repository directory
cd "$REPO_PATH" || exit 1

# Verify if the commit exists
if ! git rev-parse --quiet --verify "$COMMIT_HASH^{commit}" >/dev/null; then
    echo "Error: Commit $COMMIT_HASH does not exist"
    exit 1
fi

# Stash any untracked files
git stash push --include-untracked --message "Stashing untracked files before rebase"

# Get the hash of the commit following the target commit
NEXT_COMMIT_HASH=$(git rev-list --topo-order --reverse "$COMMIT_HASH"..HEAD | head -1)

# Memorize the "new state" of the added file in the target commit
git show "$COMMIT_HASH:$NEW_FILE" > temp_new_file

# Use GIT_SEQUENCE_EDITOR to modify the rebase todo list non-interactively
export GIT_SEQUENCE_EDITOR="sed -i '1s/^pick/edit/'"

# Start the rebase process
git rebase -i "$COMMIT_HASH^"

# Remove the new file and add the old file to simulate rename
git rm "$NEW_FILE"
git show "$COMMIT_HASH^:$OLD_FILE" > "$OLD_FILE"
git add -f "$OLD_FILE"

# Perform the rename
git mv "$OLD_FILE" "$NEW_FILE"

# Amend the commit without changing the commit message or author date
git commit --amend --no-edit --date="$(git show -s --format=%aI $COMMIT_HASH)"

# Get the author date of the target commit
TARGET_DATE=$(git show -s --format=%ai "$COMMIT_HASH")

echo "Target commit author date: $TARGET_DATE"

# Get the author date of the commit following the target commit
NEXT_DATE=$(git show -s --format=%ai "$NEXT_COMMIT_HASH")

echo "Next commit author date: $NEXT_DATE"

# Calculate the middle date between the target commit and the next commit
MIDDLE_DATE=$(date -d "$TARGET_DATE +$((($(date -d "$NEXT_DATE" +%s) - $(date -d "$TARGET_DATE" +%s)) / 2)) seconds" +"%Y-%m-%d %H:%M:%S %z")

echo "Middle date: $MIDDLE_DATE"

# Create a new commit following right after the target commit with the middle date
mv temp_new_file "$NEW_FILE"
git add "$NEW_FILE"
GIT_AUTHOR_DATE="$MIDDLE_DATE" GIT_COMMITTER_DATE="$MIDDLE_DATE" git commit -m "Update content of $NEW_FILE"

# Continue the rebase with preserved author dates
git -c rebase.instructionFormat='%s%nexec GIT_COMMITTER_DATE="$(git show -s --format=%aI)" git commit --amend --no-edit --date="$(git show -s --format=%aI)"' rebase --continue

# Restore the stashed untracked files
git stash pop

# Clean up
rm -f temp_new_file

echo "Operation completed successfully."
🤡

No comments:

Post a Comment