13

Simple Version:

If I have a branch "foo-555", with a bunch of commits with messages like:

  • foo 555: blah
  • foo 123: blah blah
  • foo 555: blah blah blah
  • foo 321: blahblah

and I want to remove all the commits that don't start with "foo 555:", is there any way to do that using git filter-branch (or any other tool for that matter)?

Original Version (More Detailed):

In our repository we have a convention where every commit message starts with a certain pattern:

Redmine #555: SOME_MESSAGE

We also do a bit of rebasing to bring in the potential release branch's changes to a specific issue's branch. In other words, I might have branch "foo-555", but before I merge it in to branch "pre-release" I need to get any commits that pre-release has that foo-555 doesn't (so that foo-555 can fast-forward merge in to pre-release).

However, because pre-release sometimes changes, we sometimes wind up with situations where you bring in a commit from pre-release, but then that commit later gets removed from pre-release. It's easy to identify commits that came from pre-release, because the number from their commit message won't match the branch number; for instance, if I see "Redmine #123: ..." in my foo-555 branch, I know that its not a commit from my branch.

So now the question: I'd like to remove all of the commits that "don't belong" to a branch; in other words, any commit that:

  • Is in my foo-555 branch, but not in the pre-release branch (pre-release..foo-555)
  • Has a commit message that doesn't start with "Redmine #555"

but of course "555" will vary from branch to branch. Is there any way to use filter-branch (or any other tool) to accomplish this? Currently the only way I can see to do it is to do go an interactive rebase ("git rebase -i") and manually remove all the "bad" commits.

machineghost
  • 33,529
  • 30
  • 159
  • 234
  • Can you not cherry pick the commits you want into the relevant branch? – Jonathan Leffler Dec 30 '10 at 00:44
  • We *can*, but let's say I've got 10 555 commits and 10 other commits; I'd have to reset and then do 10 cherry picks (vs. one filter-branch command ... if such a thing is possible). – machineghost Dec 30 '10 at 01:11

3 Answers3

11

Here's a fast solution that uses filter-branch instead of rebasing. There's no interactivity or needing to resolve conflicts.

git filter-branch --commit-filter '
    if [ `git rev-list --all --grep "<log-pattern>" | grep -c "$GIT_COMMIT"` -gt 0 ]
    then
        skip_commit "$@";
    else
        git commit-tree "$@";
    fi'  HEAD

You'll probably want to then clean up with:

git reflog expire --expire=now
git gc --prune=now
Gingi
  • 2,149
  • 1
  • 18
  • 34
  • 3
    For large repositories with a lot of commits, this can take awhile (e.g., several seconds per commit filter on FreeBSD `-CURRENT`, which has hundreds of thousands of commits). A much quicker alternative for the `git rev-list --all [...]` condition is `git show $GIT_COMMIT | grep -c ""`. – trombonehero Jul 12 '17 at 18:12
  • This doesn't really solve the stated problem. `skip_commit` in `git filter-branch` actually *squashes* commits away, it doesn't remove their changes. To remove them, you need to use `git rebase -i` instead of `git filter-branch`. – Robin Green Oct 19 '18 at 10:18
5

Write a script to remove lines with Redmine #555:

#!/bin/sh

mv $1 $1.$$
grep -v 'Redmine #555' < $1.$$ > $1
rm -f $1.$$

Of course you can do that however you want (eg echo a script of commands to ed).

Then launch your rebase with EDITOR set to your script:

EDITOR=/path/to/script git rebase -i REVISION

Of course it still won't be guaranteed to complete -- there may be errors during the rebase caused by leaving out revisions. You can still fix them and git rebase --continue manually.

Tyler
  • 21,762
  • 11
  • 61
  • 90
Ben Jackson
  • 90,079
  • 9
  • 98
  • 150
  • This seems like it would work, but ... isn't there any way to just use Git (with no shell scripts) and make the process automated via a "git filter-branch --commit-filter"? – machineghost Jan 04 '11 at 22:26
  • 1
    The help for `--commit-filter` specifically mentions that `skip_commit` leaves out the commit but **not the changes** and says to use `git-rebase` instead. `filter-branch` considers your revisions as a sequence of states and allows you to permute each commit, but the changes to not propagate to the children. `rebase` considers your revisions as a stack of patches and any modification midstream *does* propagate to future revisions. But that can cause failures which is why it can't be fully automatic. – Ben Jackson Jan 04 '11 at 22:35
0

Based on Gingi answer but simplified if statement

git filter-branch --commit-filter '
if [[ $(git show -s --format=%B "$GIT_COMMIT") == "fix" ]]
then
    skip_commit "$@";
else
    git commit-tree "$@";
fi'  master

Note that after rewriting history you should delete all local and remote tags, otherwise you'll have gangling branches

poGUIst
  • 299
  • 4
  • 10