Module 1 — Why Version Control, and What Git Actually Is (git config)

Updated 8 September 2026

Module 1 — Why Version Control, and What Git Actually Is (git config). Every Git command you will ever run makes sense only if you know what problem Git solves and what kind of machine it is. This module builds that mental model — it is the difference between memorizing commands and deriving them.

🧠 concept → 🧩 analogy → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (hidden)

Before you start. This is the first module — no earlier modules are needed, and no Git knowledge is assumed. You need: a Linux machine (or macOS, or WSL on Windows), a terminal, and the ability to type commands and read their output. If the terminal itself is still new territory, the Linux and Bash Scripting tracks in this same DevOps Learning library teach those fundamentals. We install Git itself in Part C, so you do not need it installed yet.
This page contains Mermaid diagram blocks. Notion shows them as code by default — click the block and switch it to Preview to see the diagram. You only need to do this once per block.

Part A — The problem version control solves

A1. Life without version control

Imagine you keep a server's config file. You are about to make a risky change, so you make a copy first. Next month you do it again. And again. Soon the directory looks like this: app.conf, app.conf.bak, app.conf.old, app.conf.final, app.conf.final2, app.conf.works_dont_touch.

Now answer three questions. Which copy was live last Tuesday, when things still worked? What exactly changed between .final and .final2? Who made the change, and why? You cannot. The information was never recorded. Copies capture content but throw away history: the order of changes, the author, the reason.

A version control system (VCS) exists to record exactly those three things, automatically, for every change: what changed, who changed it, and why (a message the author writes). Once recorded, you can compare any two points in time, restore any earlier state, and read the story of a file backwards.

Real-world analogy — the shop's CCTV vs a photo album

Copy-files-by-hand is a photo album: a few snapshots someone remembered to take, undated, unlabeled, with gaps exactly where the interesting events happened. A version control system is CCTV: it records continuously, every frame is timestamped, and you can wind back to any moment and see who did what.

Where the analogy stops working. CCTV records on its own. A VCS only records when you ask it to save a snapshot — and that is deliberate. You choose the moments worth recording and attach a reason to each one. Uncommitted work is invisible to it, like events outside the camera's view.

🧪 Exercise 1.1 — feel the problem
bash
mkdir -p ~/git-course/webapp && cd ~/git-course/webapp
# Recreate the classic mess by hand:
touch app.conf app.conf.bak app.conf.old app.conf.2024-11-03
touch app.conf.works_dont_touch app.conf.final app.conf.final2
ls -1        # -1 = one name per line
Expected result — click to reveal
plain text
app.conf
app.conf.2024-11-03
app.conf.bak
app.conf.final
app.conf.final2
app.conf.old
app.conf.works_dont_touch

What to read out of it: ls sorts alphabetically, so the names appear in dictionary order, not in the order you created them — the listing itself has already destroyed the one piece of history you had. Nothing here tells you which file is newest, what differs between any two, or why any copy was made. Every question you would ask in an incident ("what changed and when?") is unanswerable. This directory is the "before" picture for the whole track.

Now imagine this at 500 hosts. One person's messy folder is an annoyance. Five hundred servers, each with its own drift of hand-edited configs and .bak files, is an outage generator: no two hosts are provably identical, and no one can say what changed before things broke. This is why fleet tooling (Ansible, Terraform, Kubernetes manifests) keeps its source of truth in a VCS and pushes outward — the repository becomes the one place where change is recorded.

A2. What a version control system is

A version control system is a database of snapshots. You work on your files normally; at moments you choose, you tell the VCS "record the current state." The VCS stores that state, plus who recorded it, when, and a message saying why. Each recorded state is called a commit (we will make our first one in Module 2).

From that simple idea, everything else follows mechanically. Because every snapshot is kept: you can restore any of them. Because snapshots are ordered: you can read history. Because each records its author: you get accountability. Because two people's snapshots can be compared: you get collaboration — merging their work is an operation on recorded states, not on guesswork.

Hold on to this definition: a VCS is a snapshot database plus tools to compare, restore, and combine snapshots. Every Git command in this track is one of those four verbs — record, compare, restore, combine — wearing different clothes.

A3. Centralized vs distributed

Version control systems come in two shapes, and the difference decides what you can do when the network is down.

A centralized VCS (Subversion/SVN, CVS, Perforce in its classic mode) keeps the snapshot database on one server. Your machine holds only a working copy — the files as of one moment. Every history operation — viewing a log, comparing old versions, recording a change — is a network call to the server. If the server is down, history is unreachable. If its disk dies without backups, history is gone.

A distributed VCS (Git, Mercurial) gives every machine the entire snapshot database. When you copy a Git project (Module 8 teaches the command), you receive all snapshots ever recorded, not just the latest. Recording, comparing, and restoring are local disk operations: they work on a plane, and they take milliseconds instead of round-trips. A "central server" still usually exists in practice — but it is a convention (the copy everyone agrees to share through), not an architectural requirement. Every full copy is a complete backup.

Diagram source
flowchart TB
    subgraph C["Centralized (SVN)"]
        S["Server<br>ALL history"]
        W1["Dev A<br>working copy only"]
        W2["Dev B<br>working copy only"]
        W1 -->|"every operation"| S
        W2 -->|"every operation"| S
    end
    subgraph D["Distributed (Git)"]
        H["Shared copy<br>ALL history"]
        L1["Dev A<br>ALL history"]
        L2["Dev B<br>ALL history"]
        L1 <-->|"sync when chosen"| H
        L2 <-->|"sync when chosen"| H
    end
Counter-intuitive: "distributed" does not mean "no central server." Almost every Git team still designates one shared copy (usually on GitHub or GitLab) as the meeting point. What changes is when you need it: in SVN you need the server to do anything with history; in Git you need the shared copy only at the moments you choose to synchronize. The server moves from the critical path of every operation to the critical path of collaboration only.
Real-world analogy — the town library vs everyone owning the encyclopedia

A centralized VCS is the town's only library: one copy of the encyclopedia, and every lookup means a trip there. If it closes, nobody reads. A distributed VCS gives every household its own full encyclopedia, plus a habit of meeting weekly to copy new pages to each other — usually at one agreed house, for convenience, not necessity.

