How can I easily fix a past commit?

I just read the change of one file in the past in git , but unfortunately the decision made reorders the commits, which I don’t want. So here is my question:

From time to time, I notice an error in my code while working on a (unrelated) function. A quick git blame then shows that the error was introduced a few months ago (I do quite a lot, so this is usually not the last commit that introduced the error). At this point, I usually do this:

 git stash # temporarily put my work aside git rebase -i <bad_commit>~1 # rebase one step before the bad commit # mark broken commit for editing vim <affected_sources> # fix the bug git add <affected_sources> # stage fixes git commit -C <bad_commit> # commit fixes using same log message as before git rebase --continue # base all later changes onto this 

However, this happens so often that the above sequence is annoying. Especially the "interactive rebase" is boring. Is there a shortcut to the above sequence that allows me to change the arbitrary commit in the past with incremental changes? I understand very well that this is changing the story, but I often make the mistake that I really like to have something like

 vim <affected_sources> # fix bug git add -p <affected_sources> # Mark my 'fixup' hungs for staging git fixup <bad_commit> # amend the specified commit with staged changes, # rebase any successors of bad commit on rewritten # commit. 

Maybe a smart script that can rewrite captures the use of plumbing tools or something?

+99
git rewrite
Jun 23 '10 at 16:32
source share
11 answers

UPDATED RESPONSE

Some time ago, a new argument --fixup was added to git commit , which can be used to build a commit with a log message suitable for git rebase --interactive --autosquash . So, the easiest way to fix a past commit is:

 $ git add ... # Stage a fix $ git commit --fixup=a0b1c2d3 # Perform the commit to fix broken a0b1c2d3 $ git rebase -i --autosquash a0b1c2d3~1 # Now merge fixup commit into broken commit 

ORIGINAL RESPONSE

Here is a little Python script that I wrote some time ago that implements this git fixup logic that I was hoping for in my original question. The script assumes that you have made some changes, and then apply these changes to the commit data.

NOTE : This script is for Windows; it searches for git.exe and sets the GIT_EDITOR environment variable using set . Adjust this as necessary for other operating systems.

Using this script, I can accurately implement the "fixed patched sources, phase fixes, run the git fixup workflow", which I requested:

 #!/usr/bin/env python from subprocess import call import sys # Taken from http://stackoverflow.com/questions/377017/test-if-executable-exists-in python def which(program): import os def is_exe(fpath): return os.path.exists(fpath) and os.access(fpath, os.X_OK) fpath, fname = os.path.split(program) if fpath: if is_exe(program): return program else: for path in os.environ["PATH"].split(os.pathsep): exe_file = os.path.join(path, program) if is_exe(exe_file): return exe_file return None if len(sys.argv) != 2: print "Usage: git fixup <commit>" sys.exit(1) git = which("git.exe") if not git: print "git-fixup: failed to locate git executable" sys.exit(2) broken_commit = sys.argv[1] if call([git, "rev-parse", "--verify", "--quiet", broken_commit]) != 0: print "git-fixup: %s is not a valid commit" % broken_commit sys.exit(3) if call([git, "diff", "--staged", "--quiet"]) == 0: print "git-fixup: cannot fixup past commit; no fix staged." sys.exit(4) if call([git, "diff", "--quiet"]) != 0: print "git-fixup: cannot fixup past commit; working directory must be clean." sys.exit(5) call([git, "commit", "--fixup=" + broken_commit]) call(["set", "GIT_EDITOR=true", "&&", git, "rebase", "-i", "--autosquash", broken_commit + "~1"], shell=True) 
+147
Sep 30 '10 at 8:10
source share

What am I doing:

 git add ... # Add the fix.
 git commit # Committed, but in the wrong place.
 git rebase -i HEAD ~ 5 # Examine the last 5 commits for rebasing.

