5 ms·
The exact way in which git handles commits is very muddied - it's snapshots on the surface, a bit of diffs when packed and a lot of operations on commits are ac
by goku12 3y ago
The exact way in which git handles commits is very muddied - it's snapshots on the surface, a bit of diffs when packed and a lot of operations on commits are actually 3-way merges (including merges, rebases, cherrypicks and reverts). Keeping track of all these matter (esp the operations that use diffs), but it can also get overwhelming for a tool.
In my opinion, it's probably good enough to understand the model git is trying to emulate. Commits are stored more or less like snapshot copies of the working tree directory with commit information attached. The fact that there is de-duplication and packing behind the scenes is more a matter of trying to increase storage efficiency than of any practical difference from the directory snapshot model. Meanwhile, the more complex git operations (merges, rebases, reverts, etc) use actual diff algorithms and 3-way merges (way more often than you'd imagine) to propagate changes between these snapshots. This is especially apparent in the case of rebases, where the snapshot model falls completely on its side (modifying a commit will cause the same change in all subsequent commits).
This actually makes sense if you consider the development workflow of linux kernel before git. Versions were directories or on CVS and a lot of development was based on quilt, diffutils and patchutils. Git covers all these use cases, though it may not be immediately apparent.
Added later: It's also interesting to look at Mercurial's model. Like Git, Mercurial uses both snapshot and diffs for storage. But unlike the Git way of layering these two, Mercurial interleaves them - as diffs with full snapshots occasionally. This is more like the video codec concept of keyframes (I think that's what inspired it). This means that Mercurial, unlike Git, doesn't need repacking. And while Git exposes its internal model in its full glory, Mercurial manages to more or less abstract it away.
- nerdponx 3y agoWell-said, although I disagree that it's "muddled". The data model is that commits are snapshots, and diffs between snapshots are computed as needed. The whole system is designed around this. Packing is an implementation detail. The fact that internally it can store snapshots as diffs is more or less unrelated to the user-facing diffs. IMO it's confusing to even mention it in an educational context, except in response to the question of "how does Git prevent repo size from exploding?".
- goku12 3y ago> although I disagree that it's "muddled" I understand. I meant muddied (not muddled) in the sense that it can be confusing for beginners. For some reason, many long-time git users also don't seem to progress beyond the initial image they have. (That includes me too - I struggled with rebases for a long time). That description wasn't a criticism of the git model. Git model is clear if you take some time to study it. > Packing is an implementation detail. > The fact that internally it can store snapshots as diffs is more or less unrelated to the user-facing diffs. My point exactly! To summarize, a git user needs to remember only two things: 1. Git commits are modeled as snapshots of work tree. 2. Many operations are (user-facing) diff-based. Every other detail is a finer implementation detail that's good to know but not essential to get started.
- codeflo 3y agoThe thing that's muddied for beginners are bad YouTube tutorials (which the internet is full of), not Git or the actual documentation. People should really read the Git documentation, it's very well-written and explains the correct mental model. Also, people really shouldn't teach implementation details to beginners. Or intermediates. Perhaps anyone who casually mentions that Git stores diffs to anyone not currently opening the source code for Git itself should be disqualified from ever giving explanations for technical stuff ever again.
- goku12 3y agoI agree with your point about the official Git documentation. It is the only one I learned from and it's easy and comprehensive partly due to the involvement of actual git developers. But there is one area where I wish they stressed a bit more. Git documentation talks about the snapshot model so many times - you're never left in doubt how it's stored (including packing). But they don't stress particularly upon the fact that rebases, merges, cherrypicks and reverts are based on diffs (3-way merges). For example, I was expecting the 'drop' operation in interactive rebases to just delete that commit and leave all the subsequent commits intact (except for the DAG linkage). But to my surprise, the change introduced by that commit disappeared from all subsequent commits - leading me to suspect that they were using diffs in this stage. I eventually found a single confirmation of this in the official documents. But it's obscure. In fact, I tried and failed to find it for reference in this reply.
- cerved 3y ago> This is especially apparent in the case of rebases, where the snapshot model falls completely on its side (modifying a commit will cause the same change in all subsequent commits). I disagree. During a rebase is precisely the time the diff model is problematic. A modified commit does not cause changes in subsequent commits. Modifying commit A is modifying that commits snapshot into A~. Now the subsequent commits will be cherry-picked on top of A~. If there are subsequent commits with changes that depends on A, you have a merge conflict. A~ does not cause changes in B~, C~. The changes of B are applied on top of A~ becoming B~, the changes of C are applied on B~ etc. Thinking of commits as diffs during rebase is a recipe for confusion
- goku12 3y agoYou missed the point I was making about snapshots and diffs. In git, the identity of a commit isn't a diff/change. It's a snapshot. Many operations like commit, push, fetch, etc require you to think so too. Based on that definition, the commits are essentially changed if snapshots change - even if the change introduced by them remains the same. It's clear by your own definition that B~ and C~ are not the same snapshots/commits as B and C. They have absorbed the changes from A to A~ (or the delta from A to A~ is now reflected in snapshots B~ and C~). The fact that diffs on B and C remained the same in B~ and C~ is irrelevant to the commits' identity. > Now the subsequent commits will be cherry-picked on top of A~ Here is the important point. Cherry picking is implemented as a 3-way merge. It involves actual diffing algorithm. > Thinking of commits as diffs during rebase is a recipe for confusion Here again, there are two issues. I didn't say that commit have to be thought of as diffs. I said many operations (incl rebase and cherrypicking) use diffs to propagate changes between snapshots. This reasoning is necessary to understand why snapshots B~ and C~ are different from B and C. The second part is that thinking of rebases in terms of diffs is far from a recipe for confusion (3-way merges actually, but diff is an easier approximation). It actually help me understand the operations and allowed me to predict the results of different operations in advance. That single realization actually made Git far more approachable for me and gave me the confidence that I can solve most Git issues without having to delete the copy and cloning it again.
- 3y ago
- topaz0 3y agoI agree -- so many of the advertised advantages of git depend on operations on diffs, especially the confusing ones that people find difficult to learn, which makes it very confusing for beginners and casual users when they hear "commits are snapshots" said in a tone that seems to imply that thinking of them as diffs is an abhorrent error. Yes, understanding that they are conceptually snapshots is useful, but git wouldn't be git if it didn't do a ton of work on diffs day to day.