Where the analogy stops working. Encyclopedias are huge and copying them is costly, so owning one per house sounds wasteful. Git's snapshot database is compressed and deduplicated so aggressively (Module 3 shows how) that the "whole encyclopedia" is routinely smaller than a single checked-out copy of the project. The intuition "full history must be heavy" is exactly wrong.

🎯 Interview questions — Part A

🎯 "What is a version control system (VCS)?" — asked verbatim in Interview Coder's 90+ Git questions, Sep 2025

A version control system records snapshots of a set of files over time, together with who made each change and why. That record makes four operations possible: restoring any earlier state, comparing any two states, reading the history of a change, and merging changes made in parallel by different people. Version control is the backbone of code collaboration: it lets many people change the same codebase concurrently without overwriting each other, and it makes every change reviewable and reversible.

The details that separate candidates: an average answer stops at "it tracks changes to code." A strong answer (1) names the two architectures — centralized (SVN: history lives only on the server) and distributed (Git: every clone has full history) — and what that means operationally: offline work, speed, and every clone being a backup; (2) points out that a VCS versions any files, and that in DevOps that includes infrastructure definitions — Terraform, Ansible, Kubernetes manifests — which is what makes GitOps possible; (3) mentions that the unit of record is a deliberate, message-carrying commit, not an automatic file save — the history is curated, which is why it is readable.

🎯 "What are the advantages of Git over SVN?" — asked verbatim in Interview Coder's 90+ Git questions, Sep 2025

Four advantages, all consequences of one design decision — every clone holds the full history. Speed: log, diff, and commit are local disk reads in Git; in SVN they are network round-trips. Offline work: you can commit, branch, and inspect history with no server at all. Resilience: every developer's clone is a complete backup; an SVN server's disk is a single point of failure for history. Cheap branching: a Git branch is a tiny pointer into the local snapshot database (Module 5), so branching and merging are everyday moves; SVN branches are server-side directory copies, historically expensive enough that teams avoided them. Git also stages changes explicitly before committing (Module 2), letting you commit half your edits — SVN has no equivalent.

The details that separate candidates: an average answer says "Git is distributed and faster." A strong answer ties each advantage back to the mechanism (full local history) instead of listing adjectives, and is honest about the trade-off going the other way: SVN still handles enormous binary assets and partial checkouts of giant monorepos more simply, which is why game studios kept Perforce/SVN-style systems for years — and why Git grew LFS and partial clone (Modules 14–15) to close that gap.

Part B — What Git actually is

B1. Snapshots, not differences

Most older VCSs store a base version of each file plus a chain of differences ("line 12 changed from X to Y"). To reconstruct last month's state, they replay the chain. Git does not. Each time you record a snapshot, Git conceptually stores the full content of every file as it is right now. A commit is a picture of the whole project, not a list of edits.

Why does the design matter to you? Because it explains Git's behavior everywhere. Checking out an old state is fast — Git just unpacks that picture; nothing is replayed. Comparing two commits means diffing two complete pictures, so any two points in history can be compared directly, in any order. And a commit does not "belong" to the files it changed — it is the state of everything.

Counter-intuitive: "a full copy of every file, every commit" sounds ruinously wasteful — commit 500 times and store your project 500 times? It is not, for two reasons you will verify with your own eyes in Module 3: a file that did not change between snapshots is stored once and referenced by both (deduplication by content), and stored content is compressed and later packed with delta-compression at the storage layer. Snapshots are the model you reason with; deltas still exist, but demoted to an invisible storage optimization. Git shows you diffs, so people assume it stores diffs — it computes them on demand from snapshots.
Real-world analogy — the restaurant's end-of-day photos

A delta-based VCS is a kitchen logbook: "moved the salt one shelf down, replaced the blender." To know how the kitchen looked in March, you replay every entry since opening day. Git photographs the entire kitchen at closing time. Any photo is instantly viewable; comparing March and July is holding two photos side by side. And the photographer is clever: shelves that did not change are not re-photographed — the new photo just points at yesterday's shot of that shelf.

Where the analogy stops working. Two photos of the same shelf are merely similar; Git's snapshots of an unchanged file are the same stored object, byte for byte, found by hashing the content. That is also why Git notices even a one-character change without watching your keystrokes: different content, different hash, different object — a mechanism no camera has.

B2. Nearly every operation is local

Combine A3 and B1: your machine holds the whole snapshot database, stored as snapshots. The consequence is that almost every Git command reads or writes local disk only. Viewing history, comparing versions, recording commits, creating branches, switching branches — no network. Exactly four everyday commands touch the network, and they are precisely the ones that move snapshots between your database and someone else's: clone, fetch, pull, push (all taught in Module 8).

This is worth internalizing now, before you know any commands, because it dissolves a whole class of beginner fear. Committing does not "send" anything anywhere. Experimenting cannot break the shared copy. Until you explicitly run one of the four network commands, everything you do stays on your machine, private and reversible.

Trap: the flip side of "commits are local" is that committed work you never push leaves the building with your laptop. A stolen or dead laptop takes every unpushed commit with it. Teams bitten by this adopt the habit: share your work at least daily. Local-first is a feature for experimentation and a liability for durability — you decide which by how often you push.

B3. Git is not GitHub

Git is the version control program on your machine: open source, written by Linus Torvalds in 2005 to manage Linux kernel development, run from your terminal, fully functional with no account and no internet. GitHub is a company's website that hosts Git snapshot databases so teams have a shared meeting point — and layers collaboration features on top: pull requests, issues, code review, CI hooks. GitLab and Bitbucket are competitors in the same role. None of them is Git, and Git needs none of them: two laptops on a desert island can collaborate over a USB stick.

The confusion is commercially convenient and technically corrosive. Keep the layers separate in your head: everything in Modules 1–7 works with zero GitHub; GitHub-style collaboration gets its own module (Module 9) precisely because it is a separate layer.

🎯 Interview questions — Part B

