gitcore-tutorial — A Git Core tutorial for devs (Plumbing)
Reference, not a lesson — condensed from the official Git manual page gitcore-tutorial(7). The numbered course pages teach the model; this records what the manual says. Checked against Git 2.48.1: nothing on this page depends on a newer release.
High-level manual pages and the porcelain tutorial:
- Git-Man-Page (
git(1)): Primary command-line tool, options, and subcommand classifications. - Git-Tutorial-Man-Page (
gittutorial(7)): Getting started with imports, commits, branches, and collaboration.
Description
This tutorial explains how to use the “core” Git commands to set up and work with a Git repository.
If you just need to use Git as a revision control system you may prefer to start with “A Tutorial Introduction to Git” (gittutorial(7)) or the Git User Manual.
However, an understanding of these low-level tools can be helpful if you want to understand Git’s internals.
The core Git is often called “plumbing”, with the prettier user interfaces on top of it called “porcelain”. You may not want to use the plumbing directly very often, but it can be good to know what the plumbing does when the porcelain isn’t flushing.
Back when this document was originally written, many porcelain commands were shell scripts. For simplicity, it still uses them as examples to illustrate how plumbing is fit together to form the porcelain commands. The source tree includes some of these scripts in contrib/examples/ for reference. Although these are not implemented as shell scripts anymore, the description of what the plumbing layer commands do is still valid.
Note: Deeper technical details are often marked as Notes, which you can skip on your first reading.
Plumbing vs. Porcelain
- Plumbing: Low-level commands (
hash-object,cat-file,write-tree,commit-tree,update-index,update-ref,read-tree,merge-index) that manipulate objects and references directly. - Porcelain: High-level user-facing commands (
add,commit,checkout,switch,branch,status,merge,pull,push) designed for developer workflows. (Historically, many porcelain commands were shell scripts built around these plumbing commands).
Creating & Initializing a Repository
When you initialize a working directory with git init, Git creates a hidden .git/ directory containing the core metadata layout:
Repository Layout (.git/)
HEAD: File containing a symbolic reference to the active branch (e.g.,ref: refs/heads/masterorref: refs/heads/main).objects/: Content-addressed object database holding immutable blobs, trees, commits, and tags indexed by 160-bit SHA-1 hashes (40-character hex string).refs/: Pointer directory containing references (refs/heads/for branches,refs/tags/for tags).
Commands
$ mkdir git-tutorial
$ cd git-tutorial
$ git init
# Output: Initialized empty Git repository in .git/
Populating the Object Store & Index
Checking in work involves two stages:
- Populating the index file (cache) with working tree content state.
- Writing the index cache into immutable tree and commit objects in the database.
Commands & Object Inspection
# Create initial working tree files
$ echo "Hello World" > hello
$ echo "Silly example" > example
# Stage files into the index (low-level update-index)
$ git update-index --add hello example
# Inspect generated loose objects in the database
$ ls .git/objects/??/*
# Output example:
# .git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238
# .git/objects/f2/4c74a2e500f5ee1332c86b94199f52b1d1d962
# Check object type and content using git cat-file
$ git cat-file -t 557db03de997c86a4a028e1ebd3a1ceb225be238
# Output: blob
$ git cat-file blob 557db03
# Output: Hello World
Committing Git State (Plumbing Approach)
A commit is constructed by:
- Converting current index state to a
treeobject viagit write-tree. - Creating a
commitobject wrapping thattreeobject (with log message and parent pointers) viagit commit-tree. - Updating the active branch reference (
HEAD) to point to the new commit viagit update-ref.
Plumbing Workflow Example
# 1. Write index cache to a tree object
$ tree=$(git write-tree)
# 2. Create commit object pointing to tree
$ commit=$(echo 'Initial commit' | git commit-tree $tree)
# 3. Update branch ref to point to new commit
$ git update-ref HEAD $commit
Porcelain Equivalent
$ git commit
Comparing State & Difference Mechanics
Git provides specific plumbing utilities to inspect differences across the three core layers (Working Directory, Index Cache, Object DB):
diff-tree
+----+
| |
V V
+-----------+
| Object DB |
| Backing |
| Store |
+-----------+
^ ^
| |
| | diff-index --cached
| |
| |
diff-index | |
| V
| +-----------+
| | Index |
| | "cache" |
| +-----------+
| ^
| |
| | diff-files
V V
+-----------+
| Working |
| Directory |
+-----------+
git diff-files: Compares Working Directory vs. Index Cache.git diff-index --cached HEAD: Compares Index Cache vs.HEADcommit object in Object DB.git diff-index HEAD: Compares Working Directory vs.HEADcommit object in Object DB.git diff-tree HEAD^ HEAD: Compares two tree objects directly in the Object DB.
Command Execution
# Compare modified working tree file against index
$ echo "It's a new day for git" >> hello
$ git diff-files -p
# Stage change and compare HEAD commit against working directory
$ git update-index hello
$ git diff-index -p HEAD
Tagging Versions
Tags are references to specific commits:
- Lightweight Tag: A simple file in
.git/refs/tags/<tagname>containing a commit hash. - Annotated / Signed Tag: An explicit Git object containing tagger identity, date, message, and optional PGP signature.
# Create lightweight tag
$ git tag my-first-tag
$ git diff my-first-tag
# Create signed annotated tag
$ git tag -s v1.0
Copying & Cloning Repositories
Because Git repositories are completely self-contained inside .git/, filesystem utilities (cp, rsync) can duplicate or move repositories directly.
Copying Local Repositories
# Copy entire repository folder
$ cp -a git-tutorial new-git-tutorial
$ cd new-git-tutorial
# Refresh index stat cache to match new inode/timestamp details
$ git update-index --refresh
# Hard reset index from HEAD if needed
$ git read-tree --reset HEAD
$ git update-index --refresh
Low-Level Remote Fetch vs. Porcelain Clone
# Low-level bare clone using rsync + plumbing setup
$ mkdir my-git
$ cd my-git
$ rsync -rL rsync://rsync.kernel.org/pub/scm/git/git.git/ .git
$ git read-tree HEAD
$ git checkout-index -u -a
# Porcelain equivalent
$ git clone git://git.kernel.org/pub/scm/git/git.git/ my-git
Branching Operations
Branches are lightweight pointer files stored under .git/refs/heads/. Switching branches updates .git/HEAD to point to the target branch ref (ref: refs/heads/<branch>).
# Create and switch to new branch
$ git switch -c mybranch
# Inspect active branch reference
$ cat .git/HEAD
# Output: ref: refs/heads/mybranch
# List local branches
$ git branch
Branch Merging & External Work
# Merge mybranch into master
$ git switch master
$ git merge -m "Merge work in mybranch" mybranch
# Inspect branch topologies
$ git show-branch --topo-order --more=1 master mybranch
# Fast-forward merge mybranch up to master tip
$ git switch mybranch
$ git merge -m "Merge upstream changes." master
Low-Level 3-Way Merge Mechanics
Under the hood, git merge executes a structured three-step plumbing process:
1. Find Common Ancestor SHA
$ mb=$(git merge-base HEAD mybranch)
$ git name-rev --name-only --tags $mb
2. Read Trees into Index Stages
Populate the index with 3 tree states using git read-tree:
- Stage 1: Common Ancestor (
$mb) - Stage 2: Target / Current Head (
HEAD) - Stage 3: Incoming Remote Head (
mybranch)
$ git read-tree -m -u $mb HEAD mybranch
3. Inspect & Resolve Index Stages
# Inspect stage entries (0 = merged/clean, 1 = ancestor, 2 = HEAD, 3 = mybranch)
$ git ls-files --stage
$ git ls-files --unmerged
# Perform file-level 3-way merge on unmerged index entries
$ git merge-index git-merge-one-file hello
Publishing Work (Pushing)
To publish commits, push references and missing objects from a local private repo to a bare public repository.
# On remote host: Initialize empty bare repository
$ mkdir my-git.git
$ GIT_DIR=my-git.git git init
# On local host: Push branch reference and objects to remote
$ git push <public-host>:/path/to/my-git.git master
Packing & Optimizing Repositories
Git stores individual loose objects under .git/objects/??/. Over time, these are compressed into packfiles (.pack) and index files (.idx) under .git/objects/pack/.
# Compress loose objects into packfiles
$ git repack
# Remove loose objects that were successfully packed
$ git prune-packed
# Validate packed archive structures
$ git verify-pack .git/objects/pack/*.idx
See Also
- Git-Man-Page (
git[1]) - Git-Tutorial-Man-Page (
gittutorial[7])