50

I have cleaned up our Git repository quite a bit, we need to remove big parts from the history. I do this using:

git filter-branch --prune-empty --tree-filter 'rm -rf some_stuff'

The --prune-empty flag will remove commits that are left empty after the process, except commits with multiple parents (merge commits). Even if the branch being merged in contains absolutely nothing, and the merge adds nothing to the tree.

How do I also prune these empty merge commits from the history?

Bjarke Freund-Hansen
  • 28,728
  • 25
  • 92
  • 135

6 Answers6

51

This is superior to the rebase solution because it preserves committer info, committer dates, and non-empty merges of the original history.

git filter-branch --prune-empty --parent-filter \
    'sed "s/-p //g" | xargs -r git show-branch --independent | sed "s/\</-p /g"'

This is inspired by the same thread on the kernel mailing list than Lucas' solution. However it does not require Ruby and is a one-liner. It does require GNU versions of xargs and sed though.

raphinesse
  • 19,068
  • 6
  • 39
  • 48
  • 1
    Simplest solution for me, because no external scripts are needed. – krlmlr Nov 09 '16 at 09:42
  • 7
    On OSX `sed "s/-p //g" | xargs git show-branch --independent | sed "s/^/-p /g" | tr "\n" " "` appears to work for me – James EJ Jan 06 '17 at 05:01
  • 1
    Does not remove merge commits from bottom tree i.e. merge of two empty commits (each without ancestor) – Tomas Feb 07 '17 at 14:00
  • 3
    Another macOS option: `brew install gnu-sed findutils` so you can replace sed with gsed and xargs with gxargs fpr GNU goodness. – Dave Gregory Jul 28 '17 at 15:00
11

I needed to do this after running filter-branch on a copy of ssokolow/profile to separate out ssokolow/lap.

This did a decent job as an automatic "collapse away anything left vestigial by --prune-empty" command:

git rebase --root HEAD

(I needed the --root so it would replace the now-empty initial commit with the oldest one that still had content.)

ssokolow
  • 14,938
  • 7
  • 52
  • 57
  • 2
    Useful, though overkill in some situations. It's worth noting that this approach flattens *all* merges, not just the empty ones. – Joey Coleman Jul 29 '14 at 20:22
  • 1
    Note also that this resets GIT_COMMITTER_DATE as what git rebase does by default. – glen Nov 26 '14 at 16:45
  • @ElanRuusamäe: Do you know of a way to avoid that (i.e. keep the original commiter info)? – Thilo Sep 21 '15 at 12:13
  • 2
    @Thilo: Looking at the documentation for `git rebase` and `git am` (which git rebase delegates to), I think using `--committer-date-is-author-date` should work. I haven't tried yet myself, but I am about to. – eestrada Oct 20 '15 at 02:19
  • This `git rebase` command is asking me to fix hundreds of "*error: could not apply*". Is there a way to skip them all? – Cœur Nov 23 '15 at 14:40
  • You shouldn't be getting those errors. Something went horribly wrong. – ssokolow Nov 24 '15 at 21:49
  • "Could not apply" is a very common error in this scenario. It just means that it encountered a merge commit with a change on both branches, so it cannot rebase into a linear history. – Bryan Larsen Oct 06 '17 at 14:58
3

This looks like it worked for me: http://git.661346.n2.nabble.com/Removing-useless-merge-commit-with-quot-filter-branch-quot-td7356544.html

git filter-branch -f --prune-empty --parent-filter FULL_PATH_TO/rewrite_parent.rb master 

rewrite_parent.rb:

#!/usr/bin/ruby 
old_parents = gets.chomp.gsub('-p ', ' ') 

if old_parents.empty? then 
  new_parents = [] 
else 
  new_parents = `git show-branch --independent #{old_parents}`.split 
end 

puts new_parents.map{|p| '-p ' + p}.join(' ') 
Lucas Walter
  • 942
  • 3
  • 10
  • 23
1

The --prune-degenerate option in git-filter-repo can be used for this:

[Merge commits] can become degenerate with the pruning of other commits (having fewer than two parents, having one commit serve as both parents, or having one parent as the ancestor of the other.) If such merge commits have no file changes, they [will be] pruned.

git filter-repo --prune-degenerate always

Keep in mind that the above command will apply to the whole repo by default. You can specify --refs if you want to limit the impact.

Martin Valgur
  • 5,793
  • 1
  • 33
  • 45
0

So my solution is moving everything to sub-dir (historically) and then --subdirectory-filter

First step error combining git repositories into subdirs

I modified a bit the sh file:

#!/bin/bash

git ls-files -s | sed "s-\t-&temp_dir/-" | GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info

mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE" || true

run

git filter-branch --index-filter '~/doit.sh' HEAD

second step

git filter-branch --subdirectory-filter temp_dir --prune-empty

and push it.

Community
  • 1
  • 1
eligro
  • 785
  • 3
  • 13
  • 23
-2

I found a very cute little wrapper script around filter-branch here:

https://github.com/pflanze/chj-bin/blob/master/cj-git-filter-branch

Preconds and background here:

http://lists.q42.co.uk/pipermail/git-announce/2011-September/000486.html

rtn
  • 127,556
  • 20
  • 111
  • 121
  • It does not work. From `man git filter-branch`: "You can also use the git_commit_non_empty_tree "$@" instead of git commit-tree "$@" if you don’t wish to keep commits with a single parent and that makes no change to the tree." But merge commits have multiple parents. So it does the same as the `--prune-empty` flag. I tried it, and it does not prune the empty merge commits. :/ – Bjarke Freund-Hansen Mar 21 '12 at 12:36
  • Aah ok, damn. More research needed then. Brb. – rtn Mar 21 '12 at 12:45
  • Check out that wrapper script. – rtn Mar 21 '12 at 12:51
  • I am unable to get the perl script to work, I am getting a `Can't locate Chj/xperlfunc.pm in @INC ...` error. And if I google xperlfunc I don't find anything useful. Did you have success using this script? – Bjarke Freund-Hansen Mar 21 '12 at 13:07
  • Sorry forgot a link http://lists.q42.co.uk/pipermail/git-announce/2011-September/000486.html – rtn Mar 21 '12 at 13:21
  • Hi, yes, now it works beautifully. You should edit your answer with the information in the comments, and I'll accept it. Thanks a lot. :) – Bjarke Freund-Hansen Mar 21 '12 at 14:06