Home > OS >  Create branch containing specific, pre-existing commits
Create branch containing specific, pre-existing commits

Time:04-27

A project has the following structure: a master branch whose only commit is M, a develop branch (vertical branch) and many release-X.Y branches (horizontal branches). The o's are regular commits and * are vX.Y.Z-tagged commits. Some useful improvements within release-X.Y branches were merged back into develop (not indicated in diagram).

o -- * -- o -- o -- *
|
o
|
o -- o -- *
|
o
|
M

How to make the master branch "weave through" and "catalog" all the tagged commits in the correct [i.e. lexicographic] order without altering the existing structure?

That is, if the horizontal branches are release-0.1 and release-0.2 and the tagged commits v0.1.0, v0.2.0 and v0.2.1, how to make master such that git log master would produce (modulo extra information)

v0.2.1
v0.2.0
v0.1.0
M

(Of course, the specific tree and whether or not the desired commits are tagged is ultimately immaterial to the question, but hopefully it helps to build the context motivating the question: to have master catalog all "official" commits of a software.)

I have looked up possibilities using merge, commit, rebase and cherry-pick commands, but none appears to work (some can create new content-equivalent commits, but the goal is to keep the same exact tagged commits).

There are three questions with a similar title but they are quite different in intent: one, two and three.

I presume it is possible to use low-level instructions which manually set up references and objects to stack the tagged commits on master the right way but I would prefer to have a more systematic, higher-level solution (if there is any).

CodePudding user response:

some can create new content-equivalent commits, but the goal is to keep the same exact tagged commits

You can't. A Git commit, including its parentage, is immutable. And a good thing too! Otherwise Git would be useless.

So creating "new content-equivalent commits" is exactly what you would need to do.

CodePudding user response:

(See matt's answer first.)

Drawing branches, in Git, is a little tricky.

In some version control systems, a branch is a real thing. That is, you make a branch and populate it with commits, and those commits are on that branch, and only that branch, now and forever. So when drawing commits in such a repository, we can put the branch name on the left, and list the commits on the right, like this:

br1:   A--B--C

br2:   D--E--F

br3:   G--H

Commit C is on br1 (only), and will always and forever be on that branch, for as long as the commit continues to exist. Commit H is on br3 (only), and will similarly be on that branch forever. We cannot have commits without having branches (though we could, at least in theory, have branches without having commits).

But this is not true in Git. In Git, a branch name is a moveable label. It has no separate existence: it is ephemeral, unreal, a mere shadow that passes. Its purpose is to help us locate one specific commit, which we call the tip commit of the branch. As such, if we're drawing commits left-to-right as we do above, we should put the branch name on the right.

The connections between commits, in Git, are not there because of which branch they are "on". Instead, they're permanently part of the commits themselves. Those connections exist independent of any branch. We can even have commits that are on no branch at all, as the commits themselves are independent of the branches.

What we cannot have, in Git, is a branch without a commit. The existence of a branch name requires a specific commit for it to point-to, and more than one name can point to any specific commit. So we must draw the names on the right, with arrows coming out of them to show which commit it is that they point-to:

A--B--C   <-- br1
       \
        D--E--F   <-- br2
               \
                G--H   <-- br3

Commits A-B-C are on all three branches; commits up through F are on two branches (they're not "on" br1 but are on both br2 and br3); and commits G-H are only on one branch, br3. At least, that's the case right now. Should we choose to do so, we can delete the name br1 entirely:

A--B--C
       \
        D--E--F   <-- br2
               \
                G--H   <-- br3

We no longer need the first kink in the graph, between C and D, as commits up through F are all on the two remaining branches. G-H remain only on br3. Should we delete br2 as well, all commits are now on one branch, br3. We may even create a new name, br4, or re-create the old name br1, or move it:

A--B--C--D--E--F--G--H   <-- br1, br3

and now all commits are on the two branches.

What this means is that in a very important sense, Git's branches aren't real. They don't even exist! If something is X today, but is Y tomorrow, what's the point of calling it X?1 But of course, us ephemeral humans have a fondness for other ephemeral things, so we go ahead and use these phantoms as long as they help and/or amuse us.

The real lesson here is that you cannot and should not depend too much on branches in Git. They're just temporary service entities. Tag names, when used correctly,2 are much more solid: use tags to tag commits that have some sort of historical significance.


1Exigency, probably.

  •  Tags:  
  • git
  • Related