How can I easily fixup a past commit?

git, url-rewriting

Solution

UPDATED ANSWER

A while ago, a new `--fixup` argument was added to `git commit` which can be used to construct a commit with a log message suitable for `git rebase --interactive --autosquash`. So the simplest way to fixup a past commit is now:

$ 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 ANSWER

Here's a little Python script I wrote a while ago which implements this `git fixup` logic I hoped for in my original question. The script assumes that you staged some changes and then applies those changes to the given commit.

NOTE: This script is Windows-specific; it looks for `git.exe` and sets the `GIT_EDITOR` environment variable using `set`. Adjust this as needed for other operating systems.

Using this script I can implement precisely the 'fix broken sources, stage fixes, run git fixup ' workflow I asked for:

#!/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)

Problem

I just read amending a single file in a past commit in git but unfortunately the accepted solution 'reorders' the commits, which is not what I want. So here's my question: Every now and then, I notice a bug in my code while working on an (unrelated) feature. A quick `git blame` then reveals that the bug has been introduced a few commits ago (I commit quite a lot, so usually it's not the most recent commit which introduced the bug). 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 getting annoying. Especially the 'interactive rebase' is boring. Is there any shortcut to the above sequence, which lets me amend an arbitrary commit in the past with the staged changes? I'm perfectly aware that this changes the history, but I'm doing mistakes so often that I'd really love 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 which can rewrite commits using plumbing tools or so?

Original source

Related problems