git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [RFC] [PATCH 0/5] Implement 'prior' commit object links (and

From
Junio C Hamano <junkio@cox.net>
Date
Apr 29, 2006, 20:44 UTC
Message-ID
<7viros1585.fsf@assigned-by-dhcp.cox.net>
In-Reply-To
<e30b48$ovk$1@sea.gmane.org>
Jakub Narebski <jnareb@gmail.com> writes:
>  * "prior" - heads that represent topic branch merges

This is not any different from usual "parent" at all (but you have to think about it a bit to realize it).

Before talking about making a new commit object that links to other related commits, let's first talk about what it means to update the branch head ($GIT_DIR/refs/heads/<branch>) from commit A to commit B. Understanding what it means is more fundamental.

A git "branch" points at the tip of one possible history of a development. As the often-used word "topic branch" tells you, a "branch", i.e. that history, has a specific purpose. The purpose of my "master" branch is to give reasonably stable new feature set and bugfixes, "next" to give testable ones, and "pu" to collect remaining bits that are worthy of discussion.

When your branch head points at commit A and you update the head to point at a different commit B, you are making this statement:

	The commit B suits the purpose of the branch better
	than the commit A.

Notice there may or may not be ancestry relation between these two commits at this point of the discussion. B may be a direct child of commit A, a merge that has A as its first parent, a merge that has A as its one of its parent (but not necessarily the first), or a Nth-generation descendant if the update was a fast forward merge from another branch. It might even be an ancestor if the update rewinds the history.

Among the above cases (and there may be others), in only two cases you actually create a new commit to record that statement [*1*].

The simplest case is when commit B is a direct, single-parent child of commit A, and that statement is in your commit log message. "I started out from the commit A, and the result is this tree. The result suits what I am doing better than the previous commit and I made the world a better place." -- the "I started out from the commit A" part is on the parent header and the rest is in the free-text.

When you are creating a merge of N parents, the principle is the same. Although in pure core-git terms all parents are equal, in practice, the first parent has somewhat special meaning to you. When the parents of commit B are A and X, you started out from the commit A. Then what are other parents? You can read such a commit this way:

	I started out from commit A and came up with this tree,
	which suits my purpose better.  While doing so, I have
	also considered what X has; and this result, commit B,
	suits my purpose better than X, too.

This is why a later merge with another branch that further builds on top of X works so well.

    ----A----B
            /
       ----X----Y

If somebody built Y on X independently from us, when we merge with Y, we say the merge base is X because B says "I've already considered what X has" to do a 3-way merge. While that is what happens at the mechanical level, what is happening at the philosophical level is we are taking "I consider that B is better than X", part of the message seriously, which means "I want to keep changes I made between X B". Also the other person who made Y made a similar statement that she considers Y is better than X, and we try to preserve the changes between X and Y in the automated part of the merge while preparing the tree to commit the merge between B and Y.

Once you start reading the commit parent to mean " considering what all of these commits have, what this new commit has suits my purpose better", it becomes clear that the "previous" pointer for a branch like my "pu" is just another "parent".

I rebuild "pu" from the tip of then-current "next", and merge other topics in, and discard the previous "pu". So it results in this kind of graph:

                         o---o---o---o---o (updated "pu")
                        /   /   /   /  
        ---o---o---o---o
            \               \   \   \   \
             o---------------o---o---o---o (previous "pu")

But theoretically, I could include the previous "pu" tip as one of the parents of the updated "pu" branch.

At the mechanical level, I start from then-current "next" and merge each topic branch one-by-one on top of it. But at the philosophical level, what I am doing is to publish material that shows a set of proposed changes that are more appropriate for review by the curious than the previous round of "pu" head used to have. So the previous "pu" _is_ in the consideration while I publish the updated "pu", although it is _not_ recorded anywhere.

After I come up with a fully merged tree, I could make a fake Octopus that has the previous "pu" as its first parent and each of the topic branch heads merged as second and subsequent parents, with the resulting tree. That would be more "honest" at the philosophical level.

I am not going to actually suggest anybody doing this as a good practice, but we can make such a commit with the current tool like this:

        git checkout pu
	git tag -f prev-pu		;# remember where we were
	git reset --hard next		;# start at next
        git pull . topic-1		;# merge all remaining topics
        git pull . topic-2		;# ...
        git pull . topic-3
        ...
        git tag -f next-pu		;# this tree is what we want
        git reset --hard prev-pu	;# start from previous
        git pull --no-commit -s ours . next topic-1 topic-2 ...
	git read-tree -m -u next-pu	;# record a merge whose first
	git commit			;# parent is previous pu and
					;# has all the topics merged.
        
[Footnote]

*1* IOW, we _are_ losing some information by not recording the fact that fast-forward was done while doing so.

That record should _not_ be in the commit chain. At the mechanical level, recording that in the commit chain means two criss-crossing branches never converge at the commit chain level, which is already bad. At the philosophical level, the commit chain is a mesh of many possible "global" histories, and the record that somebody (a particular branch in a particular repository) was at what point in the mesh at given time does not belong there.

But from the repository-owner's point of view, that _might_ be a useful information to keep. I am just saying this preemptively so that if somebody wants to record it, that should not be recorded in the commit object.

Previous: Jakub NarebskiNext: Jakub Narebski
Message 6 of 16 in “Re: [RFC] [PATCH 0/5] Implement 'prior' commit object links (and”
  1. linux@horizon.comApr 29, 2006
  2. Linus TorvaldsApr 29, 2006
  3. Jakub NarebskiApr 29, 2006
  4. Junio C HamanoApr 29, 2006
  5. Jakub NarebskiApr 29, 2006
  6. Junio C HamanoApr 29, 2006
  7. Jakub NarebskiApr 29, 2006
  8. Jakub NarebskiApr 30, 2006
  9. Junio C HamanoApr 30, 2006
  10. Junio C HamanoMay 1, 2006
  11. Sam VilainMay 1, 2006
  12. Jakub NarebskiMay 1, 2006
  13. Junio C HamanoMay 1, 2006
  14. Sam VilainMay 2, 2006
  15. Martin LanghoffMay 2, 2006
  16. Sam VilainMay 1, 2006

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.