After I remove several lines from a git-versioned file, instead of making a new commit that removes those lines, is there a git command(s) that just strips those lines from the commits that originally introduced them? For example, given this file:

$ echo "Line 1" >> foo
$ echo "Line 2" >> foo
$ git commit -a -m "Add 1, 2"
[master 1005cbb] Add 1, 2
 1 file changed, 2 insertions(+)

$ echo "Line 3" >> foo
$ git commit -a -m "Add 3"
[master 5036981] Add 3
 1 file changed, 1 insertion(+)

$ echo "Line 4" >> foo
$ echo "Line 5" >> foo
$ git commit -a -m "Add 5, 6"
[master 41ca6ab] Add 5, 6
 1 file changed, 2 insertions(+)

$ git blame -s foo
1005cbbb 1) Line 1
1005cbbb 2) Line 2
50369811 3) Line 3
41ca6ab6 4) Line 4
41ca6ab6 5) Line 5

Say I remove lines 2-4:

$ sed -i '/Line [234]/d' foo
$ git diff
diff --git a/foo b/foo
index 572d5d9..e486b1b 100644
--- a/foo
+++ b/foo
@@ -1,5 +1,2 @@
 Line 1
-Line 2
-Line 3
-Line 4
 Line 5

Now instead of committing that change, I want to run something that just removes those lines from the history entirely, resulting in a new tree where 1005cbb (or whatever the new hash is) just adds "Line 1", 5036981 is empty (or gone), and 41ca6ab adds "Line 5".

Is this possible? The closest thing I can think of is to remove each line separately and git commit --fixup hash-of-the-commit-that-added-that-line before rebasing, but I'm curious if there's a better built-in method before I try to automate doing it that way.


This is what git filter-branch is for. It runs a command on every revision of the history and recommits the result, changed or not. It will change all hashes of every commit following the first modified revision, making the new branch incompatible with any clones.

If sed -i '/Line [234]/d' foo is the command that makes the modification you want, you could apply it as a tree filter:

git filter-branch --tree-filter "sed -i '/Line [234]/d' foo" HEAD

It's generally easier to put anything complicated into its own script. The filtering command is run from another directory, so give the full path.

There are a lot of filtering modes. Tree filters modify the files themselves, and can be combined with other filters to change commit messages or details, parents, tags, and so on. Tree filters behave like you ran git add -f * in a new repository - every file that exists gets added with its current content, disregarding any local or global ignores, and any file that no longer exists is removed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.