We reach the last lesson of the course, and the question to ask is the most honest one that can be raised at the end of any technical training: how much of all this will still be true in ten years' time?

Git is already two decades old. It was born in 2005 for a very specific problem — coordinating the development of the Linux kernel when its team lost the tool it was using — and it became the universal standard for version control. But it is not frozen: a new version comes out every few months, and some of those versions bring deep changes that hardly anybody uses yet.

This lesson carefully separates three things that tend to get mixed up:

  1. What already exists and you can use today, even if it is little known or marked as experimental.
  2. What is a trend: where the work is pointing, with no dates and no promises.
  3. What almost certainly is not going to change, and which is precisely what you learnt in module 1.

And then we close the course: how to carry on learning on your own, and a recap of the complete road.

A methodological warning before we start: in what follows I shall give no dates for anything that is not already available, and when I mention a version of Git I shall do so as an approximate reference. Software evolves and the release notes are the only reliable source. What I can give you is the judgement to assess each thing: what problem it solves, what state it is in and whether it suits you to adopt it.

Contents

  1. How Git evolves and how to keep up
  2. SHA-256: the replacement for SHA-1
  3. reftable: a new format for references
  4. Partial clones and the sparse index
  5. commit-graph and git maintenance
  6. switch and restore versus checkout
  7. Maturity table: what to use today
  8. Trends: where the work is pointing
  9. The role of AI assistants
  10. What is not going to change
  11. How to carry on learning
  12. The complete course, recapped

  1. How Git evolves and how to keep up

Git is developed in the open, by email, with the model we studied in lesson 10-01: patches on a public list, a maintainer who integrates, and periodic releases every few months. Each version brings a notes file describing what is new, what has been fixed and what changes in behaviour.

Knowing which version you are on and what it brings is the first habit:

git --version
git version 2.51.0

And the notes for each version are in Git's own repository, in Documentation/RelNotes/. Reading them for each new version takes five minutes and is the best way of discovering things you have been doing wrong for years.

One detail worth knowing: Git is extraordinarily conservative about backwards compatibility. A repository created in 2006 opens today with no conversion, and the commands you learnt carry on working. That is one of the reasons why new features take so long to reach common use: they are introduced as optional, they coexist with the old ways for years, and they only become the default when the whole ecosystem is ready.

This has a practical consequence for you: almost nothing you have learnt is going to stop working. What may happen is that better ways of doing the same thing appear.

  1. SHA-256: the replacement for SHA-1

Why it is needed

Recall from module 1 that everything in Git is identified by the SHA-1 hash of its content: blobs, trees, commits and tags. That hash is the axis of the entire system. And SHA-1, as a cryptographic function, is broken: in 2017 it was publicly demonstrated that it is possible to construct two different contents with the same SHA-1 hash (a collision), at a high but achievable computational cost.

It is worth being precise about what that means for Git, because it gets exaggerated in both directions:

Concern Reality
"Git is broken, anybody can forge commits" No. A useful collision requires constructing both contents deliberately; it does not allow you to manufacture an object matching an existing one you do not control
"SHA-1 in Git is just an identifier, not security" Partly true, but the hash is what guarantees integrity and what commit signatures sign (08-05)
"It does not matter, nothing will ever happen" It is a known weakness in a system that underpins the world's software supply chain

Git did not sit still. For years now it has incorporated collision detection: a variant of the SHA-1 computation that detects the characteristic patterns of a collision attack and rejects the object. The files from the public 2017 attack are rejected by Git.

But that is a patch. The underlying solution is to change hash function.

What exists today

Git can create repositories with SHA-256:

git init --object-format=sha256 new-repo
cd new-repo

echo "hello" > file.txt
git add file.txt
git commit -m "First commit"
git log --format=%H
d0e7f5c1b8a4f39e2c6b0d5a7f1e3c9b4d8a2f6e0c5b1a9d3f7e2c8b4a6d0f5e

Sixty-four hexadecimal characters instead of forty. And to check it:

git rev-parse --show-object-format
sha256

Everything you have learnt works the same: add, commit, log, branch, merge, rebase, bisect. The data model is identical; only the function that computes the identifiers changes. It is the best proof that what you learnt in lesson 01-04 is the concept, not the specific algorithm.

The real problem: interoperability

Here is why SHA-256 is still experimental and why hardly anybody uses it:

A SHA-256 repository and a SHA-1 one cannot exchange objects.

git clone https://git.example.com/team/task-manager.git   # SHA-1 repository
cd task-manager
git remote add other /path/to/sha256-repo
git fetch other
fatal: the remote uses a different object format than this repository

There is no conversion on the fly and no transparent translation. And this is an enormous problem, because the entire world — every hosting platform, every existing repository, every tool — is on SHA-1.

The underlying plan, long since designed, envisages repositories capable of operating in both formats at once, maintaining a mapping table between the SHA-1 and the SHA-256 hash of each object, so that a SHA-256 repository can talk to a SHA-1 server by translating at the boundary. That interoperability is the missing piece, and it is a considerable amount of work.

What you should do

Nothing, for now. SHA-256 is useful for experimenting and for understanding the model, and it can make sense in a closed environment that exchanges with nobody. For real work, while the ecosystem is on SHA-1, a SHA-256 repository is an island.

What is worth doing is understanding the distinction, because it is a perfect example of why the data model matters more than its details: the day Git changes hash function, you will not have to relearn a thing.

  1. reftable: a new format for references

The problem with the current format

Recall from lesson 03-01: a branch is a text file with a hash inside it.

cat .git/refs/heads/main
4f8a2e6c9d3b1a5e7f2c8b0d4a6e9f1c3b5d7a0e

It is admirably simple and it works perfectly in task-manager, with its five branches. When there are many references, Git compacts them into a single file:

cat .git/packed-refs | head -5
# pack-refs with: peeled fully-peeled sorted
4f8a2e6c9d3b1a5e7f2c8b0d4a6e9f1c3b5d7a0e refs/heads/main
8a1f6c3d4e5b6a7c8d9e0f1a2b3c4d5e6f7a8b9c refs/remotes/origin/main
2e9f4c7b1a8d3e6f0c5b2a9d4e7f1a8c3b6d0e5f refs/tags/v1.3.0

