1

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

and in the diagram it would be like

       .__________.
      /            \
o -- * -- o -- o -- *
|      \
o       \
|        \
o -- o -- *
|        /
o   .___/
|  /
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).

  • 2
    Can you draw the corresponding diagram of what you want to achieve? – mkrieger1 Apr 26 '22 at 19:54
  • 1
    You can't, but what benefit would this provide if you could? Note you can already list all the tags in the repo and filter the list however you'd like; you don't need a new "path" for that. – TTT Apr 27 '22 at 04:00
  • @TTT A benefit such as clarity of mind, for starters. Also, for public projects, it would be helpful to have a branch pointing only to "public-facing" tagged commits. In fact, this is already done in many repos - with a master branch "passing through" all versioned commits, what is accomplished via merges -, and what I envisioned (and wasted hours and hours trying to accomplish) was to create that branch after the commits already exist. – Eduardo Fischer Apr 30 '22 at 14:06

3 Answers3

2

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.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Can you clarify exactly why there is really no way to accomplish it? I would presume that the argument is that (claim 1) since the hashes depends on the parents the parents of a commit are immutable, and (claim 2) if there is any branch in which commit 2 succeeds commit 1 then commit 1 must be a parent of commit 2, then (claim 3) my goal is not achievable. I agree claim 1 and claim 2 imply claim 3, and I agree that claim 1 is true, but is claim 2 really true (if that is what you mean)? – Eduardo Fischer Apr 26 '22 at 22:21
  • 1
    It's not that claim 2 is false, it's that claim 2 is _meaningless_. The phrase "branch in which" is nonsense. You have a false idea of what a branch _is_. You seem to think a branch is a collection or list of commits — that it has extension. It doesn't. A branch is a _point_, not a _line_. A branch is the name of _one commit_ — and that's _all_ it is. Everything else is parentage, the directional graph that constitutes the history. You can change what commit a branch name points to. But you cannot alter the parentage of that commit or any commit. – matt Apr 26 '22 at 23:49
  • Since everything you are asking is based on a total misconception of what Git is, it might help you to know what Git _actually_ is. For that purpose, I've written an introduction to Git that will tell you. When you've read it, you will know _exactly_ why you can't do the thing I say you can't do. https://www.biteinteractive.com/picturing-git-conceptions-and-misconceptions/ – matt Apr 26 '22 at 23:51
  • Thanks. I was operating under the wrong mental model. In addition to branches being "merely" pointers, I have now learned that the output of `git log` is derived not from information stored in branches but rather from the parentage of the commit (and parents are immutable, being part of the input used to compute its hash), and that is the reason why the original goal was unachievable. – Eduardo Fischer May 01 '22 at 21:45
0

(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.

2Git won't let you move a tag, so unless you use git tag -f to forcibly re-create an existing tag, or delete and then re-create the tag, the commit hash ID selected by any particular tag remains constant. We could just use the raw hash IDs, but humans have an aversion to these.

(Note that pre-1.8.2, Git accidentally applied branch-name rules to tag-name updates during git fetch. That is, the name motion was allowed if and only if it was a fast-forward. This was just a bug.)

torek
  • 448,244
  • 59
  • 642
  • 775
0

You can't change "weave through" only certain commits on a branch at commit time. You can, however so something similar at git log time of your branch consists mostly of merges. Merge each release into the master branch one after the other:

git checkout master
git merge vX.Y.Z
git merge vX2.Y2.Z2
...

You might want to use the theirs merge strategy to ensure that you get the latest version in master in the event of a merge conflict, rather than having to resolve the conflicts manually.

You can show only the merge commits, i.e. one for each release, by using the "--first-parent" option of git log

git log --first-parent master

will show you all of the merge commits only, which will have a 1:1 correspondence with the releases.

rjmunro
  • 27,203
  • 20
  • 110
  • 132
  • 1
    I'm pretty sure you meant to checkout `master` there... – TTT Apr 27 '22 at 03:19
  • At first read I initially thought this was the answer too, but now I realize the question is asking how to traverse only the tags and not include everything else too. – TTT Apr 27 '22 at 03:46
  • @TTT Whoops on the branch name, fixed. Re-reading the question, I think what they think they want is impossible, but by merging they can achieve what they actually need if they use `--first-parent` to view the logs. I've added that to my answer. – rjmunro Apr 27 '22 at 12:18
  • I question if first-parent quite gets there either, since the desired (tagged) commits would be the second parent of each merge commit. – TTT Apr 27 '22 at 14:03
  • @TTT it's not perfect, but it's a common way to do things in git. The merges map 1:1 with the releases, and the commit messages will say so. What the OP wants is impossible. It's not what git branches are. – rjmunro Apr 29 '22 at 17:52
  • I think the strange thing with the question is that none of the release commits are going back into `develop`. If that's the true intent, then merging them together on `master` seems equally odd from a *state* standpoint. I might suggest adding `-s ours` to each of those merges so that the state of `master` doesn't ever actually change, and then the **only** purpose of the `master` branch is to show the list of releases in a particular order with the first-parent view of the merge commits. – TTT Apr 29 '22 at 18:13