Your editor will open with a list of the last 5 commits ready to intervene. Change:

 pick 08e833c Good change 1.
 pick 9134ac9 Good change 2.
 pick 5adda55 Bad change!
 pick 400bce4 Good change 3.
 pick 2bc82n1 Fix of bad change.

... at:

 pick 08e833c Good change 1.
 pick 9134ac9 Good change 2.
 pick 5adda55 Bad change!
 f 2bc82n1 Fix of bad change.  # Move up, and change 'pick' to 'f' for 'fixup'.
 pick 400bce4 Good change 3.

Save and exit your editor, and the fix will be returned to the commit to which it belongs.

After you have done this several times, you will do it in a few seconds in a dream. Interactive reboot is the feature that really sold me on git. It is incredibly useful for this and more ...

+29
Sep 29 '10 at 19:56
source share

A bit late for the party, but here is a solution that works as the author suggested.

Add this to your .gitconfig:

 [alias] fixup = "!sh -c '(git diff-files --quiet || (echo Unstaged changes, please commit or stash with --keep-index; exit 1)) && COMMIT=$(git rev-parse $1) && git commit --fixup=$COMMIT && git rebase -i --autosquash $COMMIT~1' -" 

Usage example:

 git add -p git fixup HEAD~5 

However, if you have unspecified changes, you must hide them before rebase.

 git add -p git stash --keep-index git fixup HEAD~5 git stash pop 

You can automatically change the alias so that it is automatically, instead of giving a warning. However, if the fix is ​​not applied cleanly, you will need to manually pull the cache after resolving the conflicts. Performing both a save and a drop down manually seems more consistent and less confusing.

+19
Jan 15 '14 at 21:50
source share

To fix one commit:

 git commit --fixup a0b1c2d3 . git rebase --autosquash -i HEAD~2 

where a0b1c2d3 is the commit for which you want to fix, and where 2 is the number of inserted +1 commits that you want to change.

Note: git rebase --autosquash does not work without -i, but works with -i, which is strange.

+9
Nov 24 '15 at 4:07
source share

UPDATE: Now you can find a cleaner version of the script: https://github.com/deiwin/git-dotfiles/blob/docs/bin/git-fixup .

I was looking for something similar. This Python script seems too complicated, so I put together my own solution:

Firstly, my git aliases look like this (borrowed from here ):

 [alias] fixup = !sh -c 'git commit --fixup=$1' - squash = !sh -c 'git commit --squash=$1' - ri = rebase --interactive --autosquash 