🎯 "What is Git?" — asked verbatim in Top Git Interview Questions for 2025, dev.to, Aug 2025

Git is a distributed version control system: a program that records snapshots of a project's files into a local database, so that any earlier state can be restored, any two states compared, and parallel lines of work merged. "Distributed" means every copy of the repository contains the entire history, so all core operations are local and fast, and synchronization with other copies happens only when explicitly requested. It was created by Linus Torvalds in 2005 for Linux kernel development and is now the de-facto standard VCS.

The details that separate candidates: an average answer is "a tool for tracking code changes." A strong answer states the storage model — content-addressed snapshots, not file diffs — because that model is what explains Git's speed, cheap branching, and integrity guarantees; mentions that history is a chain of commits each naming its parent, so tampering is detectable; and distinguishes Git from hosting (GitHub/GitLab). Naming the four network commands (clone, fetch, pull, push) as the only network operations signals real working knowledge.

🎯 "What is the difference between Git and GitHub?" — asked verbatim in both dev.to (Aug 2025) and Interview Coder (Sep 2025)

Git is the version control software itself — local, open source, terminal-based, functional without any network. GitHub is a hosting service that stores Git repositories on the internet as a shared synchronization point, and adds a collaboration layer that is not part of Git: pull requests, issue tracking, code review UI, permissions, and CI/CD integration (GitHub Actions). Git can be used without GitHub (self-hosted, or with GitLab/Bitbucket/Gitea); GitHub is meaningless without Git.

The details that separate candidates: an average answer says "Git is the tool, GitHub is the website." A strong answer can name which everyday concepts belong to which layer — commits, branches, merges, tags are Git; pull requests, forks-as-a-workflow, issues, protected branches are host features — and knows that a "pull request" does not exist in Git itself (the nearest native mechanism is git request-pull, which almost nobody uses). Bonus signal: pointing out that in DevOps the host is also the policy enforcement point — branch protection, required reviews, CI gates — which is why choosing and configuring the host is an engineering decision, not a cosmetic one.

Part C — Installing Git and getting help

C1. Install Git and check the version

Git is a single command-line program, installed from your system's package manager. On Debian/Ubuntu: sudo apt install git. On RHEL/Fedora/Amazon Linux: sudo dnf install git. On macOS: xcode-select --install (Apple's developer tools include Git) or brew install git. On Windows: install "Git for Windows" from git-scm.com, or use WSL and the Linux instructions. The install-check is the same everywhere: ask Git its version.

Package-manager versions lag the newest release, and that is fine. Anything from roughly 2.30 onward has every command this track uses; where a newer feature matters, the track says so.

🧪 Exercise 1.2 — verify the install
bash
git --version    # prints the installed version and proves the binary is on PATH
which git        # shows WHERE the binary lives
Expected result — click to reveal
plain text
git version 2.43.0
/usr/bin/git

What to read out of it: the version format is major.minor.patch — here 2.43.0; yours will likely differ, and anything ≥ 2.30 is fine for this track. which git printing a path proves the shell can find the binary; if instead you get git: command not found, Git is not installed (or not on PATH) — run your platform's install command from above and repeat. On macOS, /usr/bin/git may be Apple's shim that offers to install developer tools on first run — that offer is the installer; accept it.

🧪 Exercise 1.3 — a deliberate failure: mistype an option
bash
git --verison    # misspelled on purpose
echo "exit code: $?"   # $? holds the exit code of the previous command
Expected result — click to reveal (this one fails on purpose)
plain text
unknown option: --verison
usage: git [-v | --version] [-h | --help] [-C <path>] [-c <name>=<value>]
           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]
           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
           [--config-env=<name>=<envvar>] <command> [<args>]
exit code: 129

What to read out of it: three lessons in one error. First, Git names the exact thing it rejected (unknown option: --verison) — always read the first line before anything else. Second, it prints its own usage summary, which is a free mini-reference: you can see -v is the short form of --version. Third, the exit code is 129, not 0 — nonzero means failure, and scripts and CI pipelines make decisions based on this number. Getting comfortable reading error output now pays off all track long: several exercises in every module fail on purpose.

C2. Getting help without leaving the terminal

Official docs: git-help manual

Git ships its entire manual with itself. Three habits cover everything: git help <command> (or equivalently man git-<command>) opens the full manual page for a command; git <command> -h prints a compact option summary to the terminal without opening anything; bare git help lists the everyday commands grouped by purpose. The full manuals are dense — the -h summary is usually what you want mid-task, and the same pages live on git-scm.com/docs if you prefer a browser.

🧪 Exercise 1.4 — the three help habits
bash
git help | head -25      # bare help: the guided tour (head -25 = first 25 lines)
git status -h            # -h: compact option summary, no pager
Expected result — click to reveal
plain text
usage: git [-v | --version] [-h | --help] [-C <path>] [-c <name>=<value>]
           ...
These are common Git commands used in various situations:
start a working area (see also: git help tutorial)
   clone     Clone a repository into a new directory
   init      Create an empty Git repository or reinitialize an existing one
work on the current change (see also: git help everyday)
   add       Add file contents to the index
   mv        Move or rename a file, a directory, or a symlink
   restore   Restore working tree files
   rm        Remove files from the working tree and from the index
examine the history and state (see also: git help revisions)
   bisect    Use binary search to find the commit that introduced a bug
   diff      Show changes between commits, commit and working tree, etc
   grep      Print lines matching a pattern
   log       Show commit logs
   ...
usage: git status [<options>] [--] [<pathspec>...]
    -v, --[no-]verbose    be verbose
    -s, --[no-]short      show status concisely
    -b, --[no-]branch     show branch information
    ...

What to read out of it: bare git help groups commands by task ("start a working area", "work on the current change", "examine the history") — that grouping is a map of this entire track. Notice how many commands you already recognize from this module's roadmap: init, add, status, log, branch. The -h output for status shows each option in both short (-s) and long (--short) form — they are interchangeable; long forms read better in scripts and documentation, short forms are for typing.

🎯 Interview questions — Part C

🎯 "What are the advantages of using GIT?" — asked verbatim in Interview Coder's 90+ Git questions, Sep 2025

