• Aucun résultat trouvé

rebase Versus merge

Dans le document Version Control with Git (Page 189-195)

In addition to the problem of simply altering history, the rebase operation has further ramifications of which you should be aware.

Rebasing a sequence of commits to the tip of a branch is similar to merging the two branches: in either case, the new head of that branch will have the combined effect of both branches represented.

You might ask yourself, “Should I use merge or rebase on my sequence of commits?”

In Chapter 11, this will become an important question—especially when multiple de-velopers, repositories, and branches all come into play.

Rebasing Commits | 171

The process of rebasing a sequence of commits causes Git to generate an entirely new sequence of commits. They have new SHA1 commit IDs, are based on a new initial state, and represent different diffs even though they involve changes that achieve the same ultimate state.

When faced with a situation like that of Figure 10-12, rebasing it into Figure 10-13 doesn’t present a problem because no one or no other commit relies on the branch being rebased. However, even within your own repository you might have additional branches based upon the one you wish to rebase. Consider the graph shown in Fig-ure 10-16.

A B C D master

X Y

P Q

Z dev

dev2 Figure 10-16. Before git rebase multibranch

You might think that executing the command:

# Move onto tip of master the dev branch

$ git rebase master dev

would yield the graph in Figure 10-17. But it does not. Your first clue that it didn’t happen comes from the command’s output.

$ git rebase master dev

First, rewinding head to replay your work on top of it...

Applying: X Applying: Y Applying: Z

A B C D master

X' Y'

P' Q'

Z' dev

dev2 Figure 10-17. Desired git rebase multibranch

This says that Git applied the commits for X, Y, and Z only. Nothing was said about P or Q, and instead you obtain the graph in Figure 10-18.

A B C D master

dev

X Y P Q

X' Y' Z'

dev2 Figure 10-18. Actual git rebase multibranch

The commits X', Y', and Z' are the new versions of the old commits that stem from B. The old X and Y commits both still exist in the graph, as they are still reachable from the dev2 branch. However, the original Z commit has been removed because it is no longer reachable. The branch name that was pointing to it has been moved to the new version of that commit.

The branch history now looks like it has duplicate commit messages in it, too:

$ git show-branch

* [dev] Z ! [dev2] Q ! [master] D

---* [dev] Z

* [dev^] Y

* [dev~2] X

* + [master] D

* + [master^] C + [dev2] Q + [dev2^] P + [dev2~2] Y + [dev2~3] X

*++ [master~2] B

But remember, these are different commits that do essentially the same change. If you merge a branch with one of the new commits into another branch that has one of the old commits, Git has no way of knowing that you’re applying the same change twice.

The result is duplicate entries in git log, most likely a merge conflict, and general confusion. It’s a situation that you should find a way to clean up.

If this resulting graph is actually what you want, you’re done. More likely, moving the entire branch (including subbranches) is what you really want. To achieve that graph, you will, in turn, need to rebase the dev2 branch on the new Y' commit on the dev branch:

Rebasing Commits | 173

$ git rebase dev^ dev2

First, rewinding head to replay your work on top of it...

Applying: P Applying: Q

$ git show-branch

! [dev] Z * [dev2] Q ! [master] D

* [dev2] Q * [dev2^] P + [dev] Z +* [dev2~2] Y +* [dev2~3] X +*+ [master] D

And this is the graph shown earlier in Figure 10-17.

Another situation that can be extremely confusing is rebasing a branch that has a merge on it. For example, suppose you had a branch structure like that shown in Figure 10-19.

X Y Z

dev

A B C D

P M N

master Figure 10-19. Before git rebase merge

If you want to move the entire dev branch structure from commit N down through commit X off of B and onto D, as shown in Figure 10-20, you might expect simply to use the command git rebase master dev.

X Y Z

dev

A B C D

P M N

master Figure 10-20. Desired git rebase merge

Again, however, that command yields some surprising results:

$ git rebase master dev

First, rewinding head to replay your work on top of it...

Applying: X Applying: Y Applying: Z Applying: P Applying: N

It looks like it did the right thing. After all, Git says that it applied all the (nonmerge) commit changes. But did it really get things right?

$ git show-branch

All those commits are now in one long string!

What happened here?

Git needs to move the portion of the graph reachable from dev back to the merge base at B, so it found the commits in the range master..dev. To list all those commits, Git performs a topological sort on that portion of the graph to produce a linearized se-quence of all the commits in that range. Once that sese-quence has been determined, Git applies the commits one at a time starting on the target commit, D. Thus, we say,

“Rebase has linearized the original branch history (with merges) onto the master branch.”

Again, if that is what you wanted or you don’t care that the graph shape has been altered, then you are done. But if in such cases you want to explicitly preserve the branching and merging structure of the entire branch being rebased, use the --preserve-merges option:

# This option is a version 1.6.1 feature

$ git rebase --preserve-merges master dev Successfully rebased and updated refs/heads/dev.

Using my Git alias from “Configuring an Alias” on page 28 , we can see that the resulting graph structure maintains the original merge structure:

$ git show-graph

* 061f9fd... N

* f669404... Merge branch 'dev2' into dev

|\

| * c386cfc... Z

* | 38ab25e... P

Rebasing Commits | 175

|/

* b93ad42... Y

* 65be7f1... X

* e3b9e22... D

* f2b96c4... C

* 8619681... B

* d6fba18... A

And this looks like the graph in Figure 10-21.

X Y Z P M N

A B C D

dev master

Figure 10-21. git rebase merge after linearization

Some of the principles for answering the rebase-versus-merge question apply equally to your own repository as they do to a distributed or multi-repository scenario. In Chapter 12, you can read about the additional implications that affect developers using other repositories.

Depending on your development style and your ultimate intent, having the original branch development history linearized when it is rebased may or may not be acceptable.

If you have already published or provided the commits on the branch that you wish to rebase, consider the negative ramifications on others.

If the rebase operation isn’t the right choice and you still need the branch changes, merging may be the correct choice.

The important concepts to remember are:

• Rebase rewrites commits as new commits.

• Old commits that are no longer reachable are gone.

• Any user of one of the old, pre-rebase commits might be stranded.

• If you have a branch that uses a pre-rebase commit, you might need to rebase it in turn.

• If there is a user of a pre-rebase commit in a different repository, they still have a copy of that commit even though it has moved in your repository; they will now have to fix up their commit history, too.

CHAPTER 11

Dans le document Version Control with Git (Page 189-195)