There are many ways to do this, and which one you should use depends on what result you want, and what you and someone who collaborates with you (if it is a shared repository) are expected to see in the future.
Three main ways to do this:
- Do not worry. Disable branch
A , create a new A2 and use this. Use git reset or equivalent to re-point A elsewhere.
Methods 1 and 2 are essentially the same in the long run. For example, suppose you start by giving up A For a while, everyone is developing on A2 . Then, when everyone uses A2 , you completely remove the name A Now that A gone, you can even rename A2 to A (everyone else using it will have to do the same rename, of course). At the moment, what if something looks different between the case when you used method 1 and the case when you used method 2? (There is one place that you can still see the difference, depending on how long your “long run” lasts and when your old reflags expire.)
- Do a merge using a special strategy (see "alternative merge strategies" below). Here you want
-s theirs , but does not exist , and you have to fake it.
Side of the note: where the "created from" branch does not basically matter: git does not track such things, and in general it cannot be detected later, so maybe it does not matter to you, (If it really matters for you, you can put a few restrictions on how you manipulate the branch, or tag it with a “start point” so you can recover this information mechanically later, but this is often a sign that you are doing something wrong in the first place. queue - or at least expect are something from git, git is not provided. See. See also note 1 below.)
Definition of a branch (see also What exactly do we mean by a branch? )
A branch, or rather, a branch name in git, is just a pointer to some specific commit in the commit graph. This also applies to other links, such as tag names. What makes a special branch compared to a tag, for example, is that when you are on a branch and make a new commit, git automatically updates the branch name so that it now points to a new commit.
For example, suppose we have a commit graph that looks something like this, where o nodes (as well as marked * ) represent committ and A and B are branch names:
o <- * <- o <- o <- o <- o <-- A \ o <- o <- o <-- B
A and B each point for the greatest fixation of the branch branch or, more precisely, the branching data structure formed by starting from some commit and processing all reachable commits, each commit pointing to some parent commit (s).
If you use git checkout B so that you are in branch B and make a new commit, the new latch is set with the previous end of B as its (single) parent and git changes the identifier stored under the name of branch B , so B points to the new Review: Largest Fix:
o <- * <- o <- o <- o <- o <-- A \ o <- o <- o <- o <-- B
A fixed label * is the base for merging two branch tips. This commit and all previous commits are in both branches. 1 A merge base is relevant, for example, for a merge (duh :-)), but also for things like git cherry and other type releases.
You mentioned that branch B sometimes merged back into A :
git checkout A; git merge B
This makes a new merge, which is a commit with two parents 2 . The first parent is the previous end of the current branch, that is, the previous end of A , and the second parent is the named commit, that is, the largest commit of branch B Redrawing the above a little more compactly (but adding a little more so that the o line improves), we start with:
o
and in the end:
o
Move * to the new merge base A and B (which is actually the end of B ). Presumably, we will add a few more commits to B and, possibly, we can combine several times:
...
What git does by default with git merge <thing>
To perform a merge, you check for a branch ( A in these cases), and then run git merge and specify at least one argument, usually a different branch name, for example B The merge command begins by including the name in the commit identifier. The name of the branch turns into the identifier of the largest commit hint on the branch.
Then git merge finds the git merge base. These are the commits that we marked with * . The technical definition of a merge base is the lowest common ancestor in the commit column (and in some cases there can be more than one), but we just go to “commit marked * ” here for simplicity.
Finally, for regular git merges, two git diff commands are executed. 3 The first git diff compares commit * with a HEAD commit, that is, the tip of the current branch. The second diff compares commit * with the commit argument, that is, the tip of another branch (you can name a specific commit, and it should not be the end of the branch, but in our case we combine B into A , so we get those two ends with the ends of the branches).
When git finds any file modified compared to the merge version in both branches, git tries to merge these changes in a semi-smart (but not very smart) way: if both changes add the same text in the same region, git stores one copy of the addition. If both changes delete the same text in the same region, git only removes that text once. If both changes change the text in the same region, you get a conflict, if the changes do not exactly match (then you get one copy of the modifications). If one side makes one change and the other side makes the other change, but the changes do not seem to overlap, git accepts both changes. This is the essence of the tripartite merger.
Finally, if everything goes well, git creates a new commit in which there are two (or more, as we mentioned in footnote 2) parents. The work tree associated with this new commit is git when it performed its tripartite merge.
Alternative Merge Strategies
While the git merge default recursive strategy has -X parameters ours and theirs , they do not do what we want here. They simply say that in the event of a conflict, git should automatically resolve this conflict by choosing “our change” ( -X ours ) or “change them” ( -X theirs ).
The merge command has a different strategy completely, -s ours : it says that instead of distinguishing between a merge base with two commits, just use our source tree. In other words, if we are on branch A and we run git merge -s ours B , git will do a new merge with the second parent, which is the end of branch B , but the original tree corresponding to the version in the previous tip of branch A That is, the code for the new commit will exactly match the code for its parent.
As pointed out in this other answer , there are several ways to get git to effectively implement -s theirs . I think the easiest way to explain this sequence is:
git checkout A git merge --no-commit -s ours B git rm -rf .
The first step is to make sure that we are on branch A , as usual. The second is to start the merge, but not allow the result ( --no-commit ). To simplify merging for git - this is not necessary, it just makes things faster and quieter - we use -s ours so that git can completely skip the diff steps and we avoid all merge conflict conflicts.
At this stage we get to the trick meat. First, we delete the entire result of the merge, since it is practically useless: we do not want the work tree to be received from the tip A command, but from the vertex B Then we check each file from tip B , making it ready to commit.
Finally, we complete the new merge, which has the old tip A as its first parent and the tip B as its second parent, but has a tree from commit B
If the schedule before fixing was:
...
then the new chart is now:
...
The new merge base is the end of B , as usual, and from the point of view of the fixation schedule, this merge looks exactly like any other merge. What is unusual is that the source tree for the new merge at tip A exactly matches the source tree for tip B
1 In this particular case, since the two branches remain independent (never merged), this is also probably the point at which one of the two branches was created (or, possibly, even where they were created), although you can prove that at the moment (because someone could use git reset or various other tricks to move branch labels in the past). However, as soon as we begin the merger, the merger base is obviously no longer the starting point, and a reasonable starting point becomes harder to find.
2 Technically, a merger is any act with two or more parents. git calls merge with more than two "octopus merges" parents. In my experience, they are not common except in the git repository for git itself, and, in the end, they achieve the same thing as a few ordinary two parent merges.
3 Diffs are usually executed internally to some degree, rather than actual commands being executed. This allows for many short optimizations. It also means that if you write a custom merge driver, this custom merge driver will not start unless git detects that the file has been modified in both diffs. If it is changed in only one of the two differences, the default union simply accepts the changed.