Grouped by who benefits. For the individual: every operation except sync is local, so it is fast and works offline; history is a safety net — any recorded state is recoverable; experiments are free because branches are cheap pointers, not copies. For the team: parallel work merges mechanically instead of by hand; every change carries author, timestamp, and rationale, so git log doubles as an audit trail; every clone is a full backup, so there is no single point of failure for history. For the organization/DevOps: Git is the de-facto standard, so the entire toolchain — CI/CD, code review, GitOps controllers like Argo CD and Flux — is built to trigger from and reconcile against Git repositories; free and open source; one skill portable across every employer.

The details that separate candidates: an average answer lists adjectives — "fast, distributed, open source." A strong answer explains why each holds (fast because operations are local reads of a compressed object database; safe because snapshots are immutable and content-addressed), and lands the DevOps-specific point: Git's guarantees are what make "the repo as single source of truth" workable, which is the founding assumption of GitOps. Being able to name the trade-off (unpushed local work dies with the laptop; policy needs a hosting layer on top) shows judgment rather than advocacy.

Part D — First-time setup

D1. Tell Git who you are

Remember from A1: a VCS records what, who, and why. The who has to come from somewhere — Git stamps your name and email into every snapshot you record. On a fresh machine Git has no idea who you are, and rather than guess, it refuses to record anything until you tell it. So the very first configuration every engineer performs is setting an identity. Two commands, once per machine:

bash
git config --global user.name "Aisha Rahman"
git config --global user.email "[email protected]"

git config is Git's settings tool; --global means "for my whole user account on this machine" (D2 explains the alternatives); user.name and user.email are setting names in section.key form. Use your real name and the email tied to your hosting account — this identity becomes part of permanent, public history.

🧪 Exercise 1.5 — watch Git refuse an anonymous snapshot

This exercise runs three commands you have not been taught (init, add, commit — Module 2 teaches all three properly). Type them on faith this once: the point is what happens when identity is missing.

bash
mkdir -p ~/git-course/demo && cd ~/git-course/demo
git init                      # make this directory a repository (Module 2)
echo "hello" > notes.txt
git add notes.txt             # stage the file (Module 2)
git commit -m "first"         # try to record a snapshot (Module 2)
Expected result — click to reveal (fails on purpose — unless your machine already has an identity set)
plain text
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint: 	git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint: 	git branch -m <name>
Initialized empty Git repository in /home/aisha/git-course/demo/.git/
Author identity unknown
*** Please tell me who you are.
Run
  git config --global user.email "[email protected]"
  git config --global user.name "Your Name"
to set your account's default identity.
Omit --global to set the identity only in this repository.
fatal: unable to auto-detect email address (got 'aisha@laptop.(none)')

What to read out of it: two separate things happened. The hint: block and Initialized empty Git repository… line are git init succeeding — hints are advice, not errors; ignore the branch-name advice until D3, where we act on it. Then the commit is what failed. This is Git's error style at its best — it states the problem (Author identity unknown), gives the exact commands that fix it, and mentions an alternative (Omit --global… — that is D2's subject). The last line shows what Git tried: auto-deriving an email from your username and hostname, and rejecting the result as invalid. If your commit succeeded instead, this machine already has an identity configured — run git config user.email to see what your commits are being stamped with, and check it is the identity you intend.

Now fix it and retry:

plain text
$ git config --global user.name "Aisha Rahman"
$ git config --global user.email "[email protected]"
$ git commit -m "first"
[master (root-commit) 6efef04] first
 1 file changed, 1 insertion(+)
 create mode 100644 notes.txt

The commit line reads: branch name (master — D3 revisits this), root-commit meaning "first snapshot ever in this repository", 6efef04 the snapshot's short ID (yours will differ — Module 3 explains why it is derived from content, timestamp, and your identity), then a change summary.

Trap: Git does not verify identity — it records whatever you configured. Nothing stops a machine from committing as user.name "Linus Torvalds". Authentication to a server (Module 8) is separate from authorship stamped in commits, and interviewers love this distinction. The fix for teams that need proof of authorship is commit signing (Module 12 touches signed tags); hosting platforms then mark commits "Verified".

D2. One setting, three levels

Git reads settings from three files, in a fixed order, and the closest one wins. From widest to narrowest: system level (/etc/gitconfig, flag --system) applies to every user on the machine — rarely touched outside fleet management. Global level (~/.gitconfig, flag --global) applies to everything you do on this machine — where your identity from D1 landed. Local level (.git/config inside one repository, flag --local, and the default when you set a value inside a repo) applies to that repository only.

Later-read levels override earlier ones, so: local beats global beats system. The classic use: your global identity is your personal email; inside each work repository you set a local user.email with your corporate address. Same setting, different answer depending on where you ask from.

Real-world analogy — office dress code

The company handbook says "business casual" (system). Your department's memo says "jeans are fine" (global). Your team's whiteboard says "hoodies on Fridays" (local). On Friday you wear the hoodie: the narrowest rule that mentions the topic wins, and nobody needs to rewrite the handbook. Remove the whiteboard note and the department memo silently takes over.

Where the analogy stops working. Dress codes are enforced by people who can weigh exceptions. Git's precedence is mechanical and silent: it will not warn you that a local value is shadowing your global one, and the shadowed value does not merge with the winner — for a single-valued setting, the loser might as well not exist. When a setting "mysteriously ignores" your global config, suspicion should land on a local override immediately — and the exercise below shows the flag that proves it.

🧪 Exercise 1.6 — override globally-set identity in one repo, then prove which file wins
bash
cd ~/git-course/demo                              # inside the repo from 1.5
git config user.email "[email protected]"   # no flag = --local here
git config --show-origin user.email               # WHERE did the winning value come from?
git config --list --show-origin | grep user.      # all identity values, with their source files
Expected result — click to reveal
plain text
file:.git/config	[email protected]
file:/home/aisha/.gitconfig	user.name=Aisha Rahman
file:/home/aisha/.gitconfig	[email protected]
file:.git/config	[email protected]

