What are the conceptual differences between Mergin

2020-02-14 05:54发布

I have been using Merging heavily on master branch. But recently for a feature development in my situation merging seemed complicated for the project history. I came across Rebasing which solves my problem. I also came across the golden rule of rebasing while solving the problem.

I also used Stashing at times, it worked, but I feel like the same thing could have been achieved with merging as well.

Although I use these commands, I feel like if someone can explain the conceptually outstanding facts/rule about these three commands it would help me to get a clearer understanding. Thanks.

4条回答
祖国的老花朵
2楼-- · 2020-02-14 06:28

In brief, let’s use graph to illustrate the difference. Assume your git log looks like below,

  A---B---C dev
 /
D---E---F---G   main

git merge dev, after merge branch dev to main, there is a one more commit H on main branch

  A---B---C  dev                                  merge
 /         \
D---E---F---G---H  main

git rebase main, all the commits (A,B and C) are replay on main, and the commit history looks like as linear

D---E---F---G---H---A`---B`---C`  main            rebase

git stash, it's that when you are modifying files, assume you want to switch branch but you don’t want to commit the modified files. So can stash it, after finishing other works, you can switch back to the branch you were working in, and use git stash apply so the working directory is prepare the modified files for you.

查看更多
女痞
3楼-- · 2020-02-14 06:36

Let's say you have this repository. A, B, and C are commits. master is at C.

A - B - C [master]

You make a branch called feature. It points to C.

A - B - C [master]
          [feature]

You do some work on both master and feature.

A - B - C - D - E - F [master]
         \
          G - H - I [feature]

You want to update feature with the changes from master. You could merge master into feature, resulting in a merge commit J.

A - B - C - D - E - F [master]
         \           \
          G - H - I - J [feature]

If you do this enough times, things start to get messy.

A - B - C - D - E - F - K - L - O - P - Q [master]
         \           \       \       \ 
          G - H - I - J - M - N - Q - R - S [feature]

That might look simple, but that's because I've drawn it that way. Git history is a graph (in the computer science sense) and there's nothing that says it has to be drawn like that. And there's nothing which explicitly says, for example, commit M is part of branch feature. You have to figure that out from the graph and sometimes that can get messy.

When you decide you're done and merge feature into master, things get messy.

A - B - C - D - E - F - K - L - O - P - Q - T [master]
         \           \       \       \     /
          G - H - I - J - M - N - Q - R - S [feature]

Now it's difficult to tell that M was originally part of feature. Again, I've chosen a nice way to draw it, but Git doesn't necessarily know to do that. M is an ancestor of both master and feature. This makes it difficult to interpret history and figure out what was done in which branch. It can also cause unnecessary merge conflicts.


Let's start over and rebase instead.

A - B - C - D - E - F [master]
         \
          G - H - I [feature]

Rebasing a branch onto another branch is conceptually like moving that branch to the tip of the other. Rebasing feature onto master is like this:

                      G1 - H1 - I1 [feature]
                     /
A - B - C - D - E - F [master]
         \
          G - H - I

Every commit in feature is replayed on top of master. It's as if you took the diff between C and G, applied it to F, and called that G1. Then the diff between G and H gets applied to G1, that's H1. And so on.

There's no merge commit. It's as if you wrote the feature branch on top of master all along. This keeps a nice, clean, linear history that isn't littered with merge commits that don't tell you anything.

Note that the old feature branch is still there. It's just that nothing points to it and it will eventually be garbage collected. This is there to show you that rebase does not rewrite history; instead, rebase creates new history and then we pretend it was that way all along. This is important for two reasons:

First, if you screw up a rebase the old branch is still there. You can find it with git reflog or using ORIG_HEAD.

Second, and most important, a rebase results in new commit IDs. Everything in Git works by an ID. This is why, if you rebase a shared branch, it introduces complications.

There's A LOT more to say about rebasing vs. merging, so I'll leave it at this:

  • To update a branch, use rebase. This avoids messy intermediate merges.
  • To finish a branch...
    • Update it using rebase.
    • Then use merge --no-ff to force a merge commit to be created.
    • Then delete the feature branch, never use it again.

The end result you want to see in your history is a "feature bubble".

                      G1 - H1 - I1
                     /            \
A - B - C - D - E - F ------------ J [master]

This keeps history linear while still giving code archeologists the important context that G1, H1, and I1 were done as part of a branch and should be examined together.


Stashing is something completely different. It's basically a special branch to store patches.

Sometimes you're in the middle of something and it's not ready to commit but you need to do some other work. You could put it in a patch file with git diff > some.patch, reset your working directory, do the other work, commit it, then apply some.patch. Or you can git stash save and later git stash pop.

查看更多
Lonely孤独者°
4楼-- · 2020-02-14 06:47

Stashing is very different, in that it essentially sets aside your changes for your later. Useful if you're in the middle of something and you have to jump onto something else and switch branches.

Merging and rebasing achieve the same thing in the end - combining changes into one branch.

  • Rebasing gives an arguably "cleaner" history and conflicts are resolved inline.
  • With merging, conflicts are resolved in the merge commit.
  • A rebased branch is still merged back to master when it's complete.

The difference is that resolving the conflict inline effectively makes it as though the conflict never happened, because you're editing the file again to incorporate the change that caused the conflict.

That can be useful when you're looking back at the changes later.

The caveat, as you alluded to, is that rewriting history makes it hard or impossible to collaborate with others on that particular branch.


A series of merges from a long-standing feature branch can be quite hard to follow when looking at the graphs or visual representations from tools like gitg.

With rebases, it's much easier to follow the changes visually. It can also help when you're using tools like git-bisect to find the origin commit of bugs, as the branches are more straightforward to traverse.

Choosing which to go for depends on a few things.

Personally, I rebase and rebase often if I'm on a short-lived feature branch that I'm working on alone. Otherwise it's a merge.


There is a situation where you might have no choice to rebase - if you started your branch from the wrong point. For example, you may have had a particular feature checked out and began working on something else. That something else might be required to be merged and deployed before the feature it's based on. At this point, rebasing your changes onto a different branch will allow you to get that feature released independent of the other.


When you are merging a rebased branch back into master, that is still a merge, even if the history is linear. You may see a message in git telling you it did a "fast forward" merge. That means it merely moved the reference "master" to its new position. You can tell git not do this, and create a merge commit anyway, with the --no-ff flag on the git merge command.

Linear history:

* HEAD, master, your_branch: your last commit
|
*
|
*
|
*
|
*
|
* previous master: where master was when you rebased

rebased history with a no-ff merge commit:

* HEAD: merge branch your_branch into master
| \
|  * your_branch the last commit in your branch
|  |
|  * 
|  |
|  *
|  |
|  *
|/
* previous master: the starting point of your branch
查看更多
够拽才男人
5楼-- · 2020-02-14 06:48

What is the conceptual difference between Merging, Stashing and Rebasing in Git?

I assume that you mean "what are these three features used for", and not the actual technical difference - that you seem to know about.

Merging

Merging means that you take two branches and end up with a single one that contains everything from the original branches. This means that merging is conceptually the inverse of branching. You primarily use it to "remove" the branch - by merging it back to where it came from. Merging signifies that you do not intend to add more commits to that branch.

Rebasing

Rebasing means that you change where an existing branch originates from. Conceptually that means that you keep the branch existing as its own path of development, you just simulate that you had created it at another point of time.

The reason why this is useful, again conceptionally, is, that for plenty of branches (for example, feature branches) you don't care about which exact point in time you created them. You care about the fact that they are a "delta" to master, whatever master is right now. So you want to branch them off of the current master at all times, by rebasing them onto it whenever master changes.

Merge or rebase?

These concepts mean that it is quite obvious when to use which. If you keep the branch around (as its own entity, i.e., because you continue developing on it), then rebase. If you want the branch to disappear (conceptionally) and never want to add anoter commit to it, then merge.

It also means that neither of them is better or worse than the other. They are both tools, both are meant to be used. Both can be abused; both can wreak havoc with your repository if used without thinking.

Stashing

Stashing is completely unrelated to either merging or rebasing. Think of it as a local thing that you do for yourself; you quickly whisk away any changes you did, work on something else, and then conjure the previous state back. It's conceptionally similar to cloning a clean copy of your repository in some other place, doing work there, and then returning to your previous repository.

查看更多
登录 后发表回答