Git Introduction
Git is a free, open-source distributed version control system that tracks changes to files over time, so you can see what changed, who changed it, and why — and can always get back to any earlier state. It was created by Linus Torvalds in 2005 to manage the Linux kernel’s source code, and it has since become the version control system used by the vast majority of professional software teams. If you plan to write code, collaborate with other people, or contribute to open source, Git is not optional knowledge — it’s foundational.
This lesson introduces what Git actually is, how it stores your project’s history internally, and walks through your very first repository, commit, and history inspection.
What Is Version Control, and Why Does It Matter?
A version control system (VCS) records snapshots of a project over time. Without one, “version control” usually means folders named project_final, project_final_v2, and project_final_ACTUALLY_FINAL — a system that breaks down the moment more than one person touches the code, or you need to know exactly what changed and why.
Git solves this by letting you:
- Save named checkpoints of your project (commits) that you can always return to.
- See a full history of who changed what, and when, with a message explaining why.
- Work on experimental changes in isolation (branches) without disturbing working code.
- Merge work from multiple people or multiple lines of work back together.
- Collaborate with others via a shared remote copy of the repository, commonly hosted on GitHub.
Git is distributed, meaning every clone of a repository is a full copy of the entire project history — not just a pointer to a central server. This is different from older, centralized systems (like Subversion), where only the central server holds the full history. With Git, you can commit, branch, and inspect history completely offline; you only need network access to synchronize with others via git push, git fetch, or git pull.
Overview: How Git Works Under the Hood
The single most important mental model for Git is this: Git stores snapshots, not differences. Many people assume Git tracks a list of diffs between file versions (like a patch log). It doesn’t. Every time you commit, Git takes a snapshot of every tracked file at that moment and stores a reference to that snapshot. If a file hasn’t changed since the last commit, Git doesn’t store it again — it just links to the identical file it already has.
Internally, Git builds this snapshot from three kinds of objects, all stored in the hidden .git directory and identified by a SHA-1 content hash (a 40-character fingerprint of the object’s contents):
- Blob — the raw contents of a single file (no filename, just data).
- Tree — like a directory listing; it maps filenames to blobs (files) and other trees (subdirectories), recreating your project’s folder structure.
- Commit — points to one top-level tree (the full snapshot), one or more parent commits (its history), an author, a committer, a timestamp, and a commit message.
Because objects are addressed by the hash of their content, identical content anywhere in your project’s history is stored only once — this is what makes Git both fast and space-efficient.
A branch (like main) is nothing more than a small file containing the SHA-1 of a single commit — a lightweight, movable pointer. When you commit, Git creates a new commit object and moves the current branch pointer to it. HEAD is a pointer to “where you currently are” — normally it points at a branch name (e.g. refs/heads/main), which in turn points at a commit. When HEAD points directly at a commit instead of a branch, you’re in what’s called a detached HEAD state, covered in a later lesson.
Between your files and a commit sit two more concepts you’ll use constantly:
- Working tree (working directory) — the actual files on disk that you edit.
- Staging area (the index) — a middle step where you assemble exactly what the next commit will contain. Running
git addcopies a snapshot of a file into the index; runninggit committurns whatever is in the index into a permanent commit object.
Syntax
Git commands follow a consistent shape:
git <command> [options] [arguments]
A few of the core commands you’ll meet immediately:
| Command | Purpose |
|---|---|
git init |
Turn the current directory into a new Git repository. |
git config |
Set configuration values, such as your identity. |
git status |
Show the state of the working tree and staging area. |
git add |
Stage changes (copy them into the index) for the next commit. |
git commit |
Record the staged snapshot as a new commit object. |
git log |
Show the commit history. |
Examples
Example 1: Checking your Git installation and identity
Before your first commit, Git needs to know who you are — this name and email are baked into every commit you create.
git --version
git config --global user.name "Ada Lovelace"
git config --global user.email "ada@example.com"
git config --global init.defaultBranch main
Output:
git version 2.43.0
The --global flag writes these settings to ~/.gitconfig, so they apply to every repository on your machine. The last line sets main as the default name for a repository’s first branch (modern Git already defaults to this, but setting it explicitly avoids surprises on older installs).
Example 2: Creating your first repository and commit
mkdir recipe-book
cd recipe-book
git init
echo "# Recipe Book" > README.md
git status
git add README.md
git commit -m "docs: add initial README"
Output:
Initialized empty Git repository in /home/ada/recipe-book/.git/
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.md
nothing added to commit but untracked files present (use "git add" to track)
[main (root-commit) 3f1a2b9] docs: add initial README
1 file changed, 1 insertion(+)
create mode 100644 README.md
git init creates the hidden .git directory that holds the entire object database and configuration for this repository — nothing outside that folder is Git-specific. git status shows README.md as untracked because Git sees the file in the working tree but has never been told to track it. git add stages it into the index; git commit then writes a blob for the file’s contents, a tree describing the snapshot, and a commit object pointing to that tree — and moves the main branch pointer to the new commit.
Example 3: Inspecting history with git log
echo "## Pasta" >> README.md
git add README.md
git commit -m "docs: add pasta section"
git log --oneline
Output:
a7c9de2 (HEAD -> main) docs: add pasta section
3f1a2b9 docs: add initial README
git log --oneline prints each commit as a shortened SHA-1 prefix plus its message, newest first. Notice HEAD -> main next to the latest commit: this confirms HEAD currently points at the main branch, which points at commit a7c9de2, which has 3f1a2b9 as its parent — the chain of parent pointers is your entire project history.
How It Works Step by Step
When you run git add README.md followed by git commit -m "...", Git performs these steps internally:
- 1.
git addreads the current contents ofREADME.md, compresses and stores it as a blob object (named by the SHA-1 hash of its content), and records that filename-to-blob mapping in the index. - 2.
git commitwalks the index and builds one or more tree objects that mirror your project’s directory structure, pointing at the relevant blobs. - 3. Git creates a new commit object containing: the SHA-1 of the top-level tree, the SHA-1 of the current commit as its parent, your configured name/email as author and committer, a timestamp, and your commit message.
- 4. Git moves the current branch pointer (e.g.
main) forward to this new commit’s SHA-1. SinceHEADpoints at the branch name rather than a raw commit,HEADnow “follows” to the new commit automatically.
Common Mistakes
Mistake 1: Never setting your identity.
git commit -m "initial commit"
Without user.name and user.email configured, Git either refuses to commit or falls back to guessing an identity from your operating system account, producing commits with a wrong or generic author. Fix it once, globally, before your first commit:
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
Mistake 2: Assuming edited files are automatically tracked.
Git never commits changes you haven’t staged. Editing a tracked file and running git commit without an intervening git add commits the previous staged snapshot, silently leaving your latest edits out:
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
modified: README.md
Always run git status before committing to confirm exactly what’s staged, and use git add (or git commit -a for already-tracked files) deliberately.
Mistake 3: Running git init inside an existing repository. This creates a nested .git directory, which confuses tooling because the inner repository is now invisible to the outer one (Git treats it as a special embedded reference, not a normal folder). If this happens accidentally, simply remove the mistakenly created inner .git directory — don’t run git init again “to fix it.”
Best Practices
- Set
user.nameanduser.emailglobally before your first commit, and override them per-repository only when you genuinely need a different identity (e.g. a work vs. personal project). - Run
git statusoften — it’s free, safe, and tells you exactly what Git currently sees. - Commit small, logical changes with clear messages rather than one giant commit at the end of the day.
- Adopt a consistent commit message style early, such as Conventional Commits (
feat:,fix:,docs:,refactor:) — it pays off once history grows long. - Add a
.gitignorefile from the start so build artifacts, dependencies, and secrets never get staged by accident. - Use
git log --onelineorgit log --graph --onelineregularly to build an intuition for how commits and branches relate.
Practice Exercises
- Exercise 1: Configure your global Git identity, then create a new repository called
notes-appwith a singleREADME.mdfile, staged and committed with the messagedocs: initial commit. - Exercise 2: In that same repository, create a second file, check
git statusto confirm it shows as untracked, stage it, and commit it separately. Then rungit log --onelineand confirm you see two commits. - Exercise 3: Edit
README.mdagain but do not stage it. Rungit statusand identify which section of the output tells you the change is unstaged — then stage and commit it.
Summary
- Git is a distributed version control system: every clone holds the full project history, not just the latest snapshot.
- Git stores content as snapshots, built from three object types: blobs (file contents), trees (directory structure), and commits (a snapshot plus metadata and parent links).
- A branch is just a movable pointer to a commit;
HEADnormally points to the current branch. - The working tree, the staging area (index), and the commit history are three distinct stages a change passes through.
git initcreates a repository,git addstages changes,git commitpermanently records them, andgit loglets you inspect history.- Always configure your identity and check
git statusbefore committing to avoid the most common beginner mistakes.