What to read out of it: line 1 answers "what will actually be used, and why": the winning user.email comes from file:.git/config — the local level. The listing below it shows both user.email values still exist — global in ~/.gitconfig, local in .git/config. Nothing was overwritten; the local one wins only because it is read later. --show-origin is the debugging flag to remember: config confusion stops being guesswork the moment every value confesses which file it came from.

🧪 Exercise 1.7 — a deliberate failure: --local outside any repository
bash
cd /tmp                          # /tmp is not a Git repository
git config --local user.name "X"
echo "exit code: $?"
Expected result — click to reveal (fails on purpose)
plain text
fatal: --local can only be used inside a git repository
exit code: 128

What to read out of it: local settings live in .git/config inside a repository — with no repository, there is nowhere to write, and Git says so rather than inventing one. Note the exit code differs from Exercise 1.3's: 128 is Git's convention for "fatal error in a valid command", while 129 was "you invoked me wrongly". Scripts distinguish these. git config --global from the same directory would succeed — ~/.gitconfig exists regardless of where you stand.

Now imagine this at 500 hosts. On your laptop, config levels are personal convenience. Across a fleet, they are a management hierarchy: bake org-wide policy (proxy, credential helpers, URL rewrites) into /etc/gitconfig with your config-management tool; humans keep identity in --global; automation that must commit (CI runners, GitOps bots) sets identity per-repo or per-invocation so bot commits are attributable to the pipeline, not to whichever engineer built the image. --show-origin is your first debugging move when one host of 500 behaves differently.

D3. Sensible defaults worth setting today

Three more settings finish the setup. init.defaultBranch — the name given to a repository's first branch. Git's historical default is master; the industry (and GitHub/GitLab) moved to main, and Git itself prints a hint about this on every git init until you choose. Set main to match the ecosystem and silence the hint. core.editor — the editor Git opens when a command needs a typed message and none was given on the command line. Defaults to the shell's $EDITOR or system default, which on many distros is vim; if you do not know vim, being trapped in it during your first merge is a rite of passage worth skipping. Aliasesgit config --global alias.st status teaches Git that git st means git status; define them freely for commands you type constantly.

bash
git config --global init.defaultBranch main
git config --global core.editor nano     # or vim, or "code --wait" for VS Code
git config --global alias.st status
🧪 Exercise 1.8 — see the defaultBranch hint, then silence it
bash
cd ~/git-course
git init hintdemo               # init.defaultBranch still unset — the 1.5 hint prints again
git config --global init.defaultBranch main
git init newproj                # AFTER — hint gone
cd newproj && git branch --show-current
Expected result — click to reveal
plain text
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint: 	git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint: 	git branch -m <name>
Initialized empty Git repository in /home/aisha/git-course/hintdemo/.git/
Initialized empty Git repository in /home/aisha/git-course/newproj/.git/
main

What to read out of it: you already saw this hint in Exercise 1.5 — it prints on every git init until you configure the choice, which is exactly what makes it a nudge worth obeying. After setting the config, the second init is hint-free, and git branch --show-current confirms the new repository's first branch is named main. If it printed nothing at all, you are in the wrong directory — an empty repo in this Git version still reports its unborn branch name, so silence means "not where you think you are".

D4. One laptop, two GitHub accounts

A situation almost every engineer hits: one GitHub account for work, one personal — and one laptop. In the browser this is easy (two profiles, two logged-in sessions). The terminal is harder, because there is only one git and one home directory, and Git will happily stamp your personal email into work history forever. The setup becomes simple once you see that "which account am I using?" is really two independent questions:

  1. Identity — which name/email is stamped into the commits you record. Pure git config: this module's material, and it applies even when you never touch the network.
  2. Authentication — which credential is shown to GitHub when you run one of the four network commands from B2 (clone, fetch, pull, push). That machinery is Module 8's territory — we deliberately preview a small piece of it here, because the two halves of this setup belong on the same page.

Confusing the two produces the classic failure: you authenticate as the right account, yet the commits appear under the wrong email — because authentication never touches what git config stamped.

