By admin , 28 April 2026

Introduction

git clone looks like one operation but actually composes several. Understanding the steps helps when troubleshooting partial clones, slow clones, or surprising defaults.

The sequence

  1. Create the target directory.
  2. Run git init inside it.
  3. Add origin as a remote with the source URL.
  4. Configure default refspec +refs/heads/*:refs/remotes/origin/*.
  5. Run git fetch origin to download objects and refs.
  6. Determine the default branch (the remote's HEAD).
  7. Create a local branch tracking it.
  8. Check out that branch into the working tree.

Watching it happen

GIT_TRACE=1 GIT_TRACE_PACKET=1 git clone https://github.com/example/widget.git 2>&1 | head -50

You will see capability negotiation, refs advertisement, want/have negotiation, and pack receive.

Wire protocol

Modern Git supports protocol v2 (git config protocol.version 2 or default in 2.26+). The server advertises only the requested refs rather than the full ref list, which dramatically improves clone times for repos with many refs.

Variants

git clone --bare                       # no working tree
git clone --mirror                     # bare + all refs, configured for mirroring
git clone --depth 1                    # shallow
git clone --filter=blob:none           # partial clone, skip blobs until needed
git clone --filter=tree:0              # even less data, on-demand trees
git clone --single-branch              # only one branch's history
git clone --branch v1.2.3              # checkout a specific branch/tag
git clone --recurse-submodules         # clone submodules too
git clone --no-checkout                # skip working tree population

Partial clones

Partial clones (--filter) defer object download until objects are needed. They require server support (most modern hosts have it):

git clone --filter=blob:none https://github.com/example/big.git
cd big
git log --oneline                  # cheap; only commit and tree objects fetched
git show HEAD:src/main.c           # this triggers a blob fetch

What goes where

After a successful clone:

  • .git/objects/pack/: the pack file received from the server.
  • .git/refs/remotes/origin/: every server branch as a remote-tracking ref.
  • .git/HEAD: symbolic ref to the new local branch.
  • .git/config: [remote "origin"] section with URL and refspec.

Server-side advertisement

The first thing the server sends in any clone is the ref advertisement. Under protocol v0/v1 this lists every ref, even if you only want one; on huge repos with millions of refs the advertisement alone could take seconds. Protocol v2 changes this: the client sends an ls-refs request specifying patterns, and the server only advertises matching refs:

git -c protocol.version=2 ls-remote origin 'refs/heads/main'
GIT_TRACE_PACKET=1 git -c protocol.version=2 ls-remote origin 2>&1 | head

For day-to-day clones the speedup is invisible; for hosting servers it is dramatic.

Common mistakes

Cloning a huge repo on a flaky network and starting over from scratch; git clone in 2.30+ supports resumable cloning over the smart HTTP protocol, but only if the server supports it. Otherwise --depth 1 first, then git fetch --unshallow later. Cloning into ~/Dropbox or another sync folder; the sync client will fight Git for control of .git/index. Cloning a repo with submodules and forgetting --recurse-submodules, ending up with empty submodule directories. Finally, cloning over HTTPS without a credential helper and being prompted for credentials on every fetch; configure one once.