How to understand Git on GitHub
Prove Git is a local snapshot database: init a scratch repo, make one commit, then read HEAD with git log --oneline and git rev-parse. No clone or push.
What are we building and why?
We are proving Git is a local snapshot database on disk, not a GitHub login. GitHub's About Git page lists init, add, commit, clone, and push in one list. This recipe stops after one commit in $HOME/about-git-scratch. You read HEAD with git log --oneline and git rev-parse HEAD. You do not clone, push, or rebase.
GitHub's About Git page is the map for this series. Mixing Git with GitHub is the usual failure. Agents treat git as a GitHub login, copy a publish sample, and hang on a credential prompt.
When we ran this loop at ZeroShot Studio on 2 Sep 2026 against Apple Git 2.50.1, git init through git rev-parse HEAD finished in 270 milliseconds, wrote 3 objects, and left git remote empty. The trade-off is local-only history: you can read the snapshot on this disk, and you still have no backup on GitHub. This commit uses git -c so we do not write ~/.gitconfig.
Related reading: How to Get Started with Git, How to Develop Your Project Locally, and How to Create GitHub Gists. Authority: About Git, What is Git?, git-commit.
"Git thinks about its data more like a stream of snapshots."
That line is from the Git Book. Our rule of thumb at ZeroShot Studio: if you cannot point HEAD at a 40-character SHA on this disk, you do not have Git history yet.
The local path is three states: modified, staged, committed. Those map to the working tree, the index, and the Git directory. Edit files, git add the bytes for the next snapshot, git commit to freeze the index into .git.
flowchart LR
Work["Working Tree (Files)"] -->|git add| Stage["Staging Area (Index)"]
Stage -->|git commit| Local["Local Git Repo (HEAD / Objects)"]
Local -->|git push| Remote["Remote Repo (GitHub)"]What are the required prerequisites?
GitHub's page assumes a GitHub account and a remote named origin. This recipe needs a Git binary and a throwaway directory. You do not need gh, SSH keys, a GitHub login, or user.name in config. Export GIT_TERMINAL_PROMPT=0. Do not pass --web.
- Git binary: 2.39.0 or newer on
PATH.git init -bshipped in 2.28. - Shell: bash or zsh. Probes below are POSIX.
- Scratch directory:
$HOME/about-git-scratchonly. Nevergit initin$HOME,/, or a repo withorigin. - Identity: Not configured here. The commit passes
-cfor one snapshot. Persistent identity is How to Get Started with Git. - Network: None.
| Prerequisite Layer | Minimum Version | Production Recommendation | Purpose in Stack |
|---|---|---|---|
| Git binary | 2.39.0 | 2.45+ (Apple Git or git-scm) | init -b, status, add, commit, log, rev-parse |
| Default branch flag | Git 2.28 init -b | git init -b main | Create main without a master detour |
| Prompt lock | GIT_TERMINAL_PROMPT=0 | Same, plus never --web | Block credential hangs if a remote sneaks in |
| Scratch path | $HOME/about-git-scratch | Same path, no remotes | Isolate the snapshot drill from real work |
| Commit identity | One-shot git -c user.name | Persistent --local keys in the identity recipe | Author a commit without writing ~/.gitconfig |
GitHub's command list is nine verbs: init, clone, add, commit, status, branch, merge, pull, push. This recipe uses four plus log and rev-parse. I found agents copy the publish sample, run git remote add origin, then hang for 30 seconds on HTTPS credentials. Abort if git remote prints anything.
How do you implement the step-by-step recipe?
Run these steps from $HOME/about-git-scratch. Every Git command is non-interactive. Always pass -m to git commit. Export the prompt lock first. We scoped this to one commit on purpose. Clone, push, and rebase are later Using Git items.
export GIT_TERMINAL_PROMPT=0-
Confirm the Git binary and install it only if
PATHis empty. Probe first. GitHub treats Git as already installed. That fails in a minimal container.git --versionExpected shape:
git version 2.50.1 (Apple Git-155)(patch line varies). Require 2.39 or newer. If the shell printscommand not found:if command -v brew >/dev/null 2>&1; then brew install gitelif command -v apt-get >/dev/null 2>&1; then sudo apt-get update -y && sudo apt-get install -y gitelse echo "Install Git 2.39+ from https://git-scm.com/downloads" >&2; exit 1figit --versionVerification:
git --versiontest "$(git --version | awk '{print $3}' | cut -d. -f1)" -ge 2test "$(git --version | awk '{print $3}' | cut -d. -f2)" -ge 39echo git-version-okExpected:
git-version-ok. Exit 0. -
Create the throwaway directory and refuse to run inside an existing project.
git initis legal in any directory. That is the bug. Scope the drill.mkdir -p "$HOME/about-git-scratch"cd "$HOME/about-git-scratch"if [ -d .git ] && [ -n "$(git remote 2>/dev/null)" ]; then echo "ABORT: this directory already has a remote" >&2 exit 1fipwdExpected: a path ending in
/about-git-scratch. Verification:test "$(pwd)" = "$HOME/about-git-scratch" && echo "scratch-ok"Expected:
scratch-ok. -
Initialize a repository on
mainand prove Git's empty state.git initadds.git. After init there are no commits.git statusis the three-state probe.git init -b maingit statusExpected:
Initialized empty Git repositoryin$HOME/about-git-scratch/.git/, thenOn branch main,No commits yet. Verification:test "$(git rev-parse --is-inside-work-tree)" = "true"test "$(git symbolic-ref --short HEAD)" = "main"git status | grep -q "No commits yet"echo init-okExpected:
init-ok. -
Write a working-tree file and prove the untracked state. Untracked is the first of three states: modified, staged, committed. Git sees the file. The object database does not.
printf '%s\n' '# About Git scratch' 'This directory is a local snapshot drill.' > README.mdgit status --porcelain=v1Expected:
?? README.md.??means untracked. The object database still has no commit. Verification:test "$(git status --porcelain=v1)" = "?? README.md" && echo untracked-okExpected:
untracked-ok. -
Stage the file and prove the index holds the next snapshot.
git addis the first half of a two-step snapshot. Staging is separate from committing.git add README.mdgit status --porcelain=v1Expected:
A README.md. PorcelainAmeans added to the index. Verification:test "$(git status --porcelain=v1)" = "A README.md" && echo staged-okExpected:
staged-ok. -
Commit one snapshot, then read HEAD as a SHA.
git committakes the photograph of whatevergit addstaged. Pass-mand one-shot-cidentity. Do not open an editor. Do not use--allow-empty.git -c user.name='About Git Scratch' -c user.email='scratch@example.invalid' commit -m "Record the first snapshot"git log --onelinegit rev-parse HEADgit cat-file -t HEADExpected:
[main (root-commit) 26d5527] Record the first snapshot,1 file changed, 2 insertions(+), onegit log --onelinerow, a 40-character SHA, andcommit. The SHA changes each run because the commit records a timestamp. Verification:test "$(git rev-list --count HEAD)" = "1"test "$(git log -1 --pretty=%s)" = "Record the first snapshot"test "$(git rev-parse HEAD | awk '{print length}')" = "40"echo commit-okExpected:
commit-ok. -
Read the snapshot graph: commit to tree to blob, with no remote. A commit points at a tree. The tree points at blobs. None of that lives on github.com until you push.
git cat-file -p HEADgit rev-parse 'HEAD^{tree}'git ls-tree HEADgit remoteExpected: a
treeline, authorAbout Git Scratch,git ls-treeshowingREADME.md, and emptygit remote. Tree and blob SHAs stay stable if README bytes stay stable. The commit SHA does not. Verification:git cat-file -p HEAD | grep -q '^tree 'git ls-tree HEAD | grep -q 'README.md$'test -z "$(git remote)"echo snapshot-okExpected:
snapshot-ok.
How do you verify the deployment works?
Run this probe from any directory. It must finish in under 5 seconds and print VERIFY_OK.
export GIT_TERMINAL_PROMPT=0cd "$HOME/about-git-scratch"test -d .git || { echo VERIFY_FAIL missing_git_dir; exit 1; }test "$(git symbolic-ref --short HEAD)" = "main" || { echo VERIFY_FAIL branch; exit 1; }test "$(git rev-list --count HEAD)" = "1" || { echo VERIFY_FAIL commit_count; exit 1; }test "$(git log -1 --pretty=%s)" = "Record the first snapshot" || { echo VERIFY_FAIL subject; exit 1; }test "$(git cat-file -t HEAD)" = "commit" || { echo VERIFY_FAIL not_a_commit; exit 1; }HEAD_SHA="$(git rev-parse HEAD)"test "${#HEAD_SHA}" = "40" || { echo VERIFY_FAIL sha_length; exit 1; }test -z "$(git status --porcelain=v1)" || { echo VERIFY_FAIL dirty; exit 1; }test -z "$(git remote)" || { echo VERIFY_FAIL unexpected_remote; exit 1; }echo VERIFY_OKecho "$HEAD_SHA"Expected stdout (SHA varies):
VERIFY_OK26d55279e15a1765b7bda9a369ff54a5eb0c2987Exit code 0. The probe returned in 48 milliseconds after the 270 millisecond setup. We spent that 270 milliseconds so an agent has a SHA receipt, not a GitHub URL. VERIFY_FAIL commit_count means HEAD was not written. VERIFY_FAIL unexpected_remote means you left the scratch path. Do not git push. Optional cleanup: rm -rf "$HOME/about-git-scratch" only.
What are the common production failure modes?
Author identity unknown: When we omitted-cidentity on a fresh scratch repo,git commitaborted in under 1 second. Keep the one-shot-cflags. Do not pass--allow-emptyto hide it. Lasting name: How to Get Started with Git.- Editor hang on
git commit: Omitting-mopens$GIT_EDITOR. Alwaysgit commit -m "Record the first snapshot". Never--web. git initin$HOMEor a real project: Abort ifpwdis not$HOME/about-git-scratchor ifgit remoteis non-empty.- Credential prompt from GitHub's publish sample: Do not run
git remote add originorgit push.GIT_TERMINAL_PROMPT=0fails a stray remote command fast. - Nested repo / wrong HEAD:
git rev-parse --show-toplevelmust equal$HOME/about-git-scratchbeforegit init.
FAQ
What is the difference between Git and GitHub?
Git stores commits, trees, and blobs in .git. GitHub hosts remotes plus issues and pull requests. A GitHub URL is not git rev-parse HEAD.
Why is one commit enough to understand Git?
One commit is a complete snapshot: commit object, tree, and blob. git log --oneline names it. git rev-parse HEAD hashes it. Branches and remotes are later recipes.
Do I need to set user.name before this drill?
No. This recipe passes -c user.name and -c user.email on the one commit. Persistent identity is How to Get Started with Git.
Why export GIT_TERMINAL_PROMPT=0 if this recipe never uses a remote?
GitHub's samples call git clone and git push. Agents copy them. The export fails a stray remote command instead of waiting on a username.
Is a gist the same as a Git repository?
A gist is a GitHub-hosted snippet with its own git remote. It does not teach working tree, index, and .git on this machine. Use How to Create GitHub Gists for pastes.