git: Merge Branches but Keep Commit History

2019-03-11 04:37发布

问题:

In my git workflow we have one main repository and one branch, master. Everyone pulls from remote master and everyone pushes to remote master. I want to work in my own branch while I prepare a feature. So far my history is something like this:

git pull --rebase
git checkout -b new_feature
<make some commits>
git checkout master
git pull --rebase

Now I want to merge the branch and here's what I need:

  1. No merge commits in my local master branch.
  2. All commits made into my new_feature branch merged into master as if I had made them in master.
  3. All merged commits to be merged somewhere on top of my local remote head pointer.

My biggest concern is item 3, when is needed so that I can safely push the changes. If the merged commits are intertwined with commits before head then I will have problems pushing, see related problem I had: git: Pushing Single Commits, Reordering with rebase, Duplicate Commits.

I've read the following:

  • http://mettadore.com/2011/05/06/a-simple-git-rebase-workflow-explained/
  • Keep commits history after a 'git merge'
  • How do you rebase the current branch's changes on top of changes being merged in?

And I think I need to do:

git checkout master
git pull --rebase
git checkout new_feature
git rebase master
git checkout master
git rebase new_feature
git push

My understanding is that

git checkout new_feature
git rebase master

will make new_feature appear as if it was branched off from the new current head. Is that true? And that

git checkout master
git rebase new_feature

will place new_feature on top of master. Is that correct? If so, this is the main point of my confusion. If "git rebase master" places master commits at the bottom of new_feature, then why does "git rebase new_feature" place new_feature commits at the top of master, i.e. why doesn't it do the opposite?

回答1:

Answer

Here is a workflow that you can use that does just what you need it to do.

git checkout master
git pull --rebase                             (1)
git checkout new_feature                      
<do a bunch of commits>
git rebase master                             (2)
git checkout master
git merge new_feature                         (3)
git branch -D new_feature                     (4)

Explanation

(1) git pull --rebase will first fetch origin/master and then replay your local master on top of it. Note in the example log that your local commits are on top of your "local remote HEAD pointer."

> git log --oneline --all -10 --decorate

d34d34c (HEAD, master) Local commit message.
d3434r2 Local commit message.
d234d4c Local commit message.
er3ede3 (origin/master, origin/HEAD) Remote commit message.
sfe3fd3 Remote commit message.

You can now checkout and work on your new_feature branch for a while. When you're done...

(2) git rebase master will replay new_feature on top of master. Again, your local commits remain on top of your "local remote HEAD pointer."

> git log --oneline --all -10 --decorate

fc5773d (new_feature) Local new_feature commit.
9282838 Local new_feature commit.
d34d34c (HEAD, master) Local commit.
d3434r2 Local commit.
d234d4c Local commit.
er3ede3 (origin/master, origin/HEAD) Remote commit.
sfe3fd3 Remote commit.

The rebase command just put new_feature ahead of master, and to align them you need to run...

(3) git merge new_feature, which will do a fast-forward merge. Now HEAD, new_feature, and master all point to the same commit.

> git log --oneline --all -10 --decorate

fc5773d (HEAD, new_feature, master) Local new_feature commit.
9282838 Local new_feature commit.
d34d34c Local commit.
d3434r2 Local commit.
d234d4c Local commit.
er3ede3 (origin/master, origin/HEAD) Remote commit.
sfe3fd3 Remote commit.

(4) After that, you can safely delete the new_feature branch. Your final log before pushing will look like this:

> git log --oneline --all -10 --decorate

fc5773d (HEAD, master) Local new_feature commit 2
9282838 Local new_feature commit.
d34d34c Local commit.
d3434r2 Local commit.
d234d4c Local commit.
er3ede3 (origin/master, origin/HEAD) Remote commit.
sfe3fd3 Remote commit.


回答2:

It's unnecessary to run git rebase new_feature on the master branch after you've run git rebase master on the new_feature branch. After you've run git rebase master on the new_feature branch, you can then merge new_feature into master - it will be a fast-forward merge and won't introduce a merge commit.

The reason why git rebase new-feature isn't playing all the new-feature commits on top of master is because git recognizes master already is at the base of new feature - we performed that step with git rebase master - and that it would just be rebasing on itself. So instead it just fast-forwards to new-feature.

Also, you don't need to worry about pushing commits that reside below your remote/master tip -- the remote will reject your push should you try (unless you provide the -f option, which, don't). And, if your local master is tracking your remote master, git status will tell if your local has diverged from you remote branch.