Now the bash function is becoming quite simple:

 function gf { if [ $# -eq 1 ] then if [[ "$1" == HEAD* ]] then git add -A; git fixup $1; git ri $1~2 else git add -A; git fixup $1; git ri $1~1 fi else echo "Usage: gf <commit-ref> " fi } 

This code first includes all current changes (you can delete this part if you want to create files yourself). Then a patch is created (squash can also be used if necessary). After that, it starts an interactive reboot with the --autosquash flag at the parent of the command that you give as an argument. This will open your configured text editor so you can make sure everything is as you expect, and just closing the editor will complete the process.

The if [[ "$1" == HEAD* ]] (taken from here ) is used because if you use, for example, HEAD ~ 2 as your commit (the commit that you want to fix the current changes with), then HEAD will be biased after commit commit, and you will need to use HEAD ~ 3 to reference the same commit.

+6
Dec 02 '12 at 17:34
source share

You can avoid the interactive step by using the null editor:

 $ EDITOR=true git rebase --autosquash -i ... 

Instead of /usr/bin/vim , /bin/true will be used as an editor. It always accepts any git offers, without a hint.

+4
Nov 07 '16 at 10:52
source share

What really bothered me about the fixup workflow was that I had to figure out which one I want this change to hop every time. I created a git fixup command that helps with this.

This command creates a fixup commit, with added magic, which uses git-deps to automatically find the appropriate commit, so a frequent workflow comes down to:

 # discover and fix typo in a previously committed change git add -p # stage only typo fix git fixup # at some later point squash all the fixup commits that came up git rebase --autosquash master 

This only works if phased changes can be unambiguously attributed to a specific commit on the working tree (between the master and HEAD). I find this very often for the type of small changes that I use for this, for example. typos in comments or names of recently introduced (or renamed) methods. If it is not, it will at least display a list of candidate commits.

I use this a lot in my daily workflow to quickly integrate small changes into previously modified rows in a commit on my work branch. The script is not as beautiful as it could be, and it is written in zsh, but it did a good job of me for me, but I never felt the need to rewrite it:

https://github.com/Valodim/git-fixup

+3
Jul 20 '17 at 8:27
source share

commit --fixup and rebase --autosquash are great, but not enough. When I have an ABC commit sequence and I write a few more changes in my working tree that belong to one or more of the existing commits, I have to manually look at the history, decide which changes belong in which they make, stage them and create them fixing fixup! . But git already has access to enough information to be able to do all this for me, so I wrote a Perl script that does just that.

For each column in the git diff script, git blame is used to find the commit that last touched the corresponding rows and call git commit --fixup to write the corresponding fixup! commits essentially doing the same thing as I did before.

If you find this useful, please feel free to improve and repeat it, and maybe one day we will get such a function in git . I would like to see a tool that can understand how a merge conflict should be resolved when it was introduced using interactive rebase.

+1
Mar 30 '16 at 17:48
source share

I wrote a small shell function called gcf to do fix fix and auto rebase:

 $ git add -p ... select hunks for the patch with y/n ... $ gcf <earlier_commit_id> That commits the fixup and does the rebase. Done! You can get back to coding. 

For example, you can set the second commit to the last with: gcf HEAD~~

Here is the function . You can paste it into your ~/.bashrc

 git_commit_immediate_fixup() { local commit_to_amend="$1" if [ -z "$commit_to_amend" ]; then echo "You must provide a commit to fixup!"; return 1 fi # Get a static commit ref in case the commit is something relative like HEAD~ commit_to_amend="$(git rev-parse "${commit_to_amend}")" || return 2 #echo ">> Committing" git commit --no-verify --fixup "${commit_to_amend}" || return 3 #echo ">> Performing rebase" EDITOR=true git rebase --interactive --autosquash --autostash \ --preserve-merges "${commit_to_amend}~" } alias gcf='git_commit_immediate_fixup' 

It uses --autostash to store and retrieve any uncommitted changes, if necessary.

--autosquash requires relocation of --interactive , but we avoid interaction using the EDITOR dummy.

+1
Nov 07 '16 at 10:55
source share

I don’t know about the automatic method, but here is a solution that could most easily be made by a bot-man:

 git stash # write the patch git add -p <file> git commit -m"whatever" # message doesn't matter, will be replaced via 'fixup' git rebase -i <bad-commit-id>~1 # now cut&paste the "whatever" line from the bottom to the second line # (ie below <bad-commit>) and change its 'pick' into 'fixup' # -> the fix commit will be merged into the <bad-commit> without changing the # commit message git stash pop 
0
Aug 11 '10 at 9:02
source share

You can create a patch for a specific file using this alias.

 [alias] ... # fixup for a file, using the commit where it was last modified fixup-file = "!sh -c '\ [ $(git diff --numstat $1 | wc -l) -eq 1 ] && git add $1 && \ [ $(git diff --cached --numstat $1 | wc -l) -eq 1 ] || (echo No changes staged. ; exit 1) && \ COMMIT=$(git log -n 1 --pretty=format:"%H" $1) && \ git commit --fixup=$COMMIT && \ git rebase -i --autosquash $COMMIT~1' -" 

If you made some changes to myfile.txt but don’t want to put them in a new commit, git fixup-file myfile.txt will create a fixup! to commit where myfile.txt was last modified, and then it will rebase --autosquash .

0
Oct 19 '15 at 19:41
source share



All Articles