title: Removing files from your Git history exclusively between two commits
body: <p>I am trying to remove a number of large files from my history using <code>filter-branch</code>. I have used this command before with success but I'm currently having trouble for a particular edge case.</p>
  
  <p>The problem is that these large files were never truly removed but <strong><em>replaced by smaller versions with the same path</em></strong>.</p>
  
  <p>As far as I can tell, I believe I have a unique problem.</p>
  
  <h2>Git Log</h2>
  
  <p>To elaborate, here is a rudimentary representation of my repo:</p>
  
  <pre><code>----- A ------ B ----------- HEAD
  </code></pre>
  
  <p>where:</p>
  
  <pre><code>A is the commit where the large files were introduced
  B is the commit (about 30 later) where the large files were replaced with smaller ones
  HEAD is thousands of commits forward of B (~2 years of active development)
  </code></pre>
  
  <h2>Git Filter-Branch</h2>
  
  <p>In theory, I should be able to do something like this:</p>
  
  <pre><code>git filter-branch --index-filter 'git rm --cached --ignore-unmatch filenames' &lt;parent of A&gt;..B 
  </code></pre>
  
  <p>I believe I should use <code>&lt;parent of A&gt;</code> because <code>filter-branch</code> is not inclusive. (I'm not sure if I also need to use the parent of B also but this is the least of my worries right now).</p>
  
  <p>Running this gives me the error:</p>
  
  <pre><code>$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch filenames' &lt;parent of A&gt;..B 
  Which ref do you want to rewrite?
  </code></pre>
  
  <p>So I included <code>--glob="refs/heads/master*"</code> at the end of the command which seems to do the trick (<a href="https://gist.github.com/mike-perdide/775263" rel="nofollow">source</a>).</p>
  
  <p>Once execution has finished, the files have been completely removed - it seems that git ignores the upper bound that I specified.</p>
  
  <p>So I'm wondering if this method is even possible?</p>
  
  <h2>Alternative approaches</h2>
  
  <p>I thought I should list some other ideas I've had so that potential answers can be focussed on solving the problem.</p>
  
  <ol>
  <li>The pragmatic approach would be to commit a filename change at HEAD and then run <code>git filter-branch ... HEAD</code>. However, my repository has a number of branches in active development and I believe this method would be very messy.</li>
  <li>Another way could be to do something like what is described <a href="http://git.661346.n2.nabble.com/removing-files-from-history-but-not-filesystem-td6523784.html" rel="nofollow">here</a>. To quote: <code>create a temporary branch to point at HEAD^, filter-branch it, then add a graft to stitch the remaining commit on top of it, then filter-branch HEAD and then remove the graft.</code></li>
  </ol>
  
  <p>Hopefully someone has come across this problem before and can lend their expertise.</p>
  
  <h2>Update</h2>
  
  <p>The files I want to remove total <strong>~500MB</strong> so I'm understandably very keen to remove them! They were committed long before I joined the company and are a remnant of our move from an internal Mercurial server to GitHub (I suppose pushing 500MB to an internal server would be less noticeable than GitHub...).</p>
  
  <h2>Update 2</h2>
  
  <p>I've been following twalberg's second answer (I think I'm using it in the correct manner):</p>
  
  <pre><code>git filter-branch --index-filter '(( $(git rev-list &lt;SHA-of-child-of-B&gt; --not $GIT_COMMIT | wc -l) &gt; 0 )) &amp;&amp; git rm --cached --ignore-unmatch &lt;filenames&gt;' 
  </code></pre>
  
  <p>This produces the kind of output I'd expect:</p>
  
  <pre><code>...
  Rewrite dc8a4b29463bfa43c2f3efe0c6e5a29a5cc6e0ef (1071/5680)rm 'file1'
  rm 'file2'
  rm 'file3'
  rm 'file4'
  ...
  </code></pre>
  
  <p>Before ending in an (expected?) error:</p>
  
  <pre><code>Rewrite e6b712b57257e2edd0bb9fbbac59e4c9d7b5aa79 (1072/5680)index filter failed: (( $(git rev-list e6b712b --not $GIT_COMMIT | wc -l) &gt; 0 )) &amp;&amp; git rm -rf --ignore-unmatch &lt;filename&gt;
  </code></pre>
  
  <p>Where <code>e6b712b</code> is the child of <code>B</code>.</p>
  
  <p>At this point I assume everything has worked so I do a local filesystem clone of my repository to test it:</p>
  
  <pre><code>git clone file://&lt;repo&gt; &lt;new repo&gt;
  </code></pre>
  
  <p>The number of objects and packfile size have decreased a very small amount - I'm not sure why. By running <code>git count-objects -v</code> against the original repository versus the one that has had <code>filter-branch</code> run against it:</p>
  
  <p>Original repository:</p>
  
  <pre><code>count: 0
  size: 0
  in-pack: 106640
  packs: 1
  size-pack: 815512
  prune-packable: 0
  garbage: 0
  </code></pre>
  
  <p>The <code>filter-branch</code>ed and filesystem cloned repository:</p>
  
  <pre><code>count: 0
  size: 0
  in-pack: 96165
  packs: 1
  size-pack: 793656
  prune-packable: 0
  garbage: 0
  </code></pre>
  
  <p>I'm really not sure why this still isn't working - perhaps I'm not following the suggested answer correctly?</p>