Back to Resources

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
4 linescompact
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)"]
Rendered from Mermaid source with the native ZeroLabs diagram container.

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 -b shipped in 2.28.
  • Shell: bash or zsh. Probes below are POSIX.
  • Scratch directory: $HOME/about-git-scratch only. Never git init in $HOME, /, or a repo with origin.
  • Identity: Not configured here. The commit passes -c for one snapshot. Persistent identity is How to Get Started with Git.
  • Network: None.
Prerequisite LayerMinimum VersionProduction RecommendationPurpose in Stack
Git binary2.39.02.45+ (Apple Git or git-scm)init -b, status, add, commit, log, rev-parse
Default branch flagGit 2.28 init -bgit init -b mainCreate main without a master detour
Prompt lockGIT_TERMINAL_PROMPT=0Same, plus never --webBlock credential hangs if a remote sneaks in
Scratch path$HOME/about-git-scratchSame path, no remotesIsolate the snapshot drill from real work
Commit identityOne-shot git -c user.namePersistent --local keys in the identity recipeAuthor 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.

Terminalbash
export GIT_TERMINAL_PROMPT=0
  1. Confirm the Git binary and install it only if PATH is empty. Probe first. GitHub treats Git as already installed. That fails in a minimal container.

    Terminalbash
    git --version

    Expected shape: git version 2.50.1 (Apple Git-155) (patch line varies). Require 2.39 or newer. If the shell prints command not found:

    Terminalbash
    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 --version

    Verification:

    Terminalbash
    git --versiontest "$(git --version | awk '{print $3}' | cut -d. -f1)" -ge 2test "$(git --version | awk '{print $3}' | cut -d. -f2)" -ge 39echo git-version-ok

    Expected: git-version-ok. Exit 0.

  2. Create the throwaway directory and refuse to run inside an existing project. git init is legal in any directory. That is the bug. Scope the drill.

    Terminalbash
    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 1fipwd

    Expected: a path ending in /about-git-scratch. Verification:

    Terminalbash
    test "$(pwd)" = "$HOME/about-git-scratch" && echo "scratch-ok"

    Expected: scratch-ok.

  3. Initialize a repository on main and prove Git's empty state. git init adds .git. After init there are no commits. git status is the three-state probe.

    Terminalbash
    git init -b maingit status

    Expected: Initialized empty Git repository in $HOME/about-git-scratch/.git/, then On branch main, No commits yet. Verification:

    Terminalbash
    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-ok

    Expected: init-ok.

  4. 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.

    Terminalbash
    printf '%s\n' '# About Git scratch' 'This directory is a local snapshot drill.' > README.mdgit status --porcelain=v1

    Expected: ?? README.md. ?? means untracked. The object database still has no commit. Verification:

    Terminalbash
    test "$(git status --porcelain=v1)" = "?? README.md" && echo untracked-ok

    Expected: untracked-ok.

  5. Stage the file and prove the index holds the next snapshot. git add is the first half of a two-step snapshot. Staging is separate from committing.

    Terminalbash
    git add README.mdgit status --porcelain=v1

    Expected: A README.md. Porcelain A means added to the index. Verification:

    Terminalbash
    test "$(git status --porcelain=v1)" = "A  README.md" && echo staged-ok

    Expected: staged-ok.

  6. Commit one snapshot, then read HEAD as a SHA. git commit takes the photograph of whatever git add staged. Pass -m and one-shot -c identity. Do not open an editor. Do not use --allow-empty.

    Terminalbash
    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 HEAD

    Expected: [main (root-commit) 26d5527] Record the first snapshot, 1 file changed, 2 insertions(+), one git log --oneline row, a 40-character SHA, and commit. The SHA changes each run because the commit records a timestamp. Verification:

    Terminalbash
    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-ok

    Expected: commit-ok.

  7. 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.

    Terminalbash
    git cat-file -p HEADgit rev-parse 'HEAD^{tree}'git ls-tree HEADgit remote

    Expected: a tree line, author About Git Scratch , git ls-tree showing README.md, and empty git remote. Tree and blob SHAs stay stable if README bytes stay stable. The commit SHA does not. Verification:

    Terminalbash
    git cat-file -p HEAD | grep -q '^tree 'git ls-tree HEAD | grep -q 'README.md$'test -z "$(git remote)"echo snapshot-ok

    Expected: 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.

Terminalbash
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):

text
VERIFY_OK26d55279e15a1765b7bda9a369ff54a5eb0c2987

Exit 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 -c identity on a fresh scratch repo, git commit aborted in under 1 second. Keep the one-shot -c flags. Do not pass --allow-empty to hide it. Lasting name: How to Get Started with Git.
  • Editor hang on git commit: Omitting -m opens $GIT_EDITOR. Always git commit -m "Record the first snapshot". Never --web.
  • git init in $HOME or a real project: Abort if pwd is not $HOME/about-git-scratch or if git remote is non-empty.
  • Credential prompt from GitHub's publish sample: Do not run git remote add origin or git push. GIT_TERMINAL_PROMPT=0 fails a stray remote command fast.
  • Nested repo / wrong HEAD: git rev-parse --show-toplevel must equal $HOME/about-git-scratch before git 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.

Share