This system — loose files plus one compacted file — has limits that show up at scale:

Problem Why it happens
Many small files A repository with 100,000 references has up to 100,000 files; filesystems suffer
Expensive writes Updating a reference with packed-refs forces the whole file to be rewritten
Limited concurrency Two processes updating references at once compete for locks
Name collisions refs/heads/feature and refs/heads/feature/new cannot coexist: a directory cannot also be a file
No reference history The reflog is a separate mechanism, with its own format
Upper and lower case On filesystems that do not distinguish them, Main and main clash

That fourth point is the one you have probably already suffered:

git branch feature
git branch feature/new
fatal: cannot lock ref 'refs/heads/feature/new': 'refs/heads/feature' exists;
cannot create 'refs/heads/feature/new'

It is not an arbitrary Git rule: it is that the filesystem does not allow feature to be both a file and a directory.

What reftable is

reftable is a binary format for storing references and their reflog in a small set of sorted files, originally designed for servers with enormous numbers of references and subsequently incorporated into Git.

Its properties:

  • Binary search over sorted, prefix-compressed data: finding a reference among hundreds of thousands is immediate.
  • Append-only writes, with periodic compaction: updating does not force everything to be rewritten.
  • Atomic transactions over multiple references.
  • Integrated reflog in the same format.
  • No filesystem restrictions: feature and feature/new can coexist, and case is always distinguished.

It is used like this:

git init --ref-format=reftable new-repo
cd new-repo
git rev-parse --show-ref-format
reftable

And then:

ls .git/reftable/
0x000000000001-0x000000000003-a1b2c3d4.ref
tables.list

There is no refs/heads/. There is no packed-refs. The references live inside those binary files.

Every command you know works exactly the same. git branch, git tag, git switch, git log, git reflog. The only thing that changes is that you can no longer do cat .git/refs/heads/main, and you have to use the plumbing (lesson 09-06):

git for-each-ref --format='%(refname) %(objectname)'
git show-ref
git rev-parse main

Which is, incidentally, what you should always have done: reading the internal files by hand was never a stable interface. This is a practical lesson that goes beyond reftable: use the commands, not the files.

Status and recommendation

reftable is available in recent versions of Git and is considered functional, but it is not the default format and you have to reckon with external tools that read .git/refs/ directly not working with it.

What you should do: know about it and do not adopt it yet unless you have a repository with tens of thousands of references and are suffering because of it. It is an excellent example of evolution done well: it changes the implementation completely without changing a single command.

  1. Partial clones and the sparse index

You already know these from lesson 10-04, and here they come with the label they deserve in this lesson: they are the present, not the future.

git clone --filter=blob:none https://git.example.com/team/project.git
git sparse-checkout init --cone --sparse-index
git sparse-checkout set services/tasks

Both techniques are available, supported by the major platforms and in production use in large organisations. What is interesting from this lesson's perspective is what they represent: they are Git's answer to a problem its original design did not envisage.

Git was born with an implicit assumption: every clone has the complete repository. That is what makes it distributed and what gives it its resilience (lesson 09-05: any clone is a backup). But it is also what made it unviable for repositories of tens of gigabytes.

Partial clones relax that assumption without breaking the model: the repository still has the complete graph and still knows which objects exist; some of them simply have not been downloaded yet and are requested when they are needed. It is an elegant solution because it preserves the semantics and only defers the transfer.

The trend they represent is clear: Git is evolving towards being able to work with repositories of any size without giving up its model. It is reasonable to expect this work to continue: better management of what gets downloaded, better prefetch heuristics and fewer odd cases.

  1. commit-graph and git maintenance

Also the present, also from lesson 10-04 and 08-06.

git config --global core.commitGraph true
git config --global fetch.writeCommitGraph true
git maintenance start

They represent another line of evolution: auxiliary structures that speed things up without changing the model. The commit-graph is not new information: it is information that was already in the commit objects, precomputed in a format that reads quickly. If you delete it, you lose nothing; it gets regenerated.

That design principle — derived, regenerable, optional caches — is a very healthy one and it is reasonable to expect it to spread. It is the way to improve performance without compromising the simplicity of the storage format, which is Git's most valuable asset.

git maintenance, for its part, reflects a change of philosophy in maintenance: instead of gc interrupting you at the worst possible moment, there are scheduled tasks running in the background. It is reasonable to expect it to end up being the default behaviour.

  1. switch and restore versus checkout

A different case: this is neither performance nor cryptography, it is interface design.

git checkout is Git's most overloaded command. It does conceptually different things depending on its arguments:

git checkout main                    # switch branch
git checkout -b new                  # create a branch and switch
git checkout 4f8a2e6                 # go to a commit (detached HEAD)
git checkout -- app.js               # DISCARD changes in a file
git checkout main -- app.js          # bring a file from another branch
git checkout --ours app.js           # resolve a conflict

Six different operations, and one of them — the fourth — destroys work with no possibility of recovery. That git checkout main (harmless) and git checkout -- app.js (irreversible) should be the same command separated by two dashes is a design problem, and it has cost a lot of people a lot of grief.

The solution was to split it into two commands with clear purposes:

Before Now What it does
git checkout main git switch main Switch branch
git checkout -b new git switch -c new Create a branch and switch
git checkout 4f8a2e6 git switch --detach 4f8a2e6 Detached HEAD, explicitly
git checkout - git switch - Go back to the previous branch
git checkout -- app.js git restore app.js Discard changes on disk
git checkout HEAD -- app.js git restore --source=HEAD app.js Restore from a revision
git reset HEAD app.js git restore --staged app.js Remove from the index
git checkout --ours app.js git restore --ours app.js Resolve a conflict

Notice the detached HEAD row: with switch you have to ask for it explicitly with --detach. That is the end of the "I checked out a tag and now I am somewhere odd" that we studied in lesson 03-02.

And notice the penultimate row: git restore --staged replaces a use of git reset that confused everybody, because reset is a command that moves branches (lesson 09-02) and using it to take a file out of the index mixed two concepts.

Status

switch and restore have been available for years, were marked as experimental for quite a while and in recent versions have been consolidated. They can be used with confidence.

And checkout is not going to disappear. There are millions of scripts, tutorials and habits that depend on it, and Git is conservative about compatibility. They will coexist indefinitely.

What you should do: use switch and restore in your daily work, and know how to read checkout because you are going to find it throughout the existing documentation, in answers on the internet and in your company's scripts. It is exactly the recommendation we made in lesson 03-02, and here it is confirmed.

  1. Maturity table: what to use today

An operational summary of all of the above, plus what you already know:

Feature Status Adopt today? Reason
switch / restore Consolidated Yes, always Clearer and safer than checkout; no risk
commit-graph Stable Yes, always A regenerable cache, no drawbacks, a big improvement on large histories
git maintenance Stable Yes on medium and large repositories Background maintenance instead of interruptions
fsmonitor / untrackedCache Stable Yes if git status is slow Measure first; on small repositories it adds nothing
Partial clones (--filter=blob:none) Stable, widely supported Yes on large repositories and in CI Complete history for a fraction of the download
sparse-checkout --cone + sparse index Stable Only in monorepos with vast numbers of files It adds confusion if you do not need it
Git LFS Mature (external to Git) Yes if there are large binaries that change With the limitations of 10-03
reftable Available, not the default No unless tens of thousands of references External tools may not support it
SHA-256 Experimental No for real work No interoperability with the SHA-1 ecosystem

The column that matters is the third, and its pattern: what has no drawbacks, adopt now; what has a comprehension cost, only if you measure that you need it; what does not yet interoperate, learn about and wait.

  1. Trends: where the work is pointing

Here we leave the verifiable behind. What follows are observable directions in the ecosystem, not dated predictions.

A better experience for monorepos

It is the most visible line of work of recent years, and everything we saw in lesson 10-04 is part of it: partial clones, sparse index, fsmonitor, commit-graph. The motivation is clear: large organisations have committed to enormous repositories, and they need Git to work in them.

It is reasonable to expect more in that direction: fewer odd cases with sparse-checkout, better heuristics about what to download, and perhaps for some of these options to stop being options and become the default behaviour when Git detects that it suits them.

Specialised Git servers

A fact worth bearing in mind: the git a large server runs is not always the git you have. Very large platforms have developed their own server-side implementations — sometimes based on Git, sometimes rewrites compatible with the protocol — optimised for serving thousands of concurrent clients.

For you, this is transparent: you speak Git's protocol and you do not know what is on the other side. But it explains why some platforms support features others do not, and why quotas and behaviours differ (something we already noticed with LFS in lesson 10-03).

The trend is for that specialisation to increase, and for the Git you run on your laptop and the one running on a server to become ever more different on the inside, even though they speak the same language.

Tools built on top of Git, not replacements

This is a twenty-year pattern that shows no sign of changing. Dozens of new version control systems have appeared, and some are technically interesting. But what has succeeded is not the replacements, but the layers built on top: hosting platforms, review systems, graphical interfaces, patch management tools, deployment systems (lesson 10-05), and alternative clients with different working models that store in Git's format.

The reason is the network effect combined with a technical property: Git's storage format is simple, documented and stable. Anybody can write a tool that reads and writes it. That makes Git something like a universal file format, and replacing a universal format is far harder than replacing a program.

The practical consequence for you is reassuring: even if the interface you use changes, the model underneath probably will not.

A better command line experience

There is a sustained current of improvement in the error messages and the suggestions. Git has gone from cryptic messages to messages that explain what to do:

git branch -d GT-231-hidden-filter
error: the branch 'GT-231-hidden-filter' is not fully merged.
hint: If you are sure you want to delete it, run 'git branch -D GT-231-hidden-filter'.

That hint: line did not exist years ago. It is unspectacular and very valuable work, and it is reasonable to expect it to continue.

  1. The role of AI assistants

It deserves a section of its own because it is the most visible novelty in the ecosystem and the one that generates the most confusion. We are going to look at it with the same judgement we have applied to everything else: what it genuinely contributes and where the risk lies.

Where they genuinely help

Drafting commit messages. An assistant can read a diff and propose a message. It works well for the first line — summarising what changed — and it is a good starting point when you are staring at a blank screen.

Explaining somebody else's code or a large diff. Putting into words what a 400-line change does saves real time during a review.

Remembering commands and options. "What was the command for seeing who wrote each line while ignoring whitespace changes?" is exactly the kind of question an assistant answers well.

A first review pass. Spotting suspicious patterns, obvious omissions, inconsistencies. As an informative check, in the sense of lesson 10-02: let it inform, not block.

Generating helper scripts. The scripts of lessons 10-05 and 10-04 are exactly the kind of thing an assistant writes fluently.

Where they do not help, and the risk

The why of a change is not in the diff. And the why is what matters in a commit message (lesson 08-01). An assistant can perfectly well write "adds the hidden-tasks filter to the list" by reading the code. What it cannot write is:

The counter must carry on including them because the GT-231
specification says that "pending" counts every task that is not
completed, whether visible or not.

That paragraph — the same one from lesson 09-06 — contains a decision, a reason and a reference to a discussion. It is in your head, not in the code. An automatically generated message that describes the what and omits the why is a message that has lost its function.

A generated, unreviewed message is worse than none at all, because it looks like documentation and it is not. And the history is the only document in the project that nobody can edit afterwards.

The underlying risk: accepting without understanding. It is the same risk as the "Sync" button of lesson 10-02, amplified. If an assistant proposes git reset --hard HEAD~3 and you run it without understanding it, the problem is not the assistant: it is that you are running destructive operations blind.

And there is a very specific asymmetry worth bearing in mind: Git operations are not equally reversible. A wrong suggestion for git log costs nothing. A wrong suggestion for git push --force, git reset --hard or git clean -fd can cost work that is not recoverable. The more destructive the operation, the more it demands understanding first.

The criterion

An assistant helps you draft, remember and explore. It does not exempt you from understanding the change.

And a practical rule derived from the whole course:

