Skip to content

Instantly share code, notes, and snippets.

@slifty
Last active December 12, 2023 17:42
Show Gist options
  • Save slifty/68bd451896f6494b870280c00dc4fe9c to your computer and use it in GitHub Desktop.
Save slifty/68bd451896f6494b870280c00dc4fe9c to your computer and use it in GitHub Desktop.
Git alias to edit a commit

A git alias to edit the content of a commit

This git alias allows you to remove specific changes from a past commit / from git history and place those changes into your working directory, outside of your git history.

For example, maybe a code reviewer has identified a few files or lines that belong in their own commit or pull request. This helps you do git commit surgery on specific commits without needing to manually re-play.

git edit {commithash}

(e.g. git edit HEAD would edit the most recent commit or git edit c52b7bfe12c2f6082a69ea339eeec95a20532fa5 would edit a specific commit)

The Alias

On Linux:

edit = !"f() { EDITED_STASH=0 && GIT_SEQUENCE_EDITOR=\"sed -i '1s/^pick/edit/'\" git rebase -i \"$1~\" && git reset HEAD~1 && git add -AN && git add -p && git commit -e -C HEAD@{1} && if [ -z \"$(git status --porcelain)\" ]; then git rebase --continue; else git add -A && git stash -q && EDITED_STASH=1 && git rebase --continue && git stash pop -q && EDITED_STASH=0 && git reset; fi || (echo 'Aborting the edit...' && if [ \"$EDITED_STASH\" = \"1\" ]; then git stash drop -q; fi && git rebase --abort && git add -A && git reset); }; f"

On OSX (sed -i requires a '' parameter)

edit = !"f() { EDITED_STASH=0 && GIT_SEQUENCE_EDITOR=\"sed -i '' '1s/^pick/edit/'\" git rebase -i \"$1~\" && git reset HEAD~1 && git add -AN && git add -p && git commit -e -C HEAD@{1} && if [ -z \"$(git status --porcelain)\" ]; then git rebase --continue; else git add -A && git stash -q && EDITED_STASH=1 && git rebase --continue && git stash pop -q && EDITED_STASH=0 && git reset; fi || (echo 'Aborting the edit...' && if [ \"$EDITED_STASH\" = \"1\" ]; then git stash drop -q; fi && git rebase --abort && git add -A && git reset); }; f"

Installing

Since the alias is so long, I recommend creating a ~/.gitconfig file and adding the alias using your preferred editor:

[alias]
...
{insert_alias_code_here}

How it works

The alias does the following...

  1. Populate a critical status tracking variable (this is used to make sure the stash is kept consistent)
EDITED_STASH=0
  1. Start an interactive rebase to the specified commit, replacing pick with edit for the first line (i.e. specifying edit for the passed in commit hash).
GIT_SEQUENCE_EDITOR=\"sed -i '1s/^pick/edit/'\" git rebase -i \"$1~\"
  1. Remove the staged commit content
git reset HEAD~1
  1. Mark all new files as being intended to commit (this allows you to selectively include only a portion of the new file.)
git add -AN
  1. Perform a patch add against the original commit content. This is where you can decide what NOT to include.
git add -p
  1. Perform the commit with only the patch edited changes, using the original commit message (and allowing you to edit that message).
git commit -e -C HEAD@{1}
  1. Check to see if there is anything sitting around NOT staged for commit (check to see if an edit actually happened)
if [ -z \"$(git status --porcelain)\" ];
  1. If there were no changes (if git status --porcelain returned ""), then continue the rebase.
then git rebase --continue;
  1. Otherwise there are changes left over which have been edited out of the commit. We need to:
  • Stage all of those changes.
else git add -A
  • Stash those changes (and denote that we have stashed)
git stash -q && EDITED_STASH=1
  • Invoke the rebase
git rebase --continue
  • Pop the changes into our working directory (and register that the stash is clean)
git stash pop --quiet && EDITED_STASH=0
  • Unstage the changes
git reset;
  1. And finally, if anything goes wrong at any step of the way, we want to abort the rebase and clean up any lingering git cache and stash changes, as if none of this ever happened.
(echo 'Aborting the edit...' && if [ \"$EDITED_STASH\" = \"1\" ]; then git stash drop -q; fi && git rebase --abort && git add -A && git reset); }; f"

Some notes

  • It isn't currently possible to edit away a deleted file, though it is possible to edit away an added file.

  • If you remove all changes from a commit the edit will be aborted.

  • If your edits cause a later merge conflict (for instance if you remove a file from a commit that is edited later in your rebase) then the edit will be aborted.

  • If you ctrl-C out of the git add patch view, the alias will stop short (and you will need to manually type git rebase --abort.

@zimmski
Copy link

zimmski commented Dec 12, 2023

Note that there is some off-site commentary about this gist.

What does it say? It is behind a login.

@slifty
Copy link
Author

slifty commented Dec 12, 2023

@zimmski Sorry about that -- login is public / free on that server but also I raised a request with the server admins to allow for public access streams. In the mean time I can summarize that it was mostly me and kfogel complaining about sed parameters 😅

I'm not sure there is anything particularly critical to the actual functionality of the gist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment