When do you use git rebase instead of git merge?

2018-12-31 02:15发布

When is it recommended to use git rebase vs. git merge?

Do I still need to merge after a successful rebase?

14条回答
刘海飞了
2楼-- · 2018-12-31 02:38

Short Version

  • Merge takes all the changes in one branch and merges them into another branch in one commit.
  • Rebase says I want the point at which I branched to move to a new starting point

So when do you use either one?

Merge

  • Let's say you have created a branch for the purpose of developing a single feature. When you want to bring those changes back to master, you probably want merge (you don't care about maintaining all of the interim commits).

Rebase

  • A second scenario would be if you started doing some development and then another developer made an unrelated change. You probably want to pull and then rebase to base your changes from the current version from the repo.
查看更多
美炸的是我
3楼-- · 2018-12-31 02:40

To complement my own answer mentioned by TSamper,

  • a rebase is quite often a good idea to do before a merge, because the idea is that you integrate in your branch Y the work of the branch B upon which you will merge.
    But again, before merging, you resolve any conflict in your branch (i.e.: "rebase", as in "replay my work in my branch starting from a recent point from the branch B)
    If done correctly, the subsequent merge from your branch to branch B can be fast-forward.

  • a merge impact directly the destination branch B, which means the merges better be trivial, otherwise that branch B can be long to get back to a stable state (time for you solve all the conflicts)


the point of merging after a rebase?

In the case that I describe, I rebase B onto my branch, just to have the opportunity to replay my work from a more recent point from B, but while staying into my branch.
In this case, a merge is still needed to bring my "replayed" work onto B.

The other scenario (described in Git Ready for instance), is to bring your work directly in B through a rebase (which does conserve all your nice commits, or even give you the opportunity to re-order them through an interactive rebase).
In that case (where you rebase while being in the B branch), you are right: no further merge is needed:

A git tree at default when we have not merged nor rebased

rebase1

we get by rebasing:

rebase3

That second scenario is all about: how do I get new-feature back into master.

My point, by describing the first rebase scenario, is to remind everyone that a rebase can also be used as a preliminary step to that (that being "get new-feature back into master").
You can use rebase to first bring master "in" the new-feature branch: the rebase will replay new-feature commits from the HEAD master, but still in the new-feature branch, effectively moving your branch starting point from an old master commit to HEAD-master.
That allows you to resolve any conflicts in your branch (meaning, in isolation, while allowing master to continue to evolve in parallel if your conflict resolution stage takes too long).
Then you can switch to master and merge new-feature (or rebase new-feature onto master if you want to preserve commits done in your new-feature branch).

So:

  • "rebase vs. merge" can be viewed as two ways to import a work on, say, master.
  • But "rebase then merge" can be a valid workflow to first resolve conflict in isolation, then bring back your work.
查看更多
梦寄多情
4楼-- · 2018-12-31 02:41

Some practical examples, somewhat connected to large scale development where gerrit is used for review and delivery integration.

I merge when i uplift my feature branch to a fresh remote master. This gives minimal uplift work and it's easy to follow the history of the feature development in for example gitk.

git fetch
git checkout origin/my_feature
git merge origin/master
git commit
git push origin HEAD:refs/for/my_feature

I merge when I prepare a delivery commit.

git fetch
git checkout origin/master
git merge --squash origin/my_feature
git commit
git push origin HEAD:refs/for/master

I rebase when my delivery commit fails integration for whatever reason and I need to update it towards a fresh remote master.

git fetch
git fetch <gerrit link>
git checkout FETCH_HEAD
git rebase origin/master
git push origin HEAD:refs/for/master
查看更多
伤终究还是伤i
5楼-- · 2018-12-31 02:43

Merge means: Create a single new commit that merges my changes into the destination.

Rebase means: Create a whole new series of commits, using my current set of commits as hints. In other words, calculate what my changes would have looked like if I had started making them from the point I'm rebasing on to. After the rebase, therefore, you might need to re-test your changes and during the rebase, you would possibly have a few conflicts.

Given this, why would you rebase? Just to keep the development history clear. Let's say you're working on feature X and when you're done, you merge your changes in. The destination will now have a single commit that would say something along the lines of "Added feature X". Now, instead of merging, if you rebased and then merged, the destination development history would contain all the individual commits in a single logical progression. This makes reviewing changes later on much easier. Imagine how hard you'd find it to review the development history if 50 developers were merging various features all the time.

That said, if you have already pushed the branch you're working on upstream, you should not rebase, but merge instead. For branches that have not been pushed upstream, rebase, test and merge.

Another time you might want to rebase is when you want to get rid of commits from your branch before pushing upstream. For example: Commits that introduce some debugging code early on and other commits further on that clean that code up. The only way to do this is by performing an interactive rebase: git rebase -i <branch/commit/tag>

UPDATE: You also want to use rebase when you're using Git to interface to a version control system that doesn't support non-linear history (subversion for example). When using the git-svn bridge, it is very important that the changes you merge back into subversion are a sequential list of changes on top of the most recent changes in trunk. There are only two ways to do that: (1) Manually re-create the changes and (2) Using the rebase command, which is a lot faster.

UPDATE2 : One additional way to think of a rebase is that it enables a sort of mapping from your development style to the style accepted in the repository you're committing to. Let's say you like to commit in small, tiny chunks. You have one commit to fix a typo, one commit to get rid of unused code and so on. By the time you've finished what you need to do, you have a long series of commits. Now let's say the repository you're committing to encourages large commits, so for the work you're doing, one would expect one or maybe two commits. How do you take your string of commits and compress them to what is expected? You would use an interactive rebase and squash your tiny commits into fewer larger chunks. The same is true if the reverse was needed - if your style was a few large commits, but the repo demanded long strings of small commits. You would use a rebase to do that as well. If you had merged instead, you have now grafted your commit style onto the main repository. If there are a lot of developers, you can imagine how hard it would be to follow a history with several different commit styles after some time.

UPDATE3: Does one still need to merge after a successful rebase? Yes, you do. The reason is that a rebase essentially involves a "shifting" of commits. As I've said above, these commits are calculated, but if you had 14 commits from the point of branching, then assuming nothing goes wrong with your rebase, you will be 14 commits ahead (of the point you're rebasing onto) after the rebase is done. You had a branch before a rebase. You will have a branch of the same length after. You still need to merge before you publish your changes. In other words, rebase as many times as you want (again, only if you have not pushed your changes upstream). Merge only after you rebase.

