Reputation: 3099
I have a feature and a master branch, e.g. like this (where master has been merged twice into feature):
* 27e89b5 (HEAD -> feature) f4
|\
| * 6849a63 (master) 3
* | 3b78807 f3
* | e84e33f f2
|\ \
| |/
| * 90e6f74 2
* | 4e4025b f1
|/
* e4e0759 (tag: initial) 1
You can rebuild this MWE with the following commands:
git init
f() { echo "$1" > README.md && git add README.md && git commit -m "$1"; }
f 1
git tag initial
git checkout -b feature
f f1
git checkout master
f 2
git checkout feature
git merge master
f f2
f f3
git checkout master
f 3
git checkout feature
git merge master
f f4
When I'm on feature, I'd like to git rebase -i initial
to squash some of the feature branch commits. Git shows this:
pick 4e4025b f1
pick 90e6f74 2
pick 3b78807 f3
pick 6849a63 3
squash
ing only "f3" yields the following graph.
* be4387c (HEAD -> feature) 3
* a90d504 2
* 2afe0a4 f1
| * c447c7f (master) 3
| * efec11d 2
|/
* 661d8fc (tag: initial) 1
Questions:
Upvotes: 1
Views: 49
Reputation: 164809
Why doesn't git offer to squash "f2"?
Because f2
is a merge commit and you didn't ask to preserve merges. By default, git rebase
linearizes the history and eliminates merges. It's just stacking commits on top of the base as if they were fresh patches.
You can preserve merges with -p
and then you'll get all commits.
$ git rebase -i initial
pick bde29dc f1
pick c9a9c74 2
pick e5565c5 f2
pick 7f18c18 f3
pick ffdd62c 3
pick 35d8ae9 f4
# Rebase aa61de7..35d8ae9 onto aa61de7 (6 commands)
Can I only squash "f3" without further changes on the whole branch structure?
Yes, but whereas before you were squashing f3
into 2
, if you want to preserve the structure you have to squash f3
into f2
because that's the immediately preceding commit, topologically.
pick bde29dc f1
pick c9a9c74 2
pick e5565c5 f2
squash 7f18c18 f3
pick ffdd62c 3
pick 35d8ae9 f4
You'll wind up with this.
* (HEAD -> feature) f4
|\
| * (master) 3
* | f2
|\ \
| |/
| * 2
* | f1
|/
* (tag: initial) 1
Though it doesn't make much sense to squash a commit into a merge commit.
If you instead reorder the commits so you can squash f3
into 2
...
pick bde29dc f1
pick c9a9c74 2
squash 7f18c18 f3
pick e5565c5 f2
pick ffdd62c 3
pick 35d8ae9 f4
...you'll wind up with... something odd.
* (HEAD -> feature) f4
|\
| * 3
|/
* f3
* 2
* (tag: initial) 1
Attempting to move commits between branches with git rebase -i -p
is going to get confusing because git rebase -i
presents you with a linear view of a non-linear history. Pretending non-linear history is linear is where Git gets confusing.
But you probably shouldn't be worrying about preserving all that. Keeping Git history mostly linear makes everything much simpler. Here's how to do it.
Your history is probably the result of updating feature
with git merge master
. Here's a better way to see that.
1 -- 2 ------- 3 [master]
\ \ \
f1 - f2 - f3 - f4 [feature]
f2
and f4
are of no real value; they're updates from master
and contain no interesting content. They just make the history confusing. Only f1
and f3
contain actual content. You'd be better off eliminating those update merges with git rebase master
and getting a linear history.
$ git rebase master
1 - 2 - 3 [master]
\
f1 - f3 [feature]
Then everything is clear and simple. Continue to keep your history simple by updating branches with git rebase master
rather than git merge master
.
# After more updates with `git rebase master`
1 - 2 - 3 - 4 - 5 [master]
\
f1 - f3 - f5 - f6 [feature]
When you're ready to merge feature
into master
this is when you want a merge commit. Use git merge --no-ff feature
to force a merge commit and then delete feature
.
$ git merge --no-ff feature
$ git branch -d feature
1 - 2 - 3 - 4 - 5 ------------------ 6 [master]
\ /
f1 - f3 - f5 - f6
This will keep the branch in the history without having to keep the branch head around. This makes it clear for future code archeologists that f1 - f3 - f5 - f6
were all developed for a single feature.
Upvotes: 2