Task Delegate to the assistant Why
Drafting a commit subject Yes, with review The what is in the diff
Writing the body that explains the why No Only you know it
Remembering a command's syntax Yes It is documentation
Running a suggested destructive operation Not without understanding it reset --hard, push --force, clean -fd
Explaining somebody else's diff Yes It saves real time
Deciding whether a change is a good idea No It requires product context
Generating a helper script Yes, reading it And testing it in a test repository
Resolving a merge conflict With great caution It requires understanding both intentions (03-05)

Notice that this table is the same one from lesson 10-02 about graphical interfaces, with different names. The underlying rule has not changed throughout the module: automate the repetitive, understand the irreversible.

And there is an optimistic consequence: in a world where writing code costs less, understanding the history, knowing why a decision was taken and being able to recover from a mistake are worth more, not less. Which is exactly what this course teaches.

  1. What is not going to change

Having looked at what moves, it is time to look at what does not. And this is the most important part of the lesson.

The content-addressable data model

Every object is identified by the hash of its content. The hash may change from SHA-1 to SHA-256; the idea will not. And from it derive, as necessary consequences, properties you have used throughout the course:

  • Objects are immutable: changing the content produces a different object.
  • Identical content is stored once only, on any branch and at any time.
  • A commit's hash verifies its whole history, because it includes that of its tree and those of its parents.
  • Rewriting history creates new objects; it does not modify the old ones. That is why the reflog can recover them (09-04).

That last point is the key to half the course. rebase, amend, cherry-pick, filter-repo: they all create new objects and move references. The originals are still there until garbage collection deletes them. That is not an added feature: it is a mathematical consequence of content addressing.

The directed acyclic graph of commits

Every commit points at its parents. Out of that come branches, merges, common ancestors and everything else:

  • A branch is a moving pointer to a node (03-01).
  • A merge is a commit with two parents, and the base is the common ancestor (03-03).
  • bisect is binary search over the graph (06-02).
  • rebase is replaying a path onto another point (05-01).
  • A divergence is two references pointing at nodes with no ancestor relationship (09-03).
  • merge-base --is-ancestor is a question about reachability, and it is what we used to verify deployments (10-05).

That graph is not going to change. It is too simple, too powerful and too central.

The distributed nature

Every clone is a complete repository, with all the history and full capacity to operate offline. Out of that come:

  • The fact that you can work with no network and synchronise afterwards (04-04).
  • The fact that any clone is a viable backup (09-05).
  • The fact that there is no "official" repository except by agreement (04-01).
  • The fact that forks work (07-01) and that the kernel model is possible (10-01).

Partial clones relax the "complete" for the content, but not the model: you still have the whole graph and you are still autonomous for almost everything.

The three zones

Working copy, index and repository (lesson 01-03). The index is the piece that most bewilders you at the start and the one that gives the most value later: it is what makes add -p possible (02-04), what makes git add a safety net as well, and what underpins conflict resolution with its three stages (03-05).

That is why this course does not expire

You have learnt commands, but above all you have learnt a model. The commands will change names — checkout has been split into switch and restore — and new options will appear. The model has been identical for twenty years and there is no sign of it moving.

When in five years' time you come across a command you do not know, the question that will let you understand it in thirty seconds will always be the same: which objects does it create and which references does it move?

  1. How to carry on learning

With the course finished, the best way to carry on is by using what Git already ships.

The built-in documentation

# Complete manual for a command
git help rebase
git rebase --help

# Quick summary of options, without opening the manual
git rebase -h

# The list of ALL the conceptual guides
git help -g

That git help -g is a discovery for a lot of people. It returns a list of guides that are not command manuals but explanations of concepts:

Guide What it is about When to read it
gitcore-tutorial Git from the plumbing up: how objects are built by hand The best of them all. Read it now
giteveryday The ~20 commands that cover 99% of usage, by role As an ordered revision
gitglossary Precise definitions of all the terminology When you are unsure of a term
gitworkflows How the Git project itself works with its branches A complement to module 7
gitrevisions The complete syntax of HEAD~3, main@{2}, A...B After module 2
gittutorial Basic introduction You are past it
gitcvs-migration Migrating from centralised systems If you have to
git help gitcore-tutorial
git help giteveryday
git help gitrevisions

Start with gitcore-tutorial. It builds a repository from scratch using only plumbing commands: hash-object, update-index, write-tree, commit-tree. After this course, that guide will be readable to you and it will consolidate the mental model like nothing else.

The release notes

Every new version of Git brings a notes file. Reading them when you upgrade is the habit with the best return for staying up to date. It is where you will discover the option you have been missing for years and that has existed for the last three versions.

Practising the plumbing in a test repository

This is what consolidates it most. Create a disposable repository and build a commit without using git commit:

mkdir /tmp/lab && cd /tmp/lab && git init

# 1. Create a blob from some content
echo "Hello from the plumbing" | git hash-object -w --stdin
9f8c2e1a4b7d0f3c6e9b2a5d8f1c4e7a0b3d6f9c
# 2. Put it into the index with a name
git update-index --add --cacheinfo 100644,9f8c2e1a4b7d0f3c6e9b2a5d8f1c4e7a0b3d6f9c,greeting.txt

# 3. Turn the index into a tree object
git write-tree
2a4c8e0f1b3d6a9c2e5f8b1d4a7c0e3f6b9d2a5c
# 4. Create a commit pointing at that tree
echo "My first hand-made commit" | git commit-tree 2a4c8e0f1b3d6a9c2e5f8b1d4a7c0e3f6b9d2a5c
7e1b4d9a2c5f8b0e3d6a9c2f5b8e1d4a7c0f3b6d
# 5. Point the branch at that commit
git update-ref refs/heads/main 7e1b4d9a2c5f8b0e3d6a9c2f5b8e1d4a7c0f3b6d

# 6. Check that Git sees it as a normal repository
git log --stat
git status

You have just done by hand what git commit does for you. Blob → index → tree → commit → reference. It is the model of lesson 01-04 executed step by step, and it is the exercise that separates whoever uses Git from whoever understands it.

Reading other projects' histories

