Skip to main content
RunBook Academy

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

Partial staging with -p — hunk-level staging for clean commits

Intermediate⏱ ~21 mingit

What you'll learn

  • Explain how git add -p divides a file into hunks and presents them interactively
  • Drive the interactive prompts: y/n/q/a/d/e/?
  • Choose between edit (e) and split (s) for non-trivial hunks
  • Recognise when hunk-level staging is the right tool and when it is overkill
  • Avoid the common mistake of staging a debug print alongside the real fix

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.

Real working tree files accumulate changes. A Terraform module that started as “fix the S3 bucket tags” might also pick up a renamed variable, a reordered resource block, and a debug print() left over from a failed plan. Whole-file staging — git add terraform/main.tf — commits all of it together. The commit message says “fix bucket tags” but the diff is four unrelated changes. The reviewer cannot reason about the change and the audit trail loses meaning. git add -p solves this by splitting the file into hunks and letting you stage some while leaving others in the working tree.

How git add -p works

git add -p (or git add --patch) walks the file hunk by hunk. A hunk is a contiguous run of added and removed lines. Git’s default hunk-splitting uses the same algorithm that produces git diff output: changes separated by at least three unchanged lines become separate hunks. For each hunk, Git presents a prompt and waits for a one-key answer:

git add -p terraform/main.tf

diff --git a/terraform/main.tf b/terraform/main.tf
@@ -12,7 +12,7 @@ resource "aws_s3_bucket" "logs" {
   bucket = "prod-logs"
-  acl    = "private"
+  acl    = "log-delivery"
   tags = {
     Owner = "platform"

(1/2) Stage this hunk [y,n,q,a,d,e,?]?

The single-letter answers:

  • y — stage this hunk.
  • n — do not stage this hunk; leave it in the working tree.
  • q — quit; stage the current and all remaining hunks, then stop.
  • a — stage this hunk and all remaining hunks in this file.
  • d — do not stage this hunk or any remaining hunks in this file.
  • e — edit the hunk manually; Git opens an editor with the hunk and lets you remove lines so only the parts you want are staged.
  • s — split the current hunk into smaller hunks (useful when Git’s default split groups two unrelated changes).
  • ? — print the full help text.
flowchart TB
    A["git add -p"] --> B["split file into hunks"]
    B --> C{"for each hunk"}
    C -->|y| D["stage hunk"]
    C -->|n| E["leave in working tree"]
    C -->|e| F["edit hunk manually"]
    C -->|s| G["split into smaller hunks"]
    C -->|q| H["quit, stage remainder"]

When hunk-level staging is the right tool

Three patterns make git add -p the obvious choice:

  1. A file holds a real fix and a drive-by cleanup. The real change is two lines; the cleanup is a renamed variable and a reordered block. The commit should describe the fix only.
  2. A debug artifact has been left in the file. A print() statement, a console.log, or a temporary terraform { ignore_changes } block was added during debugging and needs to stay in the working tree but not in the commit.
  3. Two features share a file by accident. A new resource block was added to a Terraform file that already had an unrelated fix. The two changes should ship in two commits.

In each case the alternative — staging the whole file — forces multiple unrelated changes into one commit. The downstream cost is a git bisect that lands on an unrelated cleanup, a code review that has to evaluate four changes at once, and a revert that pulls in the unrelated changes along with the bad fix.

When hunk-level staging is overkill

For some files, partial staging is more friction than the change warrants:

  • A new file that has not been committed yet. There is no base to diff against. git add -p will refuse because there is nothing to split. Use plain git add <path>.
  • A file with one logical change. If the entire diff is one hunk and the whole hunk is the change, git add -p is just git add <path> with extra prompts. Skip it.
  • A binary file. Hunks are a text concept. git add -p will refuse or will offer the whole file as a single hunk.
  • A regenerated file. A package-lock.json or a generated Terraform plan file should be committed whole or not at all; partial staging is meaningless.

Driving the editor

The most useful non-y answer is e. When a hunk holds more than one logical change, e opens an editor with the hunk displayed in unified-diff format. The engineer deletes the lines they do not want to stage and saves the file; Git stages whatever remains.

@@ -12,7 +12,9 @@ resource "aws_s3_bucket" "logs" {
   bucket = "prod-logs"
-  acl    = "private"
+  acl    = "log-delivery"
+  print("DEBUG: applied tags")
   tags = {
     Owner = "platform"

Removing the + print(...) line from the editor buffer and saving stages only the acl change. The print line stays in the working tree and can be removed in a follow-up commit. The ? prompt in the hunk header prints a help line that is also a reminder: any line deleted from the editor buffer is not staged; any line left in is staged; lines can be edited in place if the change needs adjustment.

The other useful answer is s. When Git groups two unrelated changes into one hunk because they are close together in the file, s asks Git to re-split with a finer threshold. If the re-split succeeds, the engineer sees two hunks where there was one and can stage each independently.

Reviewing the partial stage

After the interactive session ends, the index holds only the staged hunks and the working tree holds the un-staged hunks plus the originally committed bytes. Both views are inspectable:

git diff --cached            # what is staged
git diff                    # what is still in the working tree
git status                  # both, side by side

The next commit will contain exactly what git diff --cached shows. The un-staged hunks remain in the working tree and will need to be staged (or reverted) before a second commit can describe them.

Production discipline

  1. Default to git add -p for any file edited more than once in a session. The cost of the interactive prompts is small compared to the cost of a commit that ships an unrelated change.
  2. Inspect git diff --cached after every partial stage. The staged hunks are exactly what the next commit will contain. A 30-second review catches a missing hunk before it becomes a missing fix.
  3. Use e for surgical precision. When a hunk groups two changes, e lets you delete the lines you do not want to stage rather than splitting the change across two commits.

Cross-course references

  • Linux for Production Sysadmins - Part XV (DiffAndPatch) covers the unified-diff format that git add -p uses; the hunk header @@ -a,b +c,d @@ is the same format patch -p1 consumes.
  • Ansible for Production Sysadmins - Part XXXVII (RepoArch) treats partial staging as the way to keep unrelated playbook changes in separate commits, which is what makes per-feature pull requests reviewable.
  • Terraform for Production Sysadmins - Part X (Plan) covers the analogous tool for Terraform plans: terraform plan -out followed by terraform apply, where the plan file is the unit of review rather than the staged hunks.

Quiz

Knowledge check · 4 questions

  1. Q1. An engineer has edited `terraform/main.tf` and added a debug `print()` statement that they want to keep in the working tree for further debugging. The rest of the change is a real fix that should ship in the next commit. Which command is most appropriate?

  2. Q2. `git add -p` can split a binary file into hunks for selective staging.

  3. Q3. Inside the interactive `git add -p` prompt, name two answers that let you control exactly which lines of a hunk are staged, and explain the difference between them.

  4. Q4. Use partial staging to extract a real fix from a working tree that also contains a debug artifact, then verify the next commit is clean.

    An engineer is debugging a Terraform plan failure. They have edited `terraform/main.tf` to make a real fix (changing an IAM policy ARN) and have also added `print('DEBUG: tags', local.tags)` to a resource block. The real fix is ready to ship; the `print()` is leftover debugging that must stay in the working tree for further investigation. The engineer wants to commit only the real fix.

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