Skip to main content
RunBook Academy

Git, CI/CD & GitOpsVI · Index / Staging AreaIndex

git add mechanics — what staging actually does under the hood

Intermediate⏱ ~20 mingit

What you'll learn

  • Trace what git add writes to the object store and to the index
  • Choose between git add <path>, git add ., and git add -A for a given intent
  • Use --intent-to-add to register an empty file for tracking without staging content
  • Use --chmod to set executable bits without a follow-up commit
  • Recognise that git add does not follow renames; git status does

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

git add looks like a single command but is doing two distinct things on every invocation: writing a blob into the object store and updating the entry in .git/index to point at that blob. The consequence is that every byte that has ever been staged is still in the object store as a reachable blob (assuming gc has not yet pruned it), and the index is a complete manifest of the next commit. Understanding the mechanics of git add is the difference between a deliberate commit and one that ships a stray file the engineer never reviewed.

What git add does, in order

For every path the command selects, git add performs the following steps:

  1. Hash the working tree file. Git reads the bytes, prefixes them with a header describing the object type (blob) and the byte length, and SHA-hashes the result. The hash is the blob’s OID.
  2. Write the blob to the object store. If a blob with that OID does not already exist under .git/objects/, Git writes it. The object store is append-only; identical bytes share a single blob across the entire repository.
  3. Update the index entry. The index entry for that path is rewritten to point at the new OID, with the current stat() cache so the next git status can determine whether the working tree has been modified since staging.
sequenceDiagram
    participant Dev as Engineer
    participant WT as Working tree
    participant OBJ as Object store\n(.git/objects)
    participant IDX as Index\n(.git/index)

    Dev->>WT: edit terraform/main.tf
    Dev->>OBJ: git add hashes bytes, writes blob
    Dev->>IDX: git add updates path -> blob OID
    Note over IDX: index now reflects new bytes

The crucial observation: git add never touches the working tree. The bytes on disk are not modified by staging. The index is updated; the file you edited is still on disk exactly as you left it. This is why a single file can be edited, partially staged, edited again, and partially re-staged without losing either version.

Pathspecs: <path>, ., and -A

git add selects files by pathspec. The three forms that come up most in infrastructure repositories are:

git add terraform/main.tf     # one explicit path
git add terraform/            # a directory and its tracked contents
git add .                     # the current directory and below
git add -A                    # the entire working tree (tracked + new + deleted)

The differences matter:

  • git add &lt;path&gt; stages only that path. If terraform/main.tf is the only file changed, only that file is staged. This is the safest form in an infrastructure repository.
  • git add . stages modifications and deletions of tracked files under the current directory, plus new files that are not ignored. It does not stage deletions of tracked files outside the current directory.
  • git add -A is the broad form: it stages modifications, deletions, and new files across the entire working tree, regardless of the current directory. The -A flag is short for --all.
flowchart TB
    A["git add &lt;path&gt;"] --> B["one explicit path only"]
    C["git add ."] --> D["cwd + subdirs\ntracked + new + deleted"]
    E["git add -A"] --> F["entire working tree\ntracked + new + deleted"]

—intent-to-add: register without content

git add --intent-to-add &lt;path&gt; is a flag for the case where a file exists in the working tree but is empty (or has no intentional content yet) and you want Git to start tracking it. The command adds the path to the index with an empty blob OID and a special “intent-to-add” marker. git status will then show the file as a new file staged for commit instead of as an untracked file.

touch playbooks/rotate-creds.yml
git add --intent-to-add playbooks/rotate-creds.yml
git status
# Changes to be committed:
#   new file:   playbooks/rotate-creds.yml

The file’s bytes are not yet in the index. When the engineer fills in the playbook and runs git add playbooks/rotate-creds.yml without --intent-to-add, the intent-to-add marker is replaced with the real blob and the file commits normally.

This flag is useful when a tooling step needs the file to be visible to Git before the file has any content — for example, a policy-as-code tool that must see the path in git diff --cached to attach a plan, or a CI stage that lints every file in the index even if the file is empty.

—chmod: change mode bits without a follow-up commit

git add --chmod=+x scripts/deploy.sh sets the executable bit in the index entry for the file. Without --chmod, Git tracks executable bits only as part of the file’s mode in the tree object, and changing the bit on disk with chmod does not update the index. The --chmod flag tells git add to update the index entry’s mode without re-hashing the content (the blob OID is unchanged because the content is unchanged).

chmod +x scripts/deploy.sh
git add --chmod=+x scripts/deploy.sh
git diff --cached
# old mode 100644 scripts/deploy.sh
# new mode 100755 scripts/deploy.sh

The inverse form, git add --chmod=-x, clears the executable bit. Both forms are useful when an infrastructure repository tracks executable hook scripts, deployment tools, or provisioning binaries that must keep their mode bits across checkouts on Windows or across tarball extractions that strip the bit.

Renames and the index

git add does not follow renames. Renames in Git are a post-hoc calculation: Git compares the blob OIDs of the removed file and the added file and declares a rename when the similarity score crosses a threshold (-M for files, --find-renames with a custom threshold). The index sees the operation as two distinct events: a deletion of the old path and an addition of the new path.

git mv scripts/deploy.sh bin/deploy.sh
git status
# Changes to be committed:
#   renamed:    scripts/deploy.sh -> bin/deploy.sh

git mv is a convenience that performs the working-tree move and a git add of both paths in one command. The renamed: line in git status is computed by Git when status runs; the index itself just records the deletion and the addition.

Production discipline

  1. Prefer explicit paths. git add terraform/main.tf is safer than git add . in any repository where a stray debug print or local override could leak into a commit. Use broad forms only after git status has confirmed there are no surprises.
  2. Use --intent-to-add deliberately. It is for files that must be visible to tooling before they have content. For every other new file, a plain git add &lt;path&gt; after the content is written is sufficient and avoids the marker bookkeeping.
  3. Stage chmod changes with --chmod. Do not rely on the filesystem mode surviving a clone or a tar extraction. The index entry is the source of truth for executable bits.

Cross-course references

  • Linux for Production Sysadmins - Part IX (Filesystem) covers the inode mode bits that --chmod manipulates; the Git analogue is that the index entry holds the mode that will be committed, not the mode currently on disk.
  • Ansible for Production Sysadmins - Part XXXVII (RepoArch) treats git add as the boundary between untrusted working changes and the playbook that will be run.
  • Terraform for Production Sysadmins - Part X (Plan) describes the equivalent of git add for Terraform: the terraform plan output that the operator reviews before terraform apply. Both are deliberate staging events.

Quiz

Knowledge check · 4 questions

  1. Q1. An engineer runs `git add .` from inside the `terraform/` subdirectory of a monorepo that also contains an `ansible/` directory. A tracked file has been deleted from `ansible/`. Which of the following is true?

  2. Q2. `git add` modifies the working tree file in addition to updating the index.

  3. Q3. Name the two storage locations `git add` writes to, and the third location it leaves unchanged.

  4. Q4. Diagnose why an executable deploy script lost its executable bit when checked out on a new machine, and choose the right remediation.

    A team commits a deploy script with the executable bit set on the engineer's macOS laptop. A new CI runner on Linux checks out the repository and finds the script non-executable, so the pipeline fails with 'Permission denied'. The engineer insists they committed the file with the bit set, and `git show HEAD:scripts/deploy.sh` on the laptop shows mode 100755.

Passing score: 75%. Answers are checked in this browser.