All Git Tales
August 6, 2026 Wilfried Nesensohn 4 min read

Commit, Merge, Branch, Tag - WHAT?

Git's interface can feel strange, but its core data model is small. This tale explains commits, merges, branches, HEAD, and tags as a few connected building blocks.

Git, The Monster?

When explaining Git to newcomers, I (only half)-jokingly tell them that Git is a nice data structure with a strange UI.

Git can be intimidating at first, and I attribute most of this to the peculiarities of its UI. To be honest, I try to stay away from the Git TUI as much as possible, as even I get confused sometimes by the interesting naming and design choices of the early days.

But to get back on topic, Git is actually very, very well thought out at its core. And it’s not that complicated either. As long as you understand the basic concepts, you’ll be able to navigate the Git jungle without much effort, using whatever tool you like, as it all boils down to a few key building blocks.

So what are these building blocks?

The Commit

Let’s start with the commit, which is, without a doubt, the star of the show. Simply put, a commit is a set of files (encapsulated as a tree) and its metadata, as well as additional metadata of the commit itself, such as date, author, the ID of its ancestor … And that’s it, there’s not much more to say. If you have a commit, identified by its ID, you have all the data you need to get to all the files which are part of this commit.

The Special Commits

Let’s continue with the merge commit, as in a VCS this is kind of important. However, a merge in Git isn’t special at all. It’s just a regular commit, with more than one ancestor.

There’s no practical limit in the number of ancestors a commit can have, but a commit with more than two ancestors is called octopus merge, while a commit with zero parents is a root commit, and a commit with one ancestor is just called commit.

In practice, most merges are formed by merging the ancestor commits on a file-by-file basis, but there’s nothing stopping you from having a merge commit whose contents have nothing to do with any of its ancestors. You can also have as many root commits as you like. And of course, you can have a lot of fun with octopus commits as well!

The Place where Commits Live

To identify commits, Git uses the ID which is, in essence, a hash over the commits contents and its parents. That means the ID is unique for every commit in your repository.

That’s useful if you want to get a specific version of your files, but when working on a feature it’s useful to have something pointed on your latest version. This “thing” pointing to your lastest version is called branch.

Of course, a branch can point to anything, and you can re-point (or reset, as it’s called in Git) it to any other commit, but importantly, this branch pointer is always advanced automatically by the Git tooling when you commit some changes.

Know Where Your Head Is

There’s an additional tiny concept which is missing from the explanation above. How does Git know which branch pointer to advance when committing? After all, a branch is just a pointer to a commit, and there could be many branches pointing to the same commit, so we need one more indirection.

This is called HEAD in Git. Technically this is a symbolic reference (symref), and often it’s a file under the .git directory. It usually points to the branch you’re working on, and the branch it’s pointing to is the branch which will be advanced when committing something new.

Under some circumstances, it can happen that HEAD is not pointing to a branch, but to a single commit instead. If that’s the case, we call this a “detached HEAD”, and while it’s technically possible to work in a repository with a detached HEAD, it’s not advisable for newcomers to do so. And even if you’re a Git expert, the number of times you have to work in this way will often be zero. So if you find your HEAD detached, make sure to attach it again, for example by checking out a branch.

The Ugly Duckling

That leaves us with the last thing you might need to know, the tag. A tag is essentially like a branch - a pointer to a commit, with the difference that a tag is never advanced automatically. It’s just a way to give a commit a name (and also prevent it from ever being garbage-collected), and you can use Git perfectly well without ever creating a single tag - it’s entirely optional.

To make it easier for you and your colleagues: Don’t think of a tag as a lightweight branch. It’s not in any way lighter than a branch. If you need to temporarily mark a commit, a branch works just as well, and doesn’t carry the risk that it will spread everywhere.

Wrapping It Up

And that’s basically it, these are the building blocks. The actions you can perform on your repository alter these blocks in various ways, but as long as you remember that commits identify the file-contents plus the IDs of their ancestors, and branches are just simple pointers with no additional data at all, most actions in Git shouldn’t pull you off track anymore. Nice.

Git fundamentalsCommitsReferences