Layer 1 — identity that switches itself. Setting a local user.email in every work repo (D2's trick) works, but it does not scale and forgetting it once means wrongly-stamped history. Since Git 2.13 the clean fix is a conditional include: a section in ~/.gitconfig that pulls in an extra config file only when the repository you are standing in lives under a chosen folder. Adopt one convention — everything work-related is cloned under ~/work/, everything personal elsewhere — and identity takes care of itself.

🧪 Exercise 1.9 — per-folder identity with includeIf (works fully offline — no GitHub account needed)
bash
mkdir -p ~/work ~/personal
cat >> ~/.gitconfig <<'EOF'

[includeIf "gitdir:~/work/"]
	path = ~/.gitconfig-work
EOF
cat > ~/.gitconfig-work <<'EOF'
[user]
	email = [email protected]
EOF
git init -q ~/work/deploy-scripts        # a "work" repository
git init -q ~/personal/bread-recipes     # a "personal" repository
cd ~/work/deploy-scripts && git config user.email
cd ~/personal/bread-recipes && git config user.email
cd ~/work/deploy-scripts && git config --show-origin user.email
cd ~/personal/bread-recipes && git config --show-origin user.email
Expected result — click to reveal
plain text
[email protected]
[email protected]
file:/home/aisha/.gitconfig-work	[email protected]
file:/home/aisha/.gitconfig	[email protected]

What to read out of it: the same command answers differently depending on which repository you are standing in — no local config was written into either repo (check: cat ~/work/deploy-scripts/.git/config has no [user] section). The two --show-origin lines prove the mechanism: inside ~/work/deploy-scripts the value came from ~/.gitconfig-work (the conditionally-included file), while inside ~/personal/bread-recipes it came from plain ~/.gitconfig. The include behaves as if its contents were pasted into the global file at the [includeIf] line — later than the global [user] section, so it wins there, exactly by D2's "last read wins" rule. Your path will show your own home directory, not /home/aisha.

Counter-intuitive: gitdir matches where the repository's .git directory is at the moment you run the command — it is evaluated per-command, not stored anywhere. Three consequences, all verified: standing in ~/work but outside any repository, git config user.email shows the personal email (no .git dir, nothing to match); moving a repo from ~/personal/ into ~/work/ silently switches its identity for all future commits; and the trailing slash in gitdir:~/work/ matters — a pattern ending in / means "and everything under it, recursively" (Git appends **), which is what makes one line cover every present and future work repo.
🧪 Exercise 1.10 — break it: drop the trailing slash
bash
sed -i 's#gitdir:~/work/#gitdir:~/work#' ~/.gitconfig    # break: remove trailing slash
cd ~/work/deploy-scripts && git config user.email
sed -i 's#gitdir:~/work#gitdir:~/work/#' ~/.gitconfig    # restore it
git config user.email
Expected result — click to reveal (fails on purpose)
plain text

What to read out of it: with gitdir:~/work (no slash) the condition only matches a .git directory located exactly at ~/work — not one inside ~/work/deploy-scripts/ — so the include never fires and the first command falls back to the global personal email. No error, no warning: config conditions that match nothing are silent, which is why the wrong-email problem is usually discovered in a code review, not at commit time. After restoring the slash, the work identity returns. When an includeIf "is being ignored", check the pattern's trailing slash first.

Layer 2 — one credential per account (a Module 8 preview). When you push, GitHub does not care what user.email says — it cares which SSH key or token you present, and one key can only be attached to one account. The standard setup, straight from GitHub's own documentation: one SSH keypair per account, and an alias per account in ~/.ssh/config so the URL you clone with selects the key:

bash
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_personal -C "personal"
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_work -C "work"
# then upload each .pub file to the matching GitHub account
# (Settings → SSH and GPG keys)
plain text
# ~/.ssh/config
Host github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_ed25519_personal
    IdentitiesOnly yes
Host github.com-work
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_ed25519_work
    IdentitiesOnly yes

Now git clone [email protected]:you/hobby.git authenticates as the personal account, while git clone [email protected]:bigco/deploy-scripts.git ~/work/deploy-scripts authenticates as the work account — github.com-work is not a real hostname, it is your alias, and SSH swaps in github.com plus the work key (IdentitiesOnly yes stops SSH from "helpfully" offering every key it knows, which would let the wrong account win). Notice what just happened: one folder convention now drives both layers — cloning work repos under ~/work/ via the work alias gives them the work credential and the work email. Honesty note: this page's exercises were all executed and verified, but an SSH round trip to GitHub (ssh -T [email protected]) needs two real GitHub accounts, so this one setup could not be executed on the machine this track was verified on — the config format above is taken directly from GitHub's documentation linked at the top of this section.

If you use HTTPS instead of SSH: the gh CLI stores one credential per account and gh auth switch flips the active one; alternatively git config --global credential."https://github.com".useHttpPath true makes the credential helper cache a credential per repository path instead of one per hostname, so different repos can hold different tokens.

Trap: the two layers fail independently, and each failure looks like the other. Wrong email but pushes work → identity layer (fix includeIf / check the trailing slash). Right email but push rejected with Permission denied or repository not found → authentication layer (wrong key/token was presented; check which alias the remote URL uses). Diagnose them separately: git config --show-origin user.email for layer 1, ssh -T git@<alias> for layer 2. And remember from D1: neither layer verifies the other — GitHub will accept a push authenticated as your work account containing commits stamped with your personal email without a murmur (platforms only display the mismatch, e.g. an unrecognized-author avatar).
Now imagine this at 500 hosts. Multi-account juggling is a human problem — automation should never inherit it. CI runners and GitOps controllers get their own machine users, per-repository deploy keys, or short-lived tokens scoped to one pipeline — never an engineer's personal credential, which turns one leaver's offboarding into a fleet-wide outage. The includeIf idea scales the other way too: config management can drop a fleet-wide /etc/gitconfig while each engineer's ~/.gitconfig carries only their personal includes.

Interviewers rarely ask about multi-account setups by name — it arrives disguised as "how do you use a different identity per project?", which is exactly the second question in the Part D interview set below.

🎯 Interview questions — Part D

🎯 "Explain the levels in git config and how you can configure values using them?" — asked verbatim in Interview Coder's 90+ Git questions, Sep 2025

Three levels, read in order, narrowest wins. System (git config --system, file /etc/gitconfig): all users on the machine; typically managed by IT or config management. Global (git config --global, file ~/.gitconfig): all of one user's repositories; where personal identity, editor, and aliases live. Local (git config --local, file .git/config; the default for writes inside a repo): one repository; overrides both others. Values are set with git config [level] section.key value, read with git config section.key, and audited with git config --list --show-origin, which prints every value with the file it came from. There are also --worktree (below local, per-worktree, with per-worktree config enabled) and per-invocation -c key=value, which overrides everything for that one command.

The details that separate candidates: an average answer names the three levels. A strong answer states the precedence mechanism (files read widest-to-narrowest; last read wins — nothing merges for single-valued keys), names the actual file paths, gives the canonical use case (corporate email locally, personal globally), and offers --show-origin as the way to debug "Git is ignoring my setting". Mentioning -c for one-shot overrides — how CI systems inject identity without touching any file — signals production experience.

🎯 "How do you configure a Git repository to use a specific user for a particular project?" — asked verbatim in Interview Coder's 90+ Git questions, Sep 2025

Inside that repository, set the identity at the local level: git config user.name "Work Name" and git config user.email "[email protected]" (inside a repo, plain git config writes locally; --local makes it explicit). These land in .git/config and override the global identity for this repository only — verify with git config --show-origin user.email. For many work repos, per-repo config does not scale; since Git 2.13 the clean solution is a conditional include in ~/.gitconfig: an [includeIf "gitdir:~/work/"] section pointing at a file with the work identity, so every repository under ~/work/ gets it automatically.

The details that separate candidates: an average answer gives the two local git config commands. A strong answer adds the verification step, the includeIf/gitdir pattern for scale, and the operational warning: identity is read at commit time, so commits made before the local override was set are already stamped wrong, and fixing them requires history rewriting (Module 10) — which is why this config belongs in your clone-a-new-work-repo checklist, not in incident response.

Part E — Production practice

E1. Symptom → cause → diagnosis → fix

Click the symptom you're seeing.

⚠️ git: command not found

What is really happening: Git is not installed, or its directory is not on PATH.

Diagnose: which git · echo $PATH

The fix: install via package manager (sudo apt install git / sudo dnf install git); on macOS accept the developer-tools prompt.

⚠️ Commit rejected with Author identity unknown

What is really happening: no user.name/user.email at any config level on this machine/account — common on fresh servers and CI runners.

Diagnose: git config --list --show-origin | grep user.

The fix: git config --global user.name "…" and …user.email "…"; in CI, inject per-invocation with git -c user.email=bot@ci …

⚠️ Commits appearing under the wrong name/email (e.g. personal email in work repos)

What is really happening: global identity applying where a local override was never set — precedence working as designed.

Diagnose: git config --show-origin user.email inside the affected repo.

The fix: set local identity in that repo, or add [includeIf "gitdir:~/work/"] to ~/.gitconfig (D4); already-made commits need history rewriting (Module 10).

⚠️ Two GitHub accounts — pushes succeed, but commits show the wrong account's email

What is really happening: the identity layer and the authentication layer are independent (D4) — the SSH key/token decided which account pushed, while git config decided what email was stamped, and nothing checks they agree.

Diagnose: git config --show-origin user.email in the repo (layer 1) · check the remote URL's host alias and ssh -T git@<alias> (layer 2).

The fix: one folder convention driving both — [includeIf "gitdir:~/work/"] for the email, a ~/.ssh/config host alias in the clone URL for the credential; check the includeIf pattern's trailing slash first.

⚠️ A setting you configured is "being ignored"

What is really happening: a narrower config level (usually local) is shadowing the value you set — silently, by design.

Diagnose: git config --list --show-origin | grep <key>

The fix: remove or change the winning entry at the level shown: git config --local --unset <key>

⚠️ hint: Using 'master' as the name for the initial branch… on every git init

What is really happening: init.defaultBranch was never chosen, so Git advertises the choice each time.

Diagnose: git config init.defaultBranch (empty output = unset).

The fix: git config --global init.defaultBranch main

⚠️ Terminal "frozen" in an unfamiliar full-screen editor after a Git command

What is really happening: Git opened core.editor/$EDITOR — often vim — to ask for a message.

Diagnose: nothing to run — you are inside vim: type :q! then Enter to leave without saving.

The fix: git config --global core.editor nano (or your editor with a wait flag).

E2. Capstone — four tickets

Ticket 1 — "New CI runner can't commit." A freshly provisioned build agent fails its pipeline at a step that records an automated version-bump commit: Author identity unknown … fatal: unable to auto-detect email address (got 'runner@ip-10-2-14-7.(none)').

Worked answer: the runner image has no Git identity at any level — Git tried to auto-derive one from user@hostname and refused the invalid result. Three fixes, in ascending quality: (1) bake git config --system user.name "CI Bot" + user.email "[email protected]" into the image — --system because runner jobs may execute as varying users; (2) set it in the pipeline's setup step with --global; (3) best: per-invocation git -c user.name="CI Bot" -c user.email="[email protected]" commit …, which leaves no machine state and survives image rebuilds. Any of these makes bot commits attributable to the pipeline in history.

Ticket 2 — "My open-source commits show my work email." An engineer contributes to a public project from their laptop and notices the public history shows [email protected]. They "already fixed their config last month" — yet it keeps happening on this project.

Worked answer: run git config --show-origin user.email inside that clone. Two possible findings. If it shows file:.git/config → dev@corp…: a local override exists in this repository — last month's "fix" was global, and local wins; remove it with git config --local --unset user.email. If it shows the global file: their global identity is the work address; set the personal one globally and add [includeIf "gitdir:~/work/"] so work repos get the corporate identity automatically. Note the commits already pushed remain stamped — changing config never edits history (Module 10 covers rewriting, and public history is rarely worth rewriting).

Ticket 3 — "Which VCS for the new infra team?" Management asks for a one-paragraph recommendation: the team of 8 will manage Terraform and Ansible code, review every change, and must survive the loss of any single machine.

Worked answer: Git. Justify with mechanisms, not fashion: every clone carries full history, so losing any machine — including the server — loses nothing that was shared (the requirement is met by architecture, not backup policy); review-per-change maps onto the branch/merge-request model every Git host provides; and the infra toolchain (Atlantis, Argo CD, Flux, pre-commit) assumes Git as the source of truth. Costs to state honestly: a learning curve for the two engineers coming from SVN, and the need to pick a hosting layer (GitHub/GitLab/self-hosted Gitea) since Git itself ships no permissions or review UI — that choice is a follow-up decision with its own security requirements.

Ticket 4 — "Audit says: prove who changed the firewall config." During an incident review, security asks whether Git history is proof of authorship for a change in the network-config repository.

Worked answer: no — and saying so precisely is the job. user.name/user.email are unverified, self-declared config values; any machine can commit under any name (D1's trap). What Git history does prove: the content of every snapshot and its position in the chain — tampering with an old commit changes every later ID (Module 3 explains why). Authorship proof requires the layers on top: authenticated push access with server-side logs showing which credential pushed (Module 8), and cryptographically signed commits/tags (Module 12) if commit-level proof is required. Recommend: enable signing for that repository and treat host push-logs, not author fields, as the audit source.

E3. Documentation reference

TopicOfficial sourceWhat it covers
Version control conceptsGit Book §1.1 — About Version ControlLocal vs centralized vs distributed, with diagrams
Git's designGit Book §1.3 — What is Git?Snapshots not diffs, locality, integrity, the three states
InstallationGit Book §1.5 — Installing GitPer-platform install instructions
First-time setupGit Book §1.6 — First-Time Git SetupIdentity, editor, checking settings
git config commandgit-config manualAll levels, files, flags, and every configuration variable
git help commandgit-help manualHelp formats, -h vs full manual, guides list
Conditional includes (includeIf)git-config manual — conditional includesgitdir patterns, trailing-slash semantics, include precedence
Multiple GitHub accountsGitHub Docs — Managing multiple accountsPer-account SSH keys, host aliases, HTTPS credential options

E4. Self-assessment

Try each question aloud, from memory, before opening its answer — the gap between your answer and the hidden one is what to study.

1. What three pieces of information does a VCS record about every change that plain file copies lose?

What changed (the content of the change itself), who made it (the author identity), and why (the message explaining the rationale) — plus when it happened. A folder of copied files like report_final_v2.docx keeps none of these reliably; a VCS records all of them for every single change, which is what turns history into an audit trail you can search and reason about.

2. In one sentence each: what does a centralized VCS keep on your machine, and what does a distributed VCS keep?

A centralized VCS keeps only your working copy — a checkout of one version — on your machine, with the full history living solely on the central server. A distributed VCS like Git keeps the complete history in every clone, so nearly every operation is a local read and every clone doubles as a full backup of the shared history.

3. "Git stores diffs between versions." What is wrong with that sentence, and what does Git store instead?

Git does not store differences — it stores a full snapshot of the project with every commit, and computes diffs on demand when you ask for them. The distinction matters because the snapshot model is what makes checking out any version a direct read (not a replay of patches) and what makes branching and integrity checking cheap.

4. Why is storing a full snapshot per commit not ruinously wasteful? (Two mechanisms.)

First, deduplication by content: a file that did not change between commits is stored exactly once and every snapshot that contains it just points to the same stored object. Second, compression at the storage layer: Git compresses its object database (and packs similar objects together), so even the stored-once content costs far less than its raw size.

5. Name the only four everyday Git commands that touch the network.

git clone, git fetch, git pull, and git push. Everything else — committing, branching, diffing, viewing history — operates on the local repository, which is why Git stays fast and fully usable offline.

6. Your colleague says "I can't use Git without a GitHub account." Correct them in two sentences.

Git is a standalone tool that runs entirely on your machine — you can init, commit, branch, and view history with no account, no internet, and no server at all. GitHub is one of several hosting services layered on top of Git that add sharing, permissions, and review; you only need one when you want to collaborate through it.

7. What are the three config levels, their files, and the precedence rule when the same key is set at all three?

System (/etc/gitconfig, flag --system) for every user on the machine; global (~/.gitconfig, flag --global) for everything you do; local (.git/config, flag --local, the default when writing inside a repo) for one repository. Git reads them widest-to-narrowest and the last value read wins, so local beats global beats system — silently, with nothing merged for single-valued keys.

8. A setting you configured globally is being ignored in one repository. What single command tells you why?

git config --list --show-origin (or git config --show-origin <key> for just that key) run inside the repository. It prints every value alongside the file it came from, so you can see exactly which narrower level — almost always a local entry in .git/config — is shadowing your global setting.

9. Exit code 129 vs 128 — which did the misspelled option produce, and what does each conventionally mean?

The misspelled option (git --verison) produced 129, Git's convention for a usage error — the command line itself was malformed, and Git printed its usage summary. 128 is Git's general fatal-error code: the command line was valid but the operation failed (like the identity-less commit's fatal: message). Either way, nonzero means failure — which is exactly what scripts and CI pipelines branch on.

10. Why does Git refuse to commit without user.name/user.email rather than guessing — and does setting them prove who authored a commit?

Because identity becomes part of permanent, shared history: Git tries to auto-derive an address from user@hostname, recognizes the result (like aisha@laptop.(none)) as invalid, and refuses rather than stamp garbage into every future snapshot. And no — these values are declared, not verified: any machine can commit under any name, so proof of authorship needs the separate layers of authenticated push access (Module 8) and cryptographic signing (Module 12).

E5. Sources

Interview questions in this module were captured verbatim from: Interview Coder — 90+ Common Git Interview Questions (Sep 20, 2025) and dev.to — Top Git Interview Questions and Answers for 2025 (Aug 1, 2025). Technical claims were verified against the official Git documentation listed in E3, and every command and its output on this page was executed on Git 2.43.0 on Linux. Where your output differs (versions, hashes, paths), the expected-result notes say which parts vary.

🗒️ Cheat sheet — Module 1

CommandWhat it does
git --versionPrint installed version; proves Git is on PATH
git help · git help <cmd> · git <cmd> -hCommand overview · full manual for one command · compact option summary
git config --global user.name "…" / user.email "…"Set the identity stamped into your commits (once per machine)
git config [--system | --global | --local] key valueWrite a setting at a chosen level (inside a repo, default is local)
git config key · git config --listRead one setting · list all settings
git config --show-origin key · --list --show-originShow which file each value comes from — the config debugger
git config --local --unset keyDelete a setting at a level
git config --global init.defaultBranch mainName new repositories' first branch main; silences the init hint
git config --global core.editor nanoChoose the editor Git opens for messages
git config --global alias.st statusDefine git st as shorthand for git status
git -c key=value <cmd>Override any setting for one command only (CI pattern)
[includeIf "gitdir:~/work/"]path = ~/.gitconfig-workIn ~/.gitconfig: auto-apply a work identity to every repo under ~/work/ (D4)
Host github.com-workIdentityFile in ~/.ssh/configPer-account SSH alias — the clone URL's host picks which account authenticates (D4)

Key concepts: a VCS is a snapshot database recording what/who/why — everything else is compare, restore, combine · centralized = history on one server; distributed = full history in every clone, so operations are local and every clone is a backup · Git stores snapshots, not diffs — deduplicated by content, compressed at the storage layer · only clone/fetch/pull/push touch the network · Git ≠ GitHub: tool vs hosting-plus-collaboration layer · config precedence: local beats global beats system, silently — debug with --show-origin · commit identity is declared, not verified · two accounts on one laptop = two independent layers: includeIf switches the stamped email by folder, SSH host aliases (or gh auth switch) pick the credential.

Next: Module 2 — Your First Repository — you have Git installed and configured; now you make a directory into a repository and record your first real snapshots: init, status, add, commit, and the staging area that makes Git's workflow unique.
Spotted a mistake or want something added? Send me a note.