As we saw in lesson 10-01: clone a project you admire with --filter=blob:none and look at how it works. How they write their messages, whether the history is linear, who integrates, how they structure their commit series. There is a great deal to learn.

Teaching it

The best filter for knowing whether you have understood something is explaining it to somebody. When a colleague gets stuck on a divergence or loses a commit, help them by explaining the model, not just by giving the command. If you can draw the graph on a whiteboard and point at where the lost commit was, you have understood it.

  1. The complete course, recapped

Let us close by looking at the whole road.

Where you started

You started not knowing what a repository is. Not knowing what the difference is between keeping a copy of a folder and versioning a project. Ten modules later:

flowchart TD
    M1["1. Understand<br/>Data model, three zones,<br/>hash of the content"]
    M2["2. Build history<br/>add, commit, diff, log"]
    M3["3. Branch<br/>branches, merge, conflicts"]
    M4["4. Collaborate<br/>remotes, fetch, push, tracking"]
    M5["5. Manipulate<br/>rebase, cherry-pick, tags, revert"]
    M6["6. Tools<br/>hooks, bisect, blame, submodules"]
    M7["7. Process<br/>PRs, reviews, workflows, CI"]
    M8["8. Craft<br/>messages, clean history,<br/>security, performance"]
    M9["9. Rescue<br/>reset, reflog, corruption,<br/>debugging"]
    M10["10. Operate<br/>real cases, LFS, scale,<br/>DevOps, the future"]

    M1 --> M2 --> M3 --> M4 --> M5 --> M6 --> M7 --> M8 --> M9 --> M10

The ten modules, in one line each

Module What you take away
1. Introduction That Git is a database of content-addressed objects, not a tool that stores differences. Blob, tree, commit, tag; the three zones; the hash that verifies everything
2. Basic operations The edit → stage → commit cycle, and that git add -p and git diff --staged turn committing into a deliberate act
3. Branches and merging That a branch is a moving pointer and that merging is finding the common ancestor. That a conflict is not an error: it is a decision Git cannot take for you
4. Remotes That there is no central server except by agreement, and that fetch and pull are different operations. Authentication, refspecs, tracking branches
5. Advanced operations That history can be rewritten before publishing it, and the golden rule: never afterwards. rebase, cherry-pick, stash, annotated tags with SemVer, revert
6. Tools That Git brings answers for "when did it break?" (bisect) and "why is it like this?" (blame + the message). Hooks, aliases, submodules, worktree
7. Collaboration That the process matters as much as the commands: proposals, review, workflows compared and continuous integration. And that the workflow is chosen to fit the team
8. Best practices The craft: messages that explain the why, a readable history, .gitignore and .gitattributes, secrets kept out, and measuring performance before touching anything
9. Troubleshooting That in Git almost nothing is truly lost. reset, divergences, the reflog, fsck --lost-found, bundle, and a debugging method with GIT_TRACE and plumbing
10. The real world That Git is one piece of an ecosystem: real cases, integration with tools, LFS, scaling, DevOps. And judgement to choose

The four ideas that hold it all up

If in five years' time you only remember four things from this course, let them be these:

1. The hash of the content. Every object is identified by the hash of what it contains. Out of that come immutability, integrity verification, automatic deduplication and the fact that rewriting history always creates new objects instead of modifying existing ones.

2. The DAG of commits. Every commit points at its parents. Branches, merges, common ancestors, bisect, rebase, divergences: it is all this graph seen from different angles. When something does not add up, draw the graph.

3. The three zones. Working copy, index and repository. Knowing which of the three each thing is in explains 90% of the confusions. And remember the asymmetry we discovered in lesson 10-02: what never went through git add is the only thing that is truly unrecoverable.

4. The golden rule. Rewrite the history you have not published; do not rewrite the history others already have. And when it has to be done — an LFS migration, a secret cleanup — give notice, coordinate and use --force-with-lease.

And a fifth, which is about attitude

Almost nothing is truly lost. What gets lost is your composure. Faced with a mess, the sequence is always the same: git status to know where you are, git log --oneline --graph to see the shape of the problem, git reflog to know where you have been. And do not run anything destructive until you understand the situation.

task-manager was only the excuse

Ana Ferrer on Ubuntu, Bruno Salas on macOS, Carla Vidal on Windows 11 and Diego Rueda with his fork have spent ten modules solving problems: the ui-components submodule, the line endings that broke the diffs, the password that had been in the history for months, the reset --hard at eleven at night, the GT-NNN tickets that tie every commit to its why.

None of that matters. task-manager does not exist. What exists is the mental model you have built for yourself by solving its problems.

The day you arrive at a repository with twenty years of history, fifty thousand files, a workflow you had never seen and a commit convention that will look odd to you, you will not recognise anything. But you will know what to ask:

  • What shape does this graph have? → git log --oneline --graph --all
  • How does this team work? → git log --format=%s -40, git rev-list --count --merges HEAD
  • Who really integrates? → git log --format='%cn' -300 | sort | uniq -c | sort -rn
  • Why is this line the way it is? → git blame -w -M and the commit message
  • When did it break? → git bisect
  • Where am I and how did I get here? → git status, git reflog
  • What is deployed? → git merge-base --is-ancestor, git describe

Those questions work in any repository in the world. That is what you take away.

Common Mistakes and Tips

Mistake 1: adopting the experimental in production. SHA-256 without interoperability turns your repository into an island. reftable can break external tools. Always distinguish between "it exists" and "it is adoptable".

Mistake 2: believing the data model is going to change. It is not going to change. The hash function may change; content addressing, the DAG and the three zones will not. Do not skip learning them "in case they become obsolete".

Mistake 3: sticking with checkout out of habit. switch and restore are clearer and safer, and they separate harmless operations from destructive ones. Make the switch; and learn to read checkout because it is throughout all the existing documentation.

Mistake 4: accepting generated commit messages without reviewing them. The assistant sees the diff, not your head. The why is something only you can write, and it is the only thing that adds value six months later.

Mistake 5: running suggested commands without understanding them. Especially reset --hard, push --force, clean -fd and filter-repo. The less reversible an operation is, the more it demands understanding first.

