Level: Advanced — For Git users who want to become power users
Table of Contents
- Version check
- About this training
- Command-line note
- Introduction to the Four Zones
- The working area
- The repository
- L’index (staging area)
- The basics revisited
- Move data to the right: add and commit
- Move data to the left: switch and checkout
- Delete files: git rm
- Rename and move files: git mv
- Basic Workflow Summary
- What is git reset?
- The two steps of the reset
- Concrete example: go back with —hard
- Other reset examples
- The fourth zone: the stash
- Working with individual files
- Committing parts of a file: the hunks
- Git is a toolbox
1. Training overview
Welcome to Git Deep Dive, by Paolo Perrotta. This training is not intended for beginners. It assumes that you already use Git, that you may have followed some training on Pluralsight. But it also assumes that you are looking to move to the next level. This is the training where you go from being a regular Git user to being a power user.
To accomplish this leap, the training covers two main topics.
First topic: The four areas of Git
We’ll take an in-depth look at the four areas of Git:
- The working area (working area)
- The index (also called staging area)
- The repository
- The stash
We will see how different Git operations move data between these areas. Armed with this mental model, you will be able to work with Git smoothly and naturally. You’ll understand advanced features like stash or the git reset command — features that people sometimes use without really understanding them.
Topic Two: Features for Large Projects
The training also covers Git features typically used on large projects:
- Git LFS (Large File Storage): how to handle large binary files
- Submodules: how to break down a project into subprojects
These features are mostly needed for large projects, but they can also be useful for smaller projects. Companies often request these skills.
In summary: this training consists of becoming a Git power user. Let’s dive in.
2. The four areas of Git
2.1 Version check
Before starting, a little version check — this is a custom in Pluralsight training courses. That said, if you follow the other Git courses on Pluralsight, you probably know that there’s not much to worry about versions for these courses.
Git hasn’t changed much in recent years and is unlikely to change much in the future. What is said in this training applies to most versions of Git, past and even future.
Exception: If you are using a very old version of Git, say a version released in 2018 or earlier, some commands used in this training were not available at the time. If this is your case, consider updating. Updating is easy and Git has excellent backwards compatibility. If you can’t update, that’s no problem either — almost all of the information in this training still applies.
2.2 About this training
The style of this training might surprise you. This is advanced training, so you might expect a lot of technical details: detailed descriptions of all Git commands, both the high-level commands (the so-called porcelain commands) and the little-used low-level commands (the so-called plumbing commands), with all their many arguments.
In reality, it’s not going to happen like that. There is no point in memorizing all these details in advance. You can easily find this information when you need it, for example by using Git’s built-in help system. Instead, we focus on how Git sees the world — how Git thinks about your data and your workflow — what actually happens when you do everyday operations.
The onion metaphor
In the previous How Git Works training, we used the onion metaphor. We’ve said that Git is an onion, and we’ve described its layers — how Git stores your information.
Compared to this training, Git Deep Dive is about cooking the onion if you’re willing to stretch the metaphor a bit. We’re not just talking about how Git stores your data. We’re talking about how to move this data and do interesting things with it.
Training structure
The training is divided into two parts:
Part One — The four areas of Git:
- Introduction to the concept of the four zones (this module)
- How basic Git operations move data between zones
git reset— one of the most powerful and confusing commands- Some additional advanced tools
Part Two — Techniques for Large Projects:
- Explore history
- Submodules — an important feature often misunderstood
- Git LFS — to handle large binary files
2.3 Note on the command line
A public service announcement before we begin: In this training, the command line will be used extensively. If you follow in parallel, you should do the same.
The training is recorded on Mac, so you will see Linux-style commands like ls to list directories (instead of dir on Windows) and slashes (/) in paths rather than backslashes (\) like on Windows. If you’re on Windows, it should be easy to translate these commands — most of them barely change between Linux and Windows.
Why prefer the command line?
Many developers are a little uncomfortable with the command line. They find it intimidating or simply unpleasant. Perhaps you prefer to use a GUI tool like the Git extension in Visual Studio Code, or a standalone client like SourceTree.
This is perfectly acceptable. GUI tools are also useful for certain operations: resolving a merge conflict or comparing two versions of a file, for example.
That said, it is highly recommended to use the command line most of the time, especially when you are learning, simply because it is generally easier that way. A GUI tool will probably implement the most common operations, but will be insufficient for important edge cases. When that happens, you have to go back to the command line anyway.
The command line is your friend. It makes learning Git easier because it gives you access to 100% of Git features. And if you learn on the command line, even if you then move to a GUI tool, you’ll understand exactly what you’re doing and you’ll know that you can always go back to the command line for more advanced commands.
2.4 Introduction to the four zones
This is the basic model used in this training. A Git project stores information in four separate storage areas.
If you’ve used Git at all, you’re probably familiar with at least three of these areas:
| Area | Description |
|---|---|
| Working area | The project directory on your file system — where you keep your current files and folders |
| Repository | The main reason you use Git — contains full project history |
| Index | Intermediate area between the working area and the repository — where you place files before a commit |
| Stash | Temporary storage area, a little away from the other three |
The two fundamental questions
This is an important concept. If you really want to understand a Git command, for most commands you need to ask yourself two important questions:
-
How does this command move data between the four areas? — Does it copy data from the index to the repository? From the repository to the working area? Does it delete data from a zone?
-
What does this command do to the repository specifically? — The repository is the most important area. How does this command modify the data there? Does it create new commits? Does it move branches? Does it move the HEAD reference?
It doesn’t matter how confusing a command seems. If you can answer these two questions, you will understand it, at least in broad terms. You can worry about the intricacies later.
2.5 The working area
The working area is the project directory on your file system. This is where you work: you edit your files, you test your code, etc.
The project used in this training — a simple recipe book — has only a few files and folders. If you want to change something in this project, you probably start by editing a file, moving it, or creating a new one. All these changes are happening in the working area.
The working area is extremely important to you. However, Git doesn’t worry about this as much. For Git, the working area is a very temporary place. Fortunately, Git generally respects the working area and will not destroy data located there. But in this training, we will see some Git commands that actually destroy data in the working area. So, don’t assume your data is safe until you commit it. Once you commit your data, Git stores it in what it considers to be the really important area: the repository.
2.6 The repository
The repository is located in the .git folder. The most important data is in a directory called the Object Database.
Git objects
There are several types of objects in the database:
- Blobs: represent the contents of a file at a given point in the project history
- Trees: represent the project folders
- Commits: created each time you do a
git commit
All of these objects are immutable. They can be created and deleted, but they can never be modified.
These objects are linked together in a structure that represents the history of your project. Each commit points to a graph of blobs and trees that represents your files and folders at the time of that commit. So, each commit is like a snapshot of your working area at a certain point in time.
Two commits may share the same objects. This means that these objects have not changed between these two commits — this is how Git stores changes to your files and directories.
Branches and HEAD
In the history of a Git project, each commit points to its parent commits. If you are referring to the first commit in a chain, you are also indirectly referring to all commits that follow it.
References to commits are an important entity in Git. They are called branches. A branch is therefore a reference to a commit — and because it references a commit that is linked to other commits, the branch is essentially the entry point into a commit history.
There is a special pointer called HEAD. There can only be one. It usually points to a branch, which itself points to a commit — so HEAD indirectly points to a commit. This is the current commit.
Inaccessible commits and garbage collection
Sometimes operations result in commits that are no longer accessible from any branch. For example, if you delete a branch, commits that are only pointed to by that branch become inaccessible. Git will eventually delete them — this is called the garbage collection.
Several operations in this training create inaccessible commits.
2.7 The index (staging area)
Now let’s talk about the third storage area: the index. It’s a very special thing. Virtually every versioning system has a working area and a repository, but the index is unique to Git — or at least, Git is the only versioning system that lets you modify the index directly.
You can think of the index as something that sits between the working area and the repository. Typically, you don’t move data from the working area to the repository directly. You go through the index finger. This is why the index is also called the staging area.
You stage the changes by adding them from the working area to the index with git add, and then you commit the changes from the index to the repository with git commit.
Clean status
When git status indicates that there is nothing to commit and nothing new in the working area, we call this state clean status. In this state, the working area, index, and repository are all aligned — they contain the same data.
Index is not empty by default
Here’s an important point: the index should not be conceptualized as an empty space between two operations. Instead, think of it as another area that contains everything — just like the working area or repository contains all your files and folders. When git status says “nothing to commit”, it does not mean that the index is empty. This means that the index contains the same files and folders as the repository.
This subtle mental shift makes it easier to understand and visualize how data moves in Git.
Check diffs with git diff
# Comparer la working area avec l'index (modifications non-stagées)
git diff
# Comparer l'index avec le repository (modifications stagées, prêtes à committer)
git diff --cached
3. Review the basic workflow
3.1 The basics revisited
In the previous module, we said that to understand a Git command, we need to ask ourselves two questions:
- How does this command move information between the four zones?
- How does it impact the repository in particular?
In this module, we’ll look at the basic Git workflow commands — the ones you already know: add, commit, switch, moving and renaming files — but through the lens of these two questions. Even if you already know these commands, this may be a different way to look at them.
3.2 Moving data to the right: add and commit
You already know the basic Git workflow: edit the file, stage the file, commit the file. Let’s repeat these commands with both questions in mind.
Scenario: add Tikka Masala to the menu
I modify the menu.txt file in the working area to add “Tikka Masala”. The git status confirms that the file has been modified, but not yet staged.
# Vérifier le statut
git status
# Output : modified: menu.txt (not staged)
Step 1: git add — copy the file from the working area to the index.
git add menu.txt
The file was copied from the working area to the index, overwriting the previous version of the same file. Now git status shows that the file is modified and staged, and git diff shows no difference (the working area and index are aligned). But if we compare the index and the repository, we see the changes.
Step 2: git commit — copy the file from the index to the repository.
git commit -m "Ajout de Tikka Masala au menu"
As soon as we commit, the updated file is copied from the index to the repository. Now everything is aligned again — we are back to clean state.
Right Move Summary
[Working Area] --git add--> [Index] --git commit--> [Repository]
Important Note: The commit command does more to the repository than just copying this file. It also creates a new commit and other objects, and updates the current branch. commit moves data and also modifies the repository — this is one of the most important commands that modify the repository. In contrast, add only moves data and does not touch the repository at all.
3.3 Moving data to the left: switch and checkout
We saw two commands that move data from left to right: add (from the working area to the index) and commit (from the index to the repository). But what about moving in the other direction?
The git switch (or git checkout) command does exactly that. It moves data from the repository to the working area and the index.
switch essentially does two things:
- In the repository, it moves the HEAD reference — it changes the current commit.
- It copies the data of the current new commit from the repository to the working area and the index.
So it first changes the repository, then moves the data.
Scenario: switch to the “ideas” branch
# Voir les différences entre les branches
git diff main ideas
# Passer à la branche ideas
git switch ideas
# Revenir à la branche principale
git switch main
What happens during the switch:
- HEAD moves to the new branch (current commit changes).
- Git copies the data from the current new commit to the working area and the index.
- The three zones are now aligned — we are in the clean state.
Difference between switch and checkout
git switch <branch>: to move to a commit pointed to by a branchgit checkout <commit>: to move to any commit, even without a branch
3.4 Delete files: git rm
Let’s talk about something less intuitive: deleting files in Git.
Scenario: create then delete a COPYRIGHT file
# Créer un nouveau fichier
touch COPYRIGHT
# Stager le fichier
git add COPYRIGHT
# Status: le fichier est "new" — il est dans la working area et l'index, mais pas dans le repository
What to do if you change your mind and want to remove the file from the index (but keep it in the working area)?
One might think of using git rm — the opposite of git add — but that’s not what git rm does exactly.
git rm without option tries to delete the file from both the working area AND the index. This can be very destructive. Git therefore has built-in security:
git rm COPYRIGHT
# Erreur : Git remarque que ce fichier n'est pas dans le repository
# Il prévient que des changements non-sauvegardés seraient perdus
Git gives two options:
- Force deletion:
git rm -f COPYRIGHT(deletes from the working area AND the index) - Delete only from index:
git rm --cached COPYRIGHT(keeps file in working area)
# Pour "dé-stager" un fichier tout en le conservant dans la working area :
git rm --cached COPYRIGHT
Conclusion: in Git, git rm without arguments is not the opposite of git add. git add only modifies the index, while git rm simple modifies both the index AND the working area. You need to use the --cached option to make rm work as the opposite of add.
3.5 Rename and move files: git mv
Renaming and moving files are actually the same thing: renaming a file is like moving it to another name in the same directory.
Scenario: rename menu.txt to menu.md
# Renommer dans la working area
mv menu.txt menu.md
After this renaming in the working area, git status shows a slightly confusing situation:
- It sees
menu.mdas a new file (untracked) - It sees
menu.txtas a deleted file
How do I tell Git it’s the same file with a different name? In fact, you don’t need to tell him explicitly. Simply copy all changes from the working area to the index:
# Ajouter le nouveau fichier (menu.md)
git add menu.md
# "Ajouter" l'ancien nom (menu.txt) — cela supprime menu.txt de l'index
git add menu.txt
Git automatically understands what happened. It compares the contents of the files in the working area and the index with the contents in the repository. If two files have the same content, it concludes that it is the same renamed file.
Git is even smarter than that: in most cases, it understands that you rename or move a file even if you modify its contents at the same time — as long as the modification is not too significant.
# Committer le renommage
git commit -m "Renommage de menu.txt en menu.md"
The git mv command
Git provides a convenience command git mv that performs the renaming in a single operation:
git mv menu.txt menu.md
# Équivalent à : mv + git add <nouveau> + git add <ancien>
Personally, this command is not used often — it’s best to perform the steps manually and let Git figure out what happened.
3.6 Basic Workflow Summary
Here’s how basic commands impact the three main areas in Git:
| Order | Working Area | Index | Repository |
|---|---|---|---|
git add | Reading | ✍️ Writing | No change |
git commit | No change | Reading | ✍️ Write (new commit + branch) |
git switch / git checkout | ✍️ Writing | ✍️ Writing | ✍️ Moves HEAD |
git rm | ❌ Deletion | ❌ Deletion | No change |
git rm --cached | No change | ❌ Deletion | No change |
git mv | ✍️ Moves | ✍️ Update | No change |
4. Understanding git reset
4.1 What is git reset?
Now we’re looking at one of the most useful Git commands that also seems to be one of the hardest to understand: git reset. For a long time, this command can be a source of nervousness. It’s used for a few common use cases without really understanding what it does. And knowing that reset is a potentially destructive operation, that doesn’t help.
But like many other things in Git, once you understand how this command works, you’ll wonder how it ever seemed so difficult.
Why does reset seem so confusing?
Two reasons:
-
Prerequisites: before understanding reset, you must be familiar with the operation of branches, the working area, the index and the repository.
-
Multiple use cases: reset can be used in different ways for different reasons. If you look for ways to do things with Git, you see reset pop up over and over again with slight variations, and the results look very different.
The good news: Now that you’re familiar with the four zones, you’re ready to understand reset.
Commands that move a branch
Quiz: How many Git commands do you know that move a branch? At least four:
commit: creates a new commit and moves the current branch to point to this new commitmerge: creates a new commit in most cases and moves the current branchrebase: creates new commits by copying existing commits and moves the current branchpull: retrieves new commits from a remote and updates local and remote branches
However, none of these commands are specialized commands that only move a branch. They move branches implicitly, as a side effect of creating or pulling new commits.
git reset is this specialized command: its main purpose, its first step, is to move a branch.
4.2 The two stages of the reset
Step 1: Move current branch
The most important thing that reset does, its first step, is simply move a branch — usually the current branch, the branch to which HEAD points.
You choose a commit, and reset moves the current branch to that commit. Note that reset does not move HEAD. HEAD still points to the same branch, but the branch itself moves, so HEAD follows.
Step 2: Copy data (optional, depending on options)
Depending on the options provided, reset can also copy data from the current new commit to the working area and/or the index:
| Options | Effect on the working area | Effect on the index |
|---|---|---|
--hard | ✍️ Updated | ✍️ Updated |
--mixed (default) | No change | ✍️ Updated |
--soft | No change | No change |
git reset --hard <commit> # Copie vers working area ET index
git reset --mixed <commit> # Copie vers l'index seulement (défaut)
git reset --soft <commit> # Ne copie rien, déplace seulement la branche
git reset <commit> # Équivalent à --mixed
4.3 Concrete example: go back with —hard
Context
We are on the main branch in clean state. We made two commits that we now regret:
- Added “Strawberry Squids” to the menu
- Adding the corresponding recipe to the recipes folder
These commits are only on our machine (not pushed yet), so we can safely modify the history.
# Voir l'historique
git log --oneline
# abc1234 (HEAD -> main) Ajout recette squids
# def5678 Ajout Strawberry Squids au menu
# ghi9012 ...commit précédent...
# Revenir au commit avant le désastre
git reset --hard ghi9012
What is happening:
- Git moves the
mainbranch to commitghi9012(the new current commit) - HEAD follows
- Because it is a
--hardreset, Git copies the contents of the current new commit to the working area AND the index - Both commits related to strawberry squids become inaccessible and will eventually be collected by the garbage collector
We are back to the state before the failed culinary attempt, all with a simple reset.
⚠️ Warning: modifying the history is an operation that changes the history of your project. Never apply this to history that has already been shared — for example, history that you have pushed to a central repository shared with your team.
4.4 Other reset examples
Example 1: Unstaging all changes (mixed HEAD reset)
You staged changes but changed your mind. You want to keep the changes in the working area but clear the index.
# Déstaguer tout (mixed HEAD reset)
git reset HEAD
# ou simplement :
git reset
What happens: the HEAD reset does not move the branch (it already points to HEAD), but it copies the data from the current commit to the index (mixed = no change in the working area). Result: all changes are destaged.
Example 2: Clear all changes (HEAD reset hard)
You want to delete all your changes in the working area and return to the state of the last commit.
# Attention : opération destructive !
git reset --hard HEAD
What is happening: the reset does not move the branch, but it copies the data from the repository to the index AND the working area. Everything in the working area and the index is overwritten by the contents of HEAD.
⚠️ Warning: This is one of the easiest ways to lose data in Git. You explicitly say “I don’t care about anything in my working area, overwrite it with the contents of HEAD”.
What to remember about reset
Regardless of the use case, the two steps are always the same:
- Move a branch (possibly to the same position)
- Copy data from the current commit to the index, the working area, both, or neither — depending on the reset type
5. Advanced workflow: stash, individual files and hunks
5.1 The fourth zone: the stash
We come to the last module on the four zones — and also to the last module of the first part of the training. It’s a bit of a catch-all. We will see tools that did not find their place in previous modules. But first, let’s finally talk about the fourth zone: the stash.
At the beginning of the training we said there are four zones, but we focused on three. The stash was ignored because the commits seen so far have an effect on the working area, the index and the repository — but none of them have an effect on the stash.
In fact, there is only one command that affects stash: git stash. If you want something to happen in the stash, you have to explicitly request it. And that’s precisely the strong point of stash: it’s all yours. The stash doesn’t change unless you explicitly ask it to change.
What is stash used for?
The stash is like a clipboard for your project. This is where you store items that you need to put aside for a while.
Scenario: set aside a current recipe
You are working on a new guacamole recipe. You updated menu.txt in the working area and created a new empty file guacamole in the working area and index. But you are interrupted and need to work on another branch.
# Mettre en réserve toutes les modifications
git stash
# ou avec les fichiers non-trackés aussi :
git stash --include-untracked
What’s happening: Git takes all the data from the working area and the index that is not in the current commit of the repository, copies it into the stash, then does a checkout of the current commit. You are back in clean condition. Your changes have disappeared from the working area and index — but they are safe in stash.
# Voir le contenu du stash
git stash list
# Output : stash@{0}: WIP on main: abc1234 Dernier commit
The stash is a multiple clipboard. You can have as many items as you want. Each element is labeled with information about the last commit for easy identification, and it is also given a serial ID (stash@{0}, stash@{1}, etc.).
Important: Stash elements are strictly local. They’re on your computer, they stay on your computer. They are not shared by push or pull.
Recover data from stash
# Récupérer le dernier élément du stash (stash@{0})
git stash apply
# Récupérer un élément spécifique
git stash apply stash@{1}
After recovering the stash, you can resume your work, complete the recipe, stage and commit.
# Vider tout le stash une fois le travail terminé
git stash clear
This is the essence of the stash workflow: a clipboard for your project.
5.2 Working with individual files
In this training, we spent a lot of time talking about commits. Typically, we only work with files until we put them in a commit. Then we work with commits all the time. But commits are pretty crude — a commit is a snapshot of your entire project.
Sometimes you want to work with something smaller than your entire project, like a single file.
Scenario: untag only the menu.txt file
You have edited menu.txt and README.txt, and you have staged all these changes. But you change your mind about the menu. You want to untag only menu.txt, not README.txt.
# Déstaguer seulement le fichier menu.txt (mixed HEAD reset sur un seul fichier)
git reset HEAD menu.txt
What’s happening: it’s a mixed HEAD reset, so it doesn’t move the current branch. It copies data from the current commit to the index — but only for the specified file. Result: only menu.txt is destaged. README.txt remains staged.
Remove working area changes for a single file
# Méthode 1 : avec git restore (commande moderne)
git restore menu.txt
# Méthode 2 : avec git checkout (méthode ancienne)
git checkout -- menu.txt
⚠️ Warning: These commands are among the most destructive you can do in Git. They destroy changes in the working area without warning and without possibility of recovery (if the changes were never committed).
Note on Git inconsistency: you would think that a git reset --hard HEAD menu.txt would also work (logical equivalent), but Git refuses to do a hard HEAD reset with a specific path — which is indeed a bit inconsistent.
# Finalement, committer README.txt
git commit -m "Modifications du README en majuscules"
5.3 Committing parts of a file: hunks
Git takes the granularity even further. It can operate on something even smaller than a file. This concept seems a bit strange, because so far we have treated the file and directory as the fundamental units of the Git model. This is partly true — it’s a good model for describing Git. But you can split this atom if you want.
The hunks
Git can divide changes in a file into sections called hunks. For each hunk, you can decide individually whether you want to stage him or not.
git add --patch menu.txt
# ou la forme abrégée :
git add -p menu.txt
Git divides your changes into hunks and gives you options for each hunk:
| Options | Action |
|---|---|
y | Stage this hunk |
n | Don’t stage this hunk (move on to the next one) |
s | Divide this hunk into smaller hunks |
q | Quit without staging other hunks |
? | View options documentation |
Scenario: Selective changes in menu.txt
You have made three changes to menu.txt:
- Added “Spaghetti Bolognese” to the top
- Change “Apple Pie” to “Apple Piece”
- Added “Aloo Gobi” at the bottom
You want to commit only the third change (Aloo Gobi).
git add -p menu.txt
# Git montre un seul hunk car les changements sont proches
# Pressez 's' pour diviser
# Hunk 1 (Spaghetti) : pressez 'n' pour skipper
# Hunk 2 (Apple Piece) : pressez 'n' pour skipper
# Hunk 3 (Aloo Gobi) : pressez 'y' pour stager
Result: After this, git status shows something strange — menu.txt appears in both the list of changes to be committed AND the list of unstaged changes, because part of it is staged and part of it is not.
# Voir les changements non-stagés
git diff
# Voir les changements stagés (qui iront dans le prochain commit)
git diff --cached
The —patch option on other commands
The --patch option is not exclusive to git add. Other commands have it too:
git restore --patch: restore only some hunksgit stash --patch: stash only certain hunksgit reset --patch: reset only some hunks
In all these cases, --patch means: do this operation not file by file, but hunk by hunk.
5.4 Git is a toolkit
Consider a toolbox — a real toolbox with screwdrivers, hammers, etc. The tools are specialized in a sense, each tool has specific functionality, but at the same time you can do a lot of different jobs with each tool.
You can use a hammer to hammer in a nail, or to remove a nail, or to straighten a piece of metal. And you can also use different tools to do similar jobs if you are creative. If you need to remove a nail, you can do it with a hammer or with pliers.
Git is a toolkit. It is not a single program that does things. It’s not even a collection of utilities that cleanly match your use cases one by one. It’s a toolbox because you have generic tools like reset or checkout.
The same tool for different jobs
Take git reset, for example. You can use it to:
- Remove the latest commits from your history
- Unstag a file
- Clean your working directory
Or git add:
- Tell Git there is a new file
- Put a modified file in the next commit
- Report that you resolved a conflict in a file during a merge
Different tools for the same job
To unstage a file, Git doesn’t give you a command called, I don’t know, unstage. There is no such order. To untag a file, you need to understand how the index works. And once you do, you can untag the file with reset, or you can use rm --cached, or you can even use the restore command.
The important point: don’t try to learn all these options for all these commands. Understand the toolbox approach. Git doesn’t tell you “this is the command you should use if you want to untag a file”. Git gives you a basic set of tools, and then it’s up to you to choose the right tool.
The UNIX philosophy
This approach is very UNIX, which is not surprising because Git was designed by Linus Torvalds, who also created Linux. This approach makes Git a little more difficult to approach than other versioning systems — but it also makes it extremely powerful and flexible.
On the other hand, this philosophy also means that it’s probably not worth learning everything there is in Git. This is probably excessive for most users. Learn the general approach, understand the model (this is essential), and look for the details when you need them.
6. Exploring the past
6.1 Become a History Surgeon
We are entering the second part of this training. It is shorter than the first — just three modules — and it covers techniques and approaches that are particularly useful for large projects, but not only for them.
For example, this module — Exploring the Past — is a brief discussion of some commands you can use to dig into the history of a project and make sense of it. Obviously if you have a large project with a long, complicated history, these techniques can be very useful. But even on a normal-sized project, you might want to explore the history.
The surgeon metaphor
There is training on vim, the text editor, where the teacher says: “Using vim is like being a text surgeon.” It’s a beautiful metaphor. Some technologies are like that: very precise and exact, and when you use them, it’s a bit like doing surgery. You’re doing exactly what you want to do on the data you want, and you’re using tools that are a little scary, maybe. They cut easily, but they are also very powerful.
Git also looks like this, especially when dealing with history — reading it or rewriting it. Rewriting history is not part of this training (there is another training course for that: “Rewriting Git History”). But the reading of the history, we can talk about it.
6.2 Reference commits
When we talk about exploring the history of a project, we are mainly talking about commits. There are many ways to reference a commit in Git.
Use git log with useful options
The default git log is not very useful when trying to make sense of complex history, because it compresses everything into a single list.
# Un git log plus utile
git log --graph --decorate --oneline
--graph: gives a graphical structure to see how commits branch and merge--decorate: show positional references like branches and HEAD (enabled by default in recent versions of Git)--oneline: each commit fits on a single line
This command alone can almost replace a GUI tool for many needs.
Ways to reference a commit
# Par son hash complet
git show abc1234567890abcdef
# Par les premiers caractères du hash
git show abc1234
# Par le nom d'une branche
git show main
git show nogood
# Par HEAD (commit courant)
git show HEAD
# Par le parent du commit courant
git show HEAD^
# Par le parent du parent
git show HEAD^^
# Ou équivalent :
git show HEAD~2
# Par le parent n'ième ancêtre
git show HEAD~10 # 10 commits avant HEAD
# Pour les commits de fusion (avec plusieurs parents)
git show HEAD~2^2 # Le deuxième parent du commit situé 2 commits avant HEAD
# Où était HEAD il y a un mois ?
git show HEAD@{1 month ago}
Caret and tilde syntax
- The caret
^means “the parent commit”.HEAD^= the parent of HEAD,HEAD^^= the parent of the parent of HEAD. - The tilde
~nmeans “go back n commits”.HEAD~2= the commit located 2 commits before HEAD.
The tilde syntax is particularly useful if you want to look at, say, the 10th commit before HEAD — you don’t need to type 10 carets.
When a commit has multiple parents (like a merge commit), you can use ^2 to denote the second parent.
6.3 Track changes in history
There are a few useful commands for drilling down into commits.
git blame
git blame shows where lines in a file came from — who modified each line and when.
git blame recipes/apple_pie.txt
For each line, you can see the last commit where that line was modified. The caret ^ in this case means that this line has been in the file since the very first time the file was added to the project.
git diff to compare commits
git diff can also compare things other than areas (working area, index, repository) — for example, two commits.
# Voir les différences entre le commit courant et 2 commits plus tôt
git diff HEAD~2
# Comparer deux branches
git diff main ideas
# (Très utile avant de fusionner !)
Comparing branches is really useful, especially before merging.
6.4 Browse log
git log is the most useful command for exploring the history of a project. It has already been used with a few options (--graph, --decorate, --oneline), but there are many more. Git log seems harmless, but it’s probably the most complicated Git command. She is extremely powerful.
Here are some examples of git log capabilities:
# Voir les changements détaillés pour chaque commit dans le log
git log --patch
# Filtrer les commits dont le message contient "apples"
git log --grep="apples"
# Trouver tous les commits qui ont ajouté ou supprimé le mot "apples" dans un fichier
git log -G "apples" --patch
# Afficher seulement les 5 derniers commits
git log -n 5
git log -5 # même chose
# Afficher une plage de commits (du 5ème avant HEAD jusqu'au parent du commit courant)
git log HEAD~5..HEAD^
# Commits dans main qui ne sont pas dans nogood
git log nogood..main
The colon syntax is particularly useful for comparing two branches. For example, git log nogood..main means: show me commits that are in the main branch but not in the nogood branch. If we merged main into nogood right now, these would be the commits we would get.
Note: git log has a huge amount of options for viewing your history in different ways, filtering commits, formatting them, etc. The goal is not to memorize them all — but to know that they exist. When you need it, a quick search is all it takes.
Crawl Command Summary
| Order | Usage |
|---|---|
git log --graph --decorate --oneline | Graphical view of history |
git show <commit> | Details of a specific commit |
git blame <file> | Who modified every line of a file |
git diff <commit1> <commit2> | Differences between two commits |
git diff <branch1> <branch2> | Differences between two branches |
git log --grep="word" | Filter by commit message |
git log -G "text" --patch | Filter by edited content |
git log <branch1>..<branch2> | Commits in branch2 but not in branch1 |
7. Use submodules
7.1 Why do submodules exist?
Let’s talk about a useful and confusing feature of Git: submodules. Sub-modules are not an ideal topic for video training because they involve a lot of technical details. So here’s a high-level overview: why they exist, how they work, and how to approach them to avoid surprises.
The problem of dependencies between projects
Consider this common scenario: You have a project that depends on another project. For example, you develop an application that uses a library. There is therefore a hierarchy: an external project (the application) which has a dependency on an internal project (the library).
This application/library example is just the most obvious. Project hierarchies with dependencies are the rule rather than the exception. In enterprise environments, it is common to have large projects divided into subprojects that use each other.
Different ways to manage dependencies
Solution 1: Copy and paste the source code
The most basic solution — and almost never a good idea — is to copy and paste the library source code into the application project. When the library is updated, there is no easy way to integrate the update into the application project.
Solution 2: Use a package manager
The most common way to manage dependencies. Use pip for Python, npm for JavaScript, NuGet for .NET, etc. You have a file in the application that declares the dependency, and the package manager installs the library.
It is very easy to update to the latest version of the library. On the other hand, it’s not as easy to contribute back to the library — if you find a bug in the library and fix it, there’s no easy way to submit the patch.
Solution 3: Reference another project directory
Have two separate project directories on your disk, one for the application and one for the library, and reference the library directory from the application. Some programming ecosystems allow this.
This is good, but it makes it difficult to keep the two projects in sync. Think about it: each specific version of the application works with a specific version of the library. When you check out an old commit in the application project, which version of the library should you use? It can get complicated.
The Git solution: submodules
Because none of these solutions are perfect, Git gives you even more ways to approach dependencies between projects. One of them is Git project nesting. One project’s directory is literally inside another project’s directory.
You put the library project directly inside your application project. The library still operates as a standalone project with its own history and branches. But from the application perspective, the library is a subdirectory managed by Git.
So each specific version of the application — each specific commit — contains a specific commit of the library, and everything stays in sync.
However, you can’t just put the directories of two Git projects inside each other. If you did that, you would have two .git directories in the same directory tree, and Git doesn’t like that. Instead, you should use one of the tools that Git gives you for this. The most commonly used tool is submodules.
7.2 Demo: using submodules
Let’s imagine two developers working on two separate projects:
- Apollo: working on the
appapplication - Liberty: works on the
liblibrary
Liberty only cares about the library, but Apollo cares about both projects.
Initialization: add lib as app submodule
Apollo adds the lib project to its app project as a submodule:
# Dans le répertoire app
git submodule add <URL-du-dépôt-lib> lib
This is essentially a clone operation, but for a submodule. This submodule behaves like a regular Git project with all lib project history.
# Revenir à la racine du projet app
cd ..
# Vérifier le statut
git status
# Output : deux fichiers stagés :
# - le répertoire lib (le sous-module)
# - un nouveau fichier .gitmodules
The .gitmodules file tells Git that the lib directory is a submodule. Apollo is committee on these changes.
Update submodule
Liberty developed a new feature for the library and pushed it. Apollo wants this update.
# On pourrait croire que pull suffit, mais...
git pull
# Rien ne se passe dans le sous-module !
# Il faut aller dans le sous-module et puller
cd lib
git pull
cd ..
# Maintenant app reconnaît que le sous-module a changé
git status
# Modified: lib
# Stager et committer
git add lib
git commit -m "Mise à jour du sous-module lib"
There is a shortcut to update all submodules at once:
# Mettre à jour tous les sous-modules (récursivement)
git submodule update --remote --recursive
This command is particularly useful if you have dozens of libraries.
Surprises with submodules
Surprise 1: Detached HEAD mode
If Apollo goes into the lib submodule and looks at the log and status, it sees that it is on the latest commit of the library, but not on a branch. He is in detached HEAD mode, even though he never asked to be.
Surprise 2: No automatic synchronization
If Apollo rolls back time to an older version of the application, it might expect Git to automatically move the submodule to the corresponding version. But no — the library is always at the latest version.
# Il faut explicitement mettre à jour le sous-module
git submodule update
# ou récursivement pour les sous-modules de sous-modules :
git submodule update --recursive
Surprise 3: Clone a project with submodules
If someone clones the project for the first time, the submodule is completely empty.
# Méthode 1 : initialiser et mettre à jour séparément
git submodule init
git submodule update
# Méthode 2 : cloner avec les sous-modules en une seule commande
git clone --recurse-submodules <URL>
7.3 Simplify work with submodules
If we were to write down what we learned from the demo about submodules, it would be mainly three things — three things to remember when working with Git submodules.
Rule 1: A submodule is a pointer to a commit
You can look at a submodule in different ways: as a nested project, as a subdirectory of your project, etc. But the best way to see it is as a pointer to a specific commit in another Git project.
When you updated the lib submodule inside the app project, what you got was essentially this: a specific commit in lib, not a project, but a specific commit.
Think of the entire submodule as a pointer, and you won’t be surprised when, for example, you find yourself in detached HEAD mode in a submodule.
Rule 2: Nothing happens automatically in a submodule
- You are checking out an old commit in the external project: do not assume that Git will checkout the corresponding commit in the submodule.
- You are cloning the external project: don’t expect Git to clone or even initialize the submodules.
Submodules are static. They were designed to prevent anything magical from happening behind the scenes. Git wants you to have explicit control. Submodules don’t change unless you explicitly ask them to.
Rule 3: There are high-level commands to simplify
Having to explicitly ask for things in submodules might seem like bad news. But the good news is that Git has high-level commands that allow you to be explicit in a very concise way.
# Cloner avec tous les sous-modules en une fois
git clone --recurse-submodules <URL>
# Mettre à jour tous les sous-modules (y compris les sous-modules de sous-modules)
git submodule update --remote --recursive
The specific commands may vary depending on the version of Git you are using, but in general you can find a Git command to simplify all important submodule operations.
Alternatives to submodules
Remember that submodules are just another tool in the Git toolbox and are not required. There are alternatives:
- Package manager: for libraries distributed as packages
- Reference another project on the same disk from your code
- Git subtrees: less powerful than submodules, but simpler — it’s essentially a copy of another project in your own repository (instead of a pointer)
Submodules are probably the most powerful of these options, but you might get away with a simpler option.
8. Working with Git LFS (Large File Storage)
8.1 The problem with large binary files
This last module covers Git Large File Storage (LFS). LFS is not a part of Git — it’s a separate project — but it deserves a place in this course because it’s a frequently asked topic.
The problem in brief
Git is good for handling text files like source code, but not as good for binary files, especially if they are large.
Concrete example: You work for an imaginary company called Globomantics. It manufactures a system that analyzes images with artificial intelligence. The project’s GitHub repository contains many C and Python files, but also PNG image files for functional tests or for training the AI.
These image files tend to be larger than source code files—megabytes rather than kilobytes—and don’t compress as well, so they take up a lot of space. And the system is constantly evolving, so the images are changing all the time.
How the problem gets worse
Git never forgets. If you commit a file and update it multiple times, and even if you eventually delete it, previous versions of the file will remain in the repository forever. So all these versions of all these big image files are piling up. The project repository grows and grows.
A very large repository would not be a problem if it remained on the server. But Git is distributed. Each person in the project clones the entire repository. A 10 GB repository isn’t 10 GB on a server somewhere — it’s 10 GB on every developer’s laptop.
Practical consequences:
- Cloning a multi-gigabyte repository can take hours for a new developer
- A simple
git pullcan take forever: if a 50 MB file has been updated 4 times since your last pull, you get all 4 versions (200 MB), even if you are only interested in the most recent version - All this breaks your workflow and takes up space on your disk
The key question: do you really need all these old versions? Maybe yes. Maybe you frequently go back in history and check out those old commits with those old files. But maybe not — maybe you rarely check out past commits and are only interested in the latest version of each file.
This is the problem that LFS is designed to solve.
8.2 Demo: using LFS
How LFS works from the user’s perspective
LFS works with Git hosting services like GitHub, GitLab, or Bitbucket — some of these services offer LFS support natively.
# Installer LFS (via votre gestionnaire de paquets)
# Sur macOS : brew install git-lfs
# Sur Ubuntu : apt install git-lfs
# Voir quels types de fichiers sont trackés par LFS
git lfs track
# Tracker les fichiers PNG
git lfs track "*.png"
# Tracker les fichiers MPG
git lfs track "*.mpg"
# Tracker tous les fichiers dans un répertoire spécifique
git lfs track "assets/**"
The magic of LFS: When you clone an LFS project, the old commits are there on your computer, but the large files themselves are not — except for the files you need in your working area (the current version). Older versions are on an LFS server. But you don’t really notice that these files are missing, because as soon as you checkout one of these old commits, LFS will download them for you.
# Vérifier l'historique
git log --oneline
# Faire un checkout d'un ancien commit (LFS télécharge les fichiers si nécessaire)
git checkout <ancien-commit>
# LFS télécharge silencieusement l'ancienne version du fichier PNG
What LFS basically does: download certain files on demand — only when you need them. If you never check out these old commits, you never pay the download cost.
Note: You need a network connection when checking out these old commits. Otherwise you will get an error.
8.3 Inside LFS
As with Git, LFS is easier to use if you know how it works internally.
How LFS knows which tracker files
The answer is in the hidden .gitattributes file. It is a Git standard for storing information about paths, groups of files.
# Contenu de .gitattributes après avoir tracké PNG et MPG
cat .gitattributes
# *.png filter=lfs diff=lfs merge=lfs -text
# *.mpg filter=lfs diff=lfs merge=lfs -text
When you ask LFS to track PNG files, LFS creates this file (if it does not exist) and adds the information. You then add this file to the project and commit it, just like any other file in the project.
How LFS intervenes: Git hooks
LFS is inserted into the Git workflow via the Git hooks system. Hooks are small programs stored in the .git/hooks directory. Git invokes these programs before or after certain operations depending on the naming convention.
For example, right after a commit, Git calls post-commit if it exists. When you start using LFS (for example, when you ask it to track PNG files), LFS writes some of these hooks so that they call LFS at the right time.
.git/hooks/
├── pre-push ← Écrit par LFS
├── post-checkout ← Écrit par LFS
├── post-commit ← Écrit par LFS
└── ...
From there, when you make a commit, Git calls post-commit which calls LFS which does its thing.
What exactly LFS does: pointer blob
Here is the most important part. LFS replaces large files in Git with pointers — small textual data snippets that tell LFS where the file is located.
When we look at a Git blob in an LFS project instead of seeing binary data (the bytes of the image), we see something like:
version https://git-lfs.github.com/spec/v1
oid sha256:abc123...
size 1234567
This is the LFS pointer. The file itself is stored elsewhere — on a remote LFS server (like Bitbucket LFS or GitHub LFS).
The LFS cache: a fifth zone
LFS doesn’t want to go to the internet every time you check out a commit. So the first time it fetches a file, it stores it in its own local cache.
If you have completed all four areas in this training, think of LFS cache as a fifth area that only exists if you are using LFS. The cache is located here, still in the .git directory, but outside the objects directory (outside the Git repository).
.git/
├── objects/ ← Repository Git (base de données d'objets)
├── lfs/ ← Cache LFS
│ └── objects/
│ └── <hash>/
│ └── <fichier>
└── ...
LFS organizes its files in a similar way to Git — based on hashes. An important detail: LFS does not compress files, because binary files may not compress well.
Summary of how LFS works
[Working Area] <---> [Index] <---> [Repository Git]
|
(pointeur LFS)
|
[Cache LFS local] <---> [Serveur LFS distant]
- LFS replaces files in Git with pointers
- The files themselves are stored on the remote LFS server
- When you need a file, LFS downloads it to the local cache
- Future downloads of the same file use local cache
The right way to think about LFS: a cache that adds lazy downloading to Git.
8.4 Do you need LFS?
Should you use LFS or not? It depends on your specific circumstances. Here are some guidelines.
Two situations that suggest using LFS
Situation 1: Large files rarely used
If you have files in your repository that are bulky (large binaries) and you or your teammates generally don’t need these files in your working areas — you rarely view them — then LFS can help by replacing the files with pointers and downloading them lazily.
Important point not to be confused: if you have never updated or deleted these large files, they will still be in the current commit, still in your working area. In this case, LFS does nothing for you — you have a cache, but you still need whatever might be in the cache.
LFS is useful when these files rarely appear in your work area — for example, images used to train an AI model that change often but which individual developers generally do not need to see in their history.
Situation 2: Exceeding the limits of online services
If your project is hosted on an online service (GitHub, GitLab, Bitbucket) and your project exceeds the limits of that service (repository size, individual file size, etc.), you can use LFS to store large files in separate LFS storage.
Using LFS with these services can be easy because they usually offer their own LFS storage with more space than regular Git storage.
Irony: LFS was designed for the first case (local cache), but most people today use it for the second reason — to satisfy GitHub’s limitations.
Alternatives to LFS
LFS is an additional tool in your project. Before you jump in, consider its alternatives.
Shallow clone: If you have a large history and don’t want to clone the entire history to every developer machine, you can use a shallow clone.
# Cloner avec seulement les 12 derniers commits
git clone --depth 12 <URL>
# Vous pouvez approfondir plus tard si besoin
git fetch --unshallow
Partial clone: Git can download the history of a project without downloading the data in this history. The data itself is lazily loaded — this is similar to what LFS does.
# Clone partiel (les blobs sont téléchargés paresseusement)
git clone --filter=blob:none <URL>
Prefetching: Git has a feature that fetches new data from the remote periodically in the background. When you do a pull, you don’t have to wait to download that data — it’s already downloaded.
Git has become much more efficient at handling large projects since Microsoft started using it to develop Windows (a project with millions of files and thousands of pushes per day). Microsoft contributed many of these optimizations to Git itself.
Guidelines
-
LFS won’t fix past issues: If you have a large repository and you adopt LFS, it can help you control the future growth of that repository, but it won’t do anything for past commits unless you rewrite the history (which some projects may not want to do).
-
Think carefully: LFS does what it says, and it’s pretty transparent. Once set up, you barely notice it. But it’s still an extra piece in your project, another thing you need to take care of.
-
Try “vanilla” Git first: Before jumping to LFS, make sure your problem cannot be solved with plain Git. Git is much more efficient today at handling large projects than it was when LFS was designed. Don’t assume you need LFS.
8.5 Conclusion of the training
We did it! We have reached the end of Git Deep Dive.
Part 1 Recap: The Four Zones
We learned that for Git, the world is divided into four zones:
- The working area: where you interact with your files
- The index: which decouples the working area from the repository
- The repository: where the data is secure and versioned
- The stash: like a clipboard that you can use to store data temporarily
Most Git commands can be understood in terms of two questions:
- How do they move data between zones?
- What do they do to the repository in particular?
Answer these questions, and you will understand the command.
We looked at the basic commands from this angle:
add,commit,switch: everyday commandsreset: one of the most powerful commands
Part 2 Recap: Techniques for Large Projects
- Explore history: with commands like
git log,git diff,git blame - Submodules: nested Git projects
- Git LFS: a popular extension for handling large binary files
The final lesson
You’ll probably forget a lot of these details in the coming weeks — everyone does. But that’s not the point. The goal is that you now have a mental model of how Git sees the world and the basic set of tools that Git gives you to manipulate that world.
You are now thinking in terms of Git. You know how to cook this onion, as we said at the beginning. And that’s enough to continue on your own or move on to the next Git training.
9. Example project: The recipe book
The example project used in this training is a simple recipe book.
Project structure
cookbook/
├── menu.txt ← La liste des plats
└── recipes/
├── README.txt
├── apple_pie.txt
└── spaghetti_alla_carbonara.txt
menu.txt
Spaghetti alla Carbonara
Apple Pie
Cheesecake
recipes/apple_pie.txt
Apple Pie
pre-made pastry
1/3 cup butter
3 tablespoons flour
3/4 cup sugar
1 tablespoon cinnamon
1 tablespoon lemon juice
9 Granny Smith apples
recipes/spaghetti_alla_carbonara.txt
Spaghetti alla Carbonara
1 pound spaghetti
2 tablespoons oil
4 ounces diced bacon
1 onion
3 eggs
1 cup parmesan cheese
1 handful parsley
salt and pepper
10. Command Quick Reference
The four zones
# Voir le statut des trois zones principales
git status
# Comparer working area et index (modifications non-stagées)
git diff
# Comparer index et repository (modifications stagées)
git diff --cached
Basic commands (Module 3)
# Stager des fichiers
git add <fichier>
git add . # Stager tout
# Committer
git commit -m "message"
# Changer de branche
git switch <branche>
# Supprimer un fichier de l'index et de la working area
git rm <fichier>
# Supprimer un fichier seulement de l'index (déstaguer)
git rm --cached <fichier>
# Renommer un fichier
git mv ancien.txt nouveau.txt
git reset (Module 4)
# Hard reset : déplace la branche ET met à jour working area + index
git reset --hard <commit>
# Mixed reset (défaut) : déplace la branche ET met à jour l'index
git reset --mixed <commit>
git reset <commit>
# Soft reset : déplace seulement la branche
git reset --soft <commit>
# HEAD reset : ne déplace pas la branche (mixed par défaut)
git reset HEAD # Déstaguer tout
git reset HEAD <fichier> # Déstaguer un fichier spécifique
# Hard HEAD reset : écraser working area et index avec le commit courant
git reset --hard HEAD
The stash (Module 5)
# Mettre en réserve les modifications
git stash
git stash --include-untracked
# Voir le contenu du stash
git stash list
# Récupérer le dernier élément du stash
git stash apply
# Récupérer un élément spécifique
git stash apply stash@{1}
# Vider tout le stash
git stash clear
Operations on individual files and hunks (Module 5)
# Stager interactivement (par hunks)
git add --patch
git add -p
# Restaurer un fichier dans la working area depuis le commit courant
git restore <fichier>
git checkout -- <fichier> # Ancienne méthode
# Déstaguer un fichier spécifique
git reset HEAD <fichier>
Explore History (Module 6)
# Log graphique
git log --graph --decorate --oneline
# Voir un commit spécifique
git show <commit>
git show HEAD
git show HEAD~2
git show HEAD^
# Blame : qui a modifié chaque ligne
git blame <fichier>
# Diff entre commits
git diff HEAD~2
git diff main ideas
# Log avec filtres
git log --grep="mot"
git log -G "texte" --patch
git log -n 5
git log HEAD~5..HEAD^
git log nogood..main
Submodules (Module 7)
# Ajouter un sous-module
git submodule add <URL> <répertoire>
# Cloner avec les sous-modules
git clone --recurse-submodules <URL>
# Initialiser et mettre à jour les sous-modules
git submodule init
git submodule update
# Mettre à jour tous les sous-modules
git submodule update --remote --recursive
Git LFS (Module 8)
# Tracker un type de fichier
git lfs track "*.png"
git lfs track "*.mp4"
# Voir les fichiers trackés
git lfs track
# Clone peu profond
git clone --depth 12 <URL>
# Clone partiel (données chargées à la demande)
git clone --filter=blob:none <URL>
“You now think in terms of Git. You know how to cook this onion.”
Search Terms
git · deep · dive · ci/cd · devops · lfs · reset · submodules · scenario · changes · command · four · stash · commands · hunks · menu.txt · submodule · area · commit · commits · data · head · history · individual