查看更多
听够珍惜
6楼-- · 2018-12-31 02:43

This answer is widely oriented around Git Flow. The tables have been generated with the nice ASCII Table Generator, and the history trees with this wonderful command (aliased as git lg):

git log --graph --abbrev-commit --decorate --date=format:'%Y-%m-%d %H:%M:%S' --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%ad%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n''          %C(white)%s%C(reset) %C(dim white)- %an%C(reset)'

Tables are in reverse chronological order to be more consistent with the history trees. See also the difference between git merge and git merge --no-ff first (you usually want to use git merge --no-ff as it makes your history look closer to the reality):

git merge

Commands:

Time          Branch "develop"             Branch "features/foo"
------- ------------------------------ -------------------------------
15:04   git merge features/foo
15:03                                  git commit -m "Third commit"
15:02                                  git commit -m "Second commit"
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Result:

* 142a74a - YYYY-MM-DD 15:03:00 (XX minutes ago) (HEAD -> develop, features/foo)
|           Third commit - Christophe
* 00d848c - YYYY-MM-DD 15:02:00 (XX minutes ago)
|           Second commit - Christophe
* 298e9c5 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git merge --no-ff

Commands:

Time           Branch "develop"              Branch "features/foo"
------- -------------------------------- -------------------------------
15:04   git merge --no-ff features/foo
15:03                                    git commit -m "Third commit"
15:02                                    git commit -m "Second commit"
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Result:

*   1140d8c - YYYY-MM-DD 15:04:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/foo' - Christophe
| * 69f4a7a - YYYY-MM-DD 15:03:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 2973183 - YYYY-MM-DD 15:02:00 (XX minutes ago)
|/            Second commit - Christophe
* c173472 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git merge vs git rebase

First point: always merge features into develop, never rebase develop from features. This is a consequence of the Golden Rule of Rebasing:

The golden rule of git rebase is to never use it on public branches.

In other words:

Never rebase anything you've pushed somewhere.

I would personally add: unless it's a feature branch AND you and your team are aware of the consequences.

So the question of git merge vs git rebase applies almost only to the feature branches (in the following examples, --no-ff has always been used when merging). Note that since I'm not sure there's one better solution (a debate exists), I'll only provide how both commands behave. In my case, I prefer using git rebase as it produces a nicer history tree :)

Between feature branches

git merge

Commands:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- --------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m "Sixth commit"
15:07                                                                    git merge --no-ff features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Result:

*   c0a3b89 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 37e933e - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| *   eb5e657 - YYYY-MM-DD 15:07:00 (XX minutes ago)
| |\            Merge branch 'features/foo' into features/bar - Christophe
| * | 2e4086f - YYYY-MM-DD 15:06:00 (XX minutes ago)
| | |           Fifth commit - Christophe
| * | 31e3a60 - YYYY-MM-DD 15:05:00 (XX minutes ago)
| | |           Fourth commit - Christophe
* | |   98b439f - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \ \            Merge branch 'features/foo' - Christophe
| |/ /
|/| /
| |/
| * 6579c9c - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 3f41d96 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* 14edc68 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git rebase