Mistake 6: never reading the release notes. It is where the option you have been missing for two years is.

Tip 1: run git help -g right now, and read gitcore-tutorial. After this course you will find it readable, and it is what consolidates the mental model once and for all.

Tip 2: keep a lab repository. /tmp/lab, disposable. Try in it anything you are unsure about before doing it in a real repository. It costs five seconds to create and it has saved many an afternoon.

Tip 3: switch on today whatever has no drawbacks.

git config --global core.commitGraph true
git config --global fetch.writeCommitGraph true
git config --global merge.conflictStyle zdiff3
git config --global rerere.enabled true
git config --global push.default simple
git config --global pull.ff only

Tip 4: when you find a new command, always ask the same thing. Which objects does it create? Which references does it move? Is it reversible? With those three answers you understand any Git command.

Tip 5: teach what you know. It is the best filter for discovering what you thought you understood and did not.

Exercises

Exercise 1: separating the mature from the experimental

A colleague comes back from a conference and proposes these changes for task-manager. For each one, say whether you would adopt it today, and why:

A. Migrate the repository to SHA-256 "because SHA-1 is broken". B. Change the reference format to reftable "because it is faster". C. Switch on the commit-graph in all the team's repositories. D. Always clone with --filter=blob:none. E. Ban git checkout in the team and use only switch and restore. F. Switch on sparse-checkout so that each person sees only their own area.

Exercise 2: building a commit with plumbing

In an empty test repository, create without using git add or git commit:

  1. A file notes.txt with the content Learning the plumbing.
  2. A second file README.md with the content # Lab.
  3. A commit containing both, with the message Commit built by hand.
  4. A branch main pointing at that commit.

Then verify with porcelain commands that Git sees it as a normal repository, and explain what each intermediate hash represents.

Exercise 3: arriving fresh at an unknown repository

You join a project you do not know: 340,000 commits, 12 years of history, 28,000 files, 4.2 GB. Nobody has time to explain anything to you and your first task is to fix a bug in a file you do not know the name of.

Write the sequence of commands you would run on your first day, explaining which question each one answers and in what order you would do it.

Solutions

Solution 1

A. SHA-256 — NO.

The starting reasoning is correct (SHA-1 has demonstrated collisions) but the conclusion is not, today:

  • There is no interoperability: a SHA-256 repository cannot exchange objects with git.example.com, or with the platforms, or with Diego's fork. task-manager would be isolated.
  • Git already incorporates collision detection, which mitigates the known attack.
  • The real risk for an application repository is low: manufacturing a collision requires constructing both contents deliberately.

What I would do: learn about it, create a test repository with --object-format=sha256 to see the model, and wait for interoperability to exist. If the team wants to strengthen integrity today, the useful answer is signing commits and tags (08-05), which is effective and compatible with everything.

B. reftable — NO.

task-manager has a few dozen references. reftable solves problems that appear with tens of thousands: write cost, concurrency, filesystem limits. Here it would contribute nothing measurable.

And it has a cost: external tools that read .git/refs/ directly may fail, and it is not the default format.

What I would do: learn about it, and know that if one day I work in a repository with vast numbers of references, it exists. The right question is the one from lesson 10-04: have I measured that this hurts?

C. commit-graph — YES, without hesitation.

It is the easiest recommendation of them all:

  • It is a derived, regenerable cache: if it is deleted, nothing is lost.
  • It does not change the storage format or the behaviour of any command.
  • It improves log, blame and reachability queries, and on large histories enormously.
  • It has no known drawback.
git config --global core.commitGraph true
git config --global fetch.writeCommitGraph true

In task-manager, with 412 commits, the improvement will be imperceptible. But it is a good global habit and it shows as soon as you work on something large.

D. --filter=blob:none always — IT DEPENDS, with caveats.

In task-manager (2 MB): unnecessary. And it has a real cost: it introduces a network dependency into operations that are currently local. git log -p over old history with no connection would fail.

In large repositories: yes, clearly.

In CI: yes, almost always, combined with fetch-depth: 0. It is the recommended configuration of 10-04.

What I would do: not as a universal rule, but as a conditional one: "on repositories over 1 GB or in CI, partial clone". And document it.

E. switch and restore — YES, with one caveat.

Adopting them, yes:

  • git switch separates switching branch from discarding files, and that separation prevents real losses of work.
  • It requires an explicit --detach for a detached HEAD.
  • git restore --staged replaces a confusing use of reset.
  • They are consolidated.

The caveat is in "banning":

  • The existing documentation, the answers on the internet and old scripts are full of checkout. Banning it does not make it disappear from the world.
  • Everybody has to be able to read it, and to understand that git checkout -- file is destructive.
  • Existing scripts that work should not be rewritten for the sake of it.

What I would do: recommend them as the default in CONTRIBUTING.md and use them in the internal examples, without banning anything.

F. sparse-checkout — NO, emphatically.

It is the worst of the six, and for two independent reasons:

  1. task-manager has nine files. There is nothing to make sparse.
  2. The motivation is wrong. "Each person seeing only their own area" is an organisational goal, not a performance one. sparse-checkout is a performance optimisation, not an access control mechanism: anybody can widen their cone with one command, and the whole history is on their disk. Using it for that gives a false sense of separation.
  3. And it would have a real cost: files that "do not exist", searches that fail to find things, and permanent confusion.

What I would do: explain that if access really does need separating, the answer is separate repositories with permissions (10-04), not a performance optimisation misapplied.

The pattern in the six answers: the ones I would adopt (C, E, and D conditionally) have no drawbacks or solve a measured problem. The ones I would not (A, B, F) are experimental, solve a problem I do not have, or are being used for something they are not. It is exactly the maturity table of section 7.

Solution 2

mkdir /tmp/lab && cd /tmp/lab && git init

Step 1: create the blobs.

printf 'Learning the plumbing\n' | git hash-object -w --stdin
c4a8f2e91b6d3a70f5c2e8b14d9a6f0c3b7e2d51
printf '# Lab\n' | git hash-object -w --stdin
7f2b9e4c1a8d5f0b3e6c9a2d5f8b1e4c7a0d3f6b

