I imported an open Git repository (upstream/master) into my own repository (origin/master) and develop on the origin/master. Because upstream/master is active, lots of commits have been pushed into the branch since I cloned the repo; upstream/master and branch/master would look like below.
--o--o--M--...--N---...--O-----o <- upstream/master
\
A------B------C----- ... --D <- branch/master
And if I merged the upstream into my master branch, it would look like the following:
--o--o--M--...--N---...--O-----o <- upstream/master
\
A---M--B----N-C------O--...--D <- master(upstream merged)
However, as the commits are interleaved when merging, if the commit N broke my master branch, the commits C-D would be broken. Although I could fix the branch with a new commit, the C-D part would be still broken.
How do I avoid the broken part? Or is there any best practice for this kind of situation?
CodePudding user response:
A merge doesn't rewrite any existing history, and it would actually be really difficult to create an "interleaved" history like you show in git.
If you start with this:
--X-----M-----N-----O-----P <- upstream
\
A------B------C------D <- mybranch
Then a straight-forward merge of upstream into mybranch would create a new "merge commit", which:
- Applies the changes both branches have made since their last common ancestor (marked X)
- Has two "parent" commits, P and D, which is how git records history
So the result looks like this:
--X-----M-----N-----O-------P <- upstream
\ \
A------B------C------D----E <- mybranch
From git's point of view, all the commits shown are "ancestors" of commit E, regardless of which branch they were on, but there is no direct relationship between, say C and N.
There is a way to merge code that recreates older changes, called "rebasing", which looks at each change you made, and creates a new commit pretending you made that change on top of P instead of X. It doesn't "interleave" commits, though, it puts all the new ones in order after the point you "rebase onto", giving something like this:
--X-----M-----N-----O-----P <- upstream
\
A1------B1------C1------D1 <- mybranch
(It's tempting to think that commits A to E have "moved", but commits in git are immutable, and represent the state of the whole code, not a change, so commits A1 to E1 have to be created new by the "rebase" machinery.)