Commands:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m "Sixth commit"
15:07                                                                    git rebase features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Result:

*   7a99663 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 708347a - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * 949ae73 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * 108b4c7 - YYYY-MM-DD 15:05:00 (XX minutes ago)
| |           Fourth commit - Christophe
* |   189de99 - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| |/
| * 26835a0 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * a61dd08 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* ae6f5fc - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

From develop to a feature branch

git merge

Commands:

Time           Branch “develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09                                                                    git commit -m “Sixth commit"
15:08                                                                    git merge --no-ff development
15:07   git merge --no-ff features/foo
15:06                                                                    git commit -m “Fifth commit"
15:05                                                                    git commit -m “Fourth commit"
15:04                                    git commit -m “Third commit"
15:03                                    git commit -m “Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m “First commit"

Result:

*   9e6311a - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 3ce9128 - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| *   d0cd244 - YYYY-MM-DD 15:08:00 (XX minutes ago)
| |\            Merge branch 'develop' into features/bar - Christophe
| |/
|/|
* |   5bd5f70 - YYYY-MM-DD 15:07:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| * | 4ef3853 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| | |           Third commit - Christophe
| * | 3227253 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/ /            Second commit - Christophe
| * b5543a2 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * 5e84b79 - YYYY-MM-DD 15:05:00 (XX minutes ago)
|/            Fourth commit - Christophe
* 2da6d8d - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git rebase

Commands:

Time           Branch “develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09                                                                    git commit -m “Sixth commit"
15:08                                                                    git rebase development
15:07   git merge --no-ff features/foo
15:06                                                                    git commit -m “Fifth commit"
15:05                                                                    git commit -m “Fourth commit"
15:04                                    git commit -m “Third commit"
15:03                                    git commit -m “Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m “First commit"

Result:

*   b0f6752 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 621ad5b - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * 9cb1a16 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * b8ffffd19 - YYYY-MM-DD 15:05:00 (XX minutes ago)
|/            Fourth commit - Christophe
*   856433e - YYYY-MM-DD 15:07:00 (XX minutes ago)
|\            Merge branch 'features/foo' - Christophe
| * 694ac81 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 5fd94d3 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* d01d589 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

Side notes

git cherry-pick

When you just need one specific commit, git cherry-pick is a nice solution (the -x option appends a line that says "(cherry picked from commit...)" to the original commit message body, so it's usually a good idea to use it - git log <commit_sha1> to see it):

Commands:

Time           Branch “develop"              Branch "features/foo"                Branch "features/bar"           
------- -------------------------------- ------------------------------- -----------------------------------------
15:10   git merge --no-ff features/bar                                                                            
15:09   git merge --no-ff features/foo                                                                            
15:08                                                                    git commit -m “Sixth commit"             
15:07                                                                    git cherry-pick -x <second_commit_sha1>  
15:06                                                                    git commit -m “Fifth commit"             
15:05                                                                    git commit -m “Fourth commit"            
15:04                                    git commit -m “Third commit"                                             
15:03                                    git commit -m “Second commit"                                            
15:02   git checkout -b features/bar                                                                              
15:01   git checkout -b features/foo                                                                              
15:00   git commit -m “First commit"                                                                              

Result:

*   50839cd - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 0cda99f - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * f7d6c47 - YYYY-MM-DD 15:03:00 (XX minutes ago)
| |           Second commit - Christophe
| * dd7d05a - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * d0d759b - YYYY-MM-DD 15:05:00 (XX minutes ago)
| |           Fourth commit - Christophe
* |   1a397c5 - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| |/
|/|
| * 0600a72 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * f4c127a - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* 0cf894c - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git pull --rebase

Not sure I can explain it better than Derek Gourlay... Basically, use git pull --rebase instead of git pull :) What's missing in the article though, is that you can enable it by default:

git config --global pull.rebase true

git rerere

Again, nicely explained here. But put simple, if you enable it, you won't have to resolve the same conflict multiple times anymore.

查看更多
几人难应
7楼-- · 2018-12-31 02:43

Git rebase is used to make the branching paths in history cleaner and repository structure linear.

It is also used to keep the branches created by you private, as after rebasing and pushing the changes to server, if you delete your branch, there will be no evidence of branch you have worked upon. So your branch is now your local concern.

After doing rebase we also get rid of an extra commit which we used to see if we do normal merge.

And yes one still needs to do merge after a successful rebase as rebase command just puts your work on top of the branch you mentioned during rebase say master and makes the first commit of your branch as a direct descendant of the master branch. This means we can now do a fast forward merge to bring changes from this branch to master branch.

查看更多
登录 后发表回答