-w writes the object into the database; without it, it would only compute the hash. Each hash is the SHA-1 of that specific content (preceded by a header with the type and the size). If two people create the same content in any repository in the world, they get the same hash: that is content addressing.

Check that they are there:

git cat-file -t c4a8f2e9    # blob
git cat-file -s c4a8f2e9    # size in bytes
git cat-file -p c4a8f2e9    # content

Step 2: build the index.

git update-index --add --cacheinfo 100644,c4a8f2e91b6d3a70f5c2e8b14d9a6f0c3b7e2d51,notes.txt
git update-index --add --cacheinfo 100644,7f2b9e4c1a8d5f0b3e6c9a2d5f8b1e4c7a0d3f6b,README.md

git ls-files --stage
100644 c4a8f2e91b6d3a70f5c2e8b14d9a6f0c3b7e2d51 0	README.md
100644 7f2b9e4c1a8d5f0b3e6c9a2d5f8b1e4c7a0d3f6b 0	notes.txt

100644 is the mode: a normal non-executable file (100755 would be executable, 040000 a directory, 120000 a symbolic link). The 0 is the index stage: 0 means "no conflict" (stages 1, 2 and 3 are the base, ours and theirs during a merge, lesson 03-05).

Notice a surprising detail: the files are not on disk. The index declares them, but the working copy is empty. It is the most forceful proof that the index is an independent zone, not a reflection of the disk.

Step 3: turn the index into a tree.

git write-tree
5e9c2f8b4a1d7e0c3f6b9a2d5e8c1f4b7a0d3e6c
git cat-file -p 5e9c2f8b
100644 blob 7f2b9e4c1a8d5f0b3e6c9a2d5f8b1e4c7a0d3f6b	README.md
100644 blob c4a8f2e91b6d3a70f5c2e8b14d9a6f0c3b7e2d51	notes.txt

A tree is a directory: it associates names with object hashes. Its own hash summarises all its content, and that is why comparing two trees by their hash is enough to know whether they are identical. It is exactly the property that makes the sparse index of lesson 10-04 possible.

Step 4: create the commit.

echo "Commit built by hand" | git commit-tree 5e9c2f8b4a1d7e0c3f6b9a2d5e8c1f4b7a0d3e6c
b3f7a0c5e2d8b1f4a7c0e3d6b9f2a5c8e1d4b7f0
git cat-file -p b3f7a0c5
tree 5e9c2f8b4a1d7e0c3f6b9a2d5e8c1f4b7a0d3e6c
author Ana Ferrer <ana.ferrer@example.com> 1785926400 +0200
committer Ana Ferrer <ana.ferrer@example.com> 1785926400 +0200

Commit built by hand

The commit points at a tree (the complete snapshot of the project) and adds metadata. It has no parent line because it is the root commit; if we had passed it -p <hash>, it would have one. That parent line is the whole DAG.

Step 5: point the branch.

git update-ref refs/heads/main b3f7a0c5e2d8b1f4a7c0e3d6b9f2a5c8e1d4b7f0
git symbolic-ref HEAD refs/heads/main

Step 6: verify with porcelain.

git log --stat
commit b3f7a0c5e2d8b1f4a7c0e3d6b9f2a5c8e1d4b7f0 (HEAD -> main)
Author: Ana Ferrer <ana.ferrer@example.com>
Date:   Sat Aug 1 12:00:00 2026 +0200

    Commit built by hand

 README.md  | 1 +
 notes.txt  | 1 +
 2 files changed, 2 insertions(+)
git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	deleted:    README.md
	deleted:    notes.txt

That deleted is correct and highly instructive: the files exist in the commit and in the index, but not on disk, because we never wrote them there. They are materialised with:

git checkout .      # or: git restore .
ls
cat notes.txt

What each hash represents:

Hash Object What it is
c4a8f2e9 blob The content of notes.txt, with no name and no permissions
7f2b9e4c blob The content of README.md
5e9c2f8b tree The root directory: names + modes + hashes of the blobs
b3f7a0c5 commit A tree + author + date + message + parents
refs/heads/main reference A file (or reftable entry) with the commit's hash

That is the whole of Git. Four object types and a reference. Everything else — branches, merges, rebase, stash, bisect, LFS, GitOps — is built on top of this.

Solution 3

The order matters: first understand the shape of the repository, then how the team works, and only at the end look for the file.

# ========== PHASE 1: CLONE WITHOUT SUFFERING (10-04) ==========
git clone --filter=blob:none https://git.example.com/team/project.git
cd project

git config core.commitGraph true
git config core.fsmonitor true
git config core.untrackedCache true
git commit-graph write --reachable --changed-paths

Why: 4.2 GB with a full clone is many minutes. The partial clone brings the complete graph — which is what I need in order to investigate — for a fraction of the download. And the commit-graph with --changed-paths is indispensable: without it, git log on a file across 340,000 commits will be unbearable.

# ========== PHASE 2: THE SHAPE OF THE REPOSITORY ==========
git rev-list --count --all           # how much history?
git ls-files | wc -l                 # how many files?
git count-objects -vH                # how big is it and why?
git log -1 --format=%ci              # is it alive?
git log --reverse --format=%ci | head -1   # since when?

# What top-level directories are there?
git ls-tree --name-only HEAD

Why: these six commands give you in one minute the map nobody has time to explain to you: scale, structure and whether the project is active.

# ========== PHASE 3: HOW THE TEAM WORKS ==========
# Linear history or with merges? (lesson 10-01)
git log --oneline --graph -40
git rev-list --count --merges HEAD
git rev-list --count HEAD

# What message convention do they use?
git log --format=%s -50

# Who writes and who integrates?
git log --format='%an' -500 | sort | uniq -c | sort -rn | head
git log --format='%cn' -500 | sort | uniq -c | sort -rn | head

# Are there documented conventions?
ls CONTRIBUTING.md README.md .editorconfig .gitattributes 2>/dev/null
cat CONTRIBUTING.md 2>/dev/null

# Which branches are alive?
git branch -r --sort=-committerdate --format='%(refname:short) %(committerdate:relative)' | head -20

# How do they version?
git tag --sort=-v:refname | head -10
git describe --tags

Why: before touching anything you have to know how things are done here. A proposal that ignores the message convention or the branching flow gets rejected, and learning it from the history is faster and more reliable than asking.

The proportion of merges and the author/committer difference reveal the working model (lesson 10-01). The CONTRIBUTING.md, if it exists, is read from top to bottom.

# ========== PHASE 4: PREPARE THE ENVIRONMENT ==========
# Is there LFS? (10-03)
cat .gitattributes 2>/dev/null | grep lfs
git lfs install && git lfs ls-files | head

# Are there submodules? (06-05)
cat .gitmodules 2>/dev/null

# How is it built and tested?
cat README.md | head -60
ls Makefile package.json Containerfile .github/workflows/ 2>/dev/null

# Useful aliases for the investigation (06-04)
git config alias.hist "log --graph --oneline --decorate"
git config alias.who "log --format='%h %an %ar %s'"
# ========== PHASE 5: FIND THE FILE WITH THE BUG ==========
# By the text of the error message the user sees
git grep -n "The amount cannot be negative"

# If the message is in translation files, look for its key
git grep -n "error.amount.negative"

# By approximate name
git ls-files | grep -i amount

# Has that area been touched recently?
git log --oneline -20 -- src/billing/

Why: git grep searches the versioned content and is far faster than the system tools because it only looks at what is in the index, ignoring build artefacts and ignored files.

# ========== PHASE 6: UNDERSTAND BEFORE CHANGING (06-03, 09-06) ==========
# Who wrote that line and why? Ignoring whitespace and code movement
git blame -w -M -C -L 120,145 src/billing/validator.js

# The commit message: the intention
git log -1 --format=%B <hash-returned-by-blame>

# The complete history of that function
git log -L :validateAmount:src/billing/validator.js

# Are there reformatting commits polluting the blame?
ls .git-blame-ignore-revs 2>/dev/null

Why: this is the step that separates a fix from a loop, and it is the lesson of the final exercise of module 9. If the behaviour is intentional and justified in writing, changing it without more thought breaks something somebody asked for, and in a fortnight the same ticket will come back in the opposite direction.

# ========== PHASE 7: IF THE BUG IS A REGRESSION (06-02) ==========
git bisect start
git bisect bad HEAD
git bisect good v3.8.0
git bisect run ./tests/reproduce.sh
git bisect reset
# ========== PHASE 8: WORK TO THE CONVENTIONS ==========
git switch -c fix-amount-validation origin/main
# ... fix it, with a regression test ...
git add -p
git commit    # with the message format I observed in phase 3
git diff origin/main...HEAD   # review myself before proposing
git push -u origin fix-amount-validation

Summary of the method: clone without suffering, understand the shape, understand the process, prepare the environment, find the place, understand the intention before changing anything, and work to the conventions you have observed.

None of those commands is specific to this project. They work in any repository in the world, and that is exactly the capability this course gives you.

Conclusion

What moves, what does not, and what to do

Already exists and you can use it today: switch and restore instead of checkout; the commit-graph and git maintenance; partial clones and the sparse index; fsmonitor and untrackedCache; and Git LFS for binaries. All of that is mature, and whatever has no drawbacks is worth switching on now.

Exists but is not yet adoptable: SHA-256, which solves the known weakness of SHA-1 but does not interoperate with the ecosystem; and reftable, which replaces refs/ and packed-refs with a binary format that is far better at scale but is not yet the default. Learn about them; do not adopt them yet.

A trend, with no dates: a better experience for monorepos, specialised Git servers the client never sees, tools built on top of Git rather than replacing it — the dominant pattern for twenty years — and increasingly helpful error messages.

AI assistants help you draft, remember and explore. They do not exempt you from understanding the change: the why is not in the diff, and destructive operations demand comprehension before suggestion. In a world where writing code costs less, understanding the history and knowing how to recover from a mistake are worth more, not less.

And what is not going to change is what you learnt in module 1: the content-addressable data model, the DAG of commits, the distributed nature and the three zones. The hash function may change; the ideas will not. That is why this course does not expire.

The end of the course

You started not knowing what a repository is.

Now you understand that Git is a database of immutable objects identified by the hash of their content, organised in a directed acyclic graph, replicated in full in every clone. You know how to build history with intent, branch and merge, collaborate with others through a server, rewrite what you have not yet published — and only that — investigate when and why something broke, work with a review and continuous integration process, write messages that will still be useful in two years' time, keep secrets out and performance under control, get out of any mess using the reflog and the plumbing, and operate Git in production with tags that deploy and artefacts identified by the commit hash.

Above all, you have a mental model with which to reason about situations this course has not covered. When you come across an unknown command, you will know to ask which objects it creates and which references it moves. When something does not add up, you will know to draw the graph. When somebody panics, you will know that almost nothing is truly lost.

task-manager was only the excuse. Ana, Bruno, Carla and Diego do not exist, and neither do their GT-NNN tickets. What does exist is what remains with you after having solved their problems: the submodule that fell out of sync, the line endings across three operating systems, the password in the history, the reset --hard at eleven at night, the LFS migration that rewrote two years of commits.

That is the luggage. It is transferable to any repository, any company and any workflow you come across, today and in ten years' time.

Now go to a real repository — your own, your team's, or that of an open project you admire — and use it. Run git log --oneline --graph --all and look at the shape it has. Read git help gitcore-tutorial. Create a lab repository in /tmp and break things on purpose to practise the rescue.

And when a colleague loses a commit and the panic shows on their face, sit down beside them, type git reflog, and explain to them why nothing has been lost.

That is where you find out that you have learnt Git.

Mastering Git: From Beginner to Advanced

Module 1: Introduction to Git

Module 2: Basic Git Operations

Module 3: Branching and Merging

Module 4: Working with Remote Repositories

Module 5: Advanced Git Operations

Module 6: Git Tools and Techniques

Module 7: Collaboration and Workflow Strategies

Module 8: Git Best Practices and Tips

Module 9: Troubleshooting and Debugging

Module 10: Git in the Real World

© Copyright 2026. All rights reserved