Say I introduced <feature.c> a while ago and now notice it shouldn't have been part of my main
branch but rather a branch feature
. Is it possible to use e.g. git-filter-branch
to automatically move all of <feature.c>'s history out of my main
branch into the feature
branch?
问题:
回答1:
It sounds like you're doing something fairly insane! :)
That said, I see a few options, none of which are particularly automated.
If you've got a ton of commits with that file present, just admit the mistake, make a new branch off of your HEAD, and put further commits with that feature into that branch until they're stable. If you're repo's shared, this becomes the only real option. No one wants to have divergent history, especially if that feature was committed deep in the history.
If you're only talking about 10 or so commits that have actually touched that file, and they're fairly recently committed without many other commits interleaved, you could check out a new branch on HEAD, and revert the branch you don't want this feature in back to before you added the feature, and then cherry pick commits you need out of the feature branch until you're ready to commit them all in at a later date.
If you're dealing with a ton of history, lots of interleaved commits, and you really don't want to have that feature present at all, you could write up a little shell script that takes the output of
git log
and cherry picks it into a new branch. Something along the lines of:$ cd git-repo $ git checkout -b feature-x $ the-perfect-shell-script `git log --pretty=format:"%H" path/to/feature.c`
Once you have that feature branch with all of the commits cherry picked out, you can then use
git filter-branch
to filter out all of the commits that reference that file. The man page has a simple example that does exactly that.Once you've got that, you can then
git rebase feature-x --onto <filtered-branch>
and you should be good to go.Of course that should be quite discouraged, especially if any of that is published.
回答2:
Ok, here's my go:
Had I not commits manipulating <feature.c>, I could have branched of from <feature.c>'s first commit and then used git cherry-pick
with git log
in a loop, as suggested by Tim Visher. Starting from master I guess this should work:
#!/bin/bash
# create the feature branch starting from feature.c's first commit
FIRSTCOMMIT=$(git log --pretty=format:"%H" feature.c | tail -n1)
git checkout -b feature $FIRSTCOMMIT
# find all commits concerning feature.c...
for i in $(git log feature..master --reverse --pretty=format:"%H" feature.c)
do
# ... cherry-pick them ...
git cherry-pick $i
# ... and copy ONE modified tag of it if existing
git describe --tags --exact-match $i && xargs taghelperscript
done
# now eliminate feature.c from master
git filter-branch --prune-empty --tag-name-filter "cat" --index-filter 'git rm --cached --ignore-unmatch feature.c' $FIRSTCOMMIT..master
With taghelperscript
being something like git tag prefix.$1
(maybe this can be done better?). The tagging part probably only works for the lightweight tags I use. Also be advised that this does not work if <feature.c> has been renamed at some point, and if it existed already in the initial commit this might either cause two separate histories, or (my guess) a commit in master
which contains the deletion of <feature.c> which is likely to cause a merge conflict or confusion later.
The trouble is, some of my commits modify other files, thus causing git cherry-pick
to trigger an unresolved merge, or introduces these other files. So instead, I'll try some git filter-branch
magic. Later. Stay tuned...