DEV Community

Samuel Rouse
Samuel Rouse

Posted on

Git Bits: The Graph

Git is...complicated. This series focuses on breaking git down into commit-sized pieces.

The Graph

The foundation of git is a directed acyclic graph. You can dive into the Wikipedia article for more information than you probably want, but the basics as they relate to Git are this:

  • Each commit in a Git repo is a node on the graph.
  • Each commit "knows about" (references) the one or more nodes that came before it.
  • We can trace the history of commits through the graph.
  • We can't go backward, so there are no loops, but it is possible to have multiple paths between two nodes.

Notes about Graphs

Example graph of commits on multiple branches


Fig.1 - Example graph of commits on multiple branches

This graph was created with the absolutely amazing Mermaid.js. It represents commits with a number and a hash by default, like 0-2b99de5. The number is the order they appear in the graph definition. The hash is a randomly generated hexadecimal to simulate what actual hashes in git look like.

Nodes

There are mostly two types of "nodes" on the graph in git: commits and merges.

You can see that 2-a0f289f on the develop branch has a line that leads back to 1-10ed042, which in turn has a line that leads back to 0-2b99de5, the start of this graph. You can walk the graph and see the history of each commit in this way.

Merges


Fig.2 - Example graph pointing out a merge commit

Looking at the graph, you can see a node where the changes from the feature branch are merged into develop. That node is connected to two parents: 5-ca2ed61 on develop and 6-fdcd8eb on feature.

It is possible to perform a merge of more than two sources – an "octopus merge" – but it is very uncommon.

Branches

Git's branching system is sometimes referred to as lightweight branching. Compared to some version control systems which make whole copies for new branches, git branches are essentially just a pointer to a commit on the graph, so making a new branch is basically instantaneous. Let's see that in action.

I've created an empty array and committed a simple README file. If I run git log, it gives me the commit.

commit 1df473a77206a3208ef8c9213dbd298e0ad9b369 (HEAD -> main)
Author: Me <me@example.com>
Date:   Fri Sep 19 12:10:39 2025 -0400

    Initial commit
Enter fullscreen mode Exit fullscreen mode

If I make a new branch with git branch develop and run git log again, I see that this same commit is now represented on main and develop.

commit 1df473a77206a3208ef8c9213dbd298e0ad9b369 (HEAD -> main, develop)
Author: Me <me@example.com>
Date:   Fri Sep 19 12:10:39 2025 -0400

    Initial commit
Enter fullscreen mode Exit fullscreen mode

Even though I've created a branch, I haven't changed the contents of the repo. The branch is metadata – information about the commits.

Divergence

Interestingly, the branch doesn't keep track of every commit on it. The branch only has to know about the latest commit, because that one knows about the commit that came before it, and so on.

Going back to our branch diagram from earlier, the develop branch can build the list of commits in its history, and discover where it has one in common with main – 0-2b99de5, so we can say develop diverged from main at 1-10ed042.

Timeless Representation

One important distinction is that time is not actually a necessary part of the graph. We think of them in time order because it's helpful to us, but the graph is really only concerned with direction.

The HEAD

While there are timestamps on commits, there's really only one time git is interested in: right now. Git represents "wherever you are right now" as a value called HEAD.

If we look at that git log command again, we can see that HEAD is pointing to the main branch, even though we also created a develop branch.

commit 1df473a77206a3208ef8c9213dbd298e0ad9b369 (HEAD -> main, develop)
Enter fullscreen mode Exit fullscreen mode

If we git checkout develop we'll still be on the same commit, but HEAD will be different.

commit 1df473a77206a3208ef8c9213dbd298e0ad9b369 (HEAD -> develop, main)
Enter fullscreen mode Exit fullscreen mode

You can use commands to move back from HEAD, like git checkout HEAD~1 which will check out one commit before the current HEAD.

Re-writing History

The Graph keeps track of the relationship between things, but it is not written in stone. There are several ways you can change the past, but, just as in the movies, there are risks of doing so.

Most of the concerns with re-writing history are around shared branches. Because git is decentralized, there isn't something that says which history is correct if histories diverge. You create a different timeline that didn't happen for everyone. It's a science-fiction nightmare, but for code.

Still, when you are on your personal branch, re-writing history can be really useful.

git commit --amend let's you change the last commit you created. If you misspelled a word or forgot an update, --amend can let you create a history where that didn't happen. For simple changes that exist only for you, this is an easy way to avoid having a commit with an obvious error.

Rebase

Rebasing has a bad reputation. Rebasing is when change history by "moving the start of your branch". This usually happens during parallel development when two or more people are submitting changes to a branch or repository. When one developer's work is accepted and merged, another developer may need to incorporate that work.

Mermaid GitGraph showing parallel development


Fig.3 - Branch feature1 is merged in while we are working on feature2

Now that feature1 is merged, we have a couple options. We could create a merge from main to feature2 with git merge main while we are on the feature2 branch.

Mermaid GitGraph showing a merge from main to a feature branch


Fig.4 - Merge main into feature2

If feature2 is a branch that multiple people are working on, that's usually the correct solution. But if it's just one developer, use git rebase main creates a "cleaner" history, because there is no longer a point where history diverged.

Mermaid GitGraph showing feature2 branch after rebasing


Fig.5 - Rebase feature2 onto main

We can say, "feature2 was rebased onto main." History has changed, and there is no record that it was ever attached to the earlier commit.

We'll talk more about rebasing another time. But from the simple diagrams we can see that rebase picks up part of the graph and connects it somewhere else.

Resources

Some information in this article comes from the unassailable git-scm.com site and the Pro Git Book available to read for free, there.

The section Branches in a Nutshell provides some really good detail and diagrams on the graph and branching.

Summary

Let's bring it back together. Git is a directed graph of commits, each representing a snapshot of the repo. Branches and tags are metadata that point to specific commits. You can add to, change, or even move parts of the graph.

git commit -am 'The Graph'
git push
Enter fullscreen mode Exit fullscreen mode

Top comments (0)