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.

2
  • Can you not cherry pick the commits you want into the relevant branch? Commented Dec 30, 2010 at 0: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). Commented Dec 30, 2010 at 1:11

3 Answers 3

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 
Sign up to request clarification or add additional context in comments.

2 Comments

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 "<log-pattern>".
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.
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.

2 Comments

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"?
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.
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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.