Build CoreOrdered learning track

Pathspecs and Ignore Matching

Learn Git In Action - Part 066

Menguasai pathspec, ignore matching, exclude rules, tracked vs untracked behavior, dan teknik membatasi operasi Git ke path yang tepat tanpa merusak repository.

13 min read2568 words
PrevNext
Lesson 66126 lesson track24–68 Build Core
#git#version-control#pathspec#gitignore+1 more

Part 066 — Pathspecs and Ignore Matching

Revision syntax menjawab: commit/object mana?

Pathspec menjawab: path mana?

Ignore rules menjawab: untracked path mana yang sebaiknya tidak diperhatikan Git?

Banyak engineer mengira path di Git cuma string biasa:

git add src

Padahal Git punya bahasa path sendiri: pathspec.

Pathspec dipakai oleh banyak command:

git add <pathspec>
git restore <pathspec>
git checkout <tree-ish> -- <pathspec>
git diff -- <pathspec>
git log -- <pathspec>
git grep <pattern> -- <pathspec>
git ls-files <pathspec>

Ignore matching adalah topik berbeda, tapi berinteraksi erat dengan pathspec karena keduanya menentukan apa yang masuk operasi Git.

Dokumentasi resmi Git mendeskripsikan pathspec sebagai pola yang digunakan banyak command untuk membatasi scope operasi ke subset tree atau working tree. Dokumentasi gitignore menjelaskan bahwa .gitignore hanya mengatur file yang intentionally untracked; file yang sudah tracked tidak otomatis diabaikan.


1. Mental Model: Tree, Working Tree, Pathspec, Ignore

Git melihat path dari beberapa layer.

Bedakan:

KonsepMenjawabBerlaku pada
tree pathpath di snapshot commit/treeobject database
working-tree pathfile/directory di filesystemworking directory
pathspecpath mana yang command targetkancommand scope
ignore ruleuntracked path mana yang disembunyikan/diabaikanuntracked file discovery
tracked filefile sudah ada di index/historytidak dipengaruhi .gitignore secara otomatis

.gitignore bukan firewall. .gitignore bukan security boundary. .gitignore bukan cara menghapus file dari history.


2. The -- Separator Is Not Optional Discipline

Ketika command menerima revision dan path, gunakan --.

git log main -- src/app

Artinya:

  • revision: main;
  • pathspec: src/app.

Tanpa --:

git log main src/app

Git harus menebak apakah src/app revision atau path.

Dalam repo kecil mungkin aman. Dalam repo besar dengan branch/tag/path bernama sama, ini bisa salah.

Policy: di runbook, script, dan dokumentasi tim, selalu pakai -- ketika command mencampur revision dan path.

git diff origin/main...HEAD -- src/main/java
git checkout v1.2.0 -- config/default.yml
git log --follow -- src/domain/Case.java

3. Basic Pathspec Matching

Pathspec paling sederhana adalah path literal atau prefix directory.

git status -- src
git diff -- README.md
git add src/main/java
git log -- docs

Dalam banyak command, directory path berarti semua path di bawah directory itu.

git diff -- src

Artinya diff untuk path di bawah src/.

Tapi jangan mengasumsikan semua command memperlakukan path sama persis. Dokumentasi tiap command menentukan apakah path relatif terhadap current directory atau repository root.


4. Pathspec Is Evaluated by Git, Not Just the Shell

Shell glob dan Git pathspec glob bukan hal yang sama.

git add *.java

Jika shell expand *.java, Git menerima daftar file hasil ekspansi shell.

git add '*.java'

Jika quote dipakai, Git menerima pattern dan melakukan pathspec matching sendiri.

Ini berbeda.

Contoh:

# Shell expands only files in current directory, unless shell globstar enabled.
git add *.js

# Git receives pattern and can match according to pathspec rules.
git add '*.js'

Rule praktis:

  • quote pattern dengan *, ?, [, !, :;
  • gunakan :(glob) jika butuh glob semantics eksplisit;
  • untuk script, prefer path list dari git ls-files -z + xargs -0 jika scope rumit.

5. Pathspec Magic

Pathspec yang diawali : dapat memakai magic signature atau magic word.

Contoh:

git grep TODO -- ':(top)src'
git ls-files ':(icase)readme.md'
git add ':(literal)*.md'
git diff -- ':(exclude)vendor/**'

Long form:

:(top,literal)README.md
:(glob)src/**/*.java
:(icase)readme.md
:(exclude)generated/**

Short form juga ada untuk magic tertentu, tapi long form lebih readable untuk handbook dan script.


6. :(top) — Anchor to Repository Root

:(top) membuat pathspec dievaluasi dari root repository, bukan current directory.

git status -- ':(top)README.md'
git grep 'CaseState' -- ':(top)src/main/java'

Ini penting untuk script yang bisa dipanggil dari subdirectory.

Bad script:

git diff -- src/main/java

Jika script dijalankan dari services/billing, path tersebut mungkin tidak ada.

Better:

git diff -- ':(top)src/main/java'

Atau set root eksplisit:

root=$(git rev-parse --show-toplevel)
cd "$root"
git diff -- src/main/java

7. :(literal) — Treat Special Characters Literally

Path dengan karakter khusus bisa disalahartikan sebagai glob/pathspec magic.

git add ':(literal)docs/[draft]*.md'

Artinya: file literal bernama docs/[draft]*.md, bukan pattern.

Use case:

  • generated file dengan bracket;
  • test fixture dengan * pada nama file;
  • migration file dengan karakter aneh;
  • repository lint yang harus aman terhadap semua path.

Dalam automation, jangan membangun command string mentah dari path user. Gunakan NUL-separated path list dan --pathspec-from-file jika perlu.


8. :(icase) — Case-Insensitive Matching

git ls-files ':(icase)readme.md'

Use case:

  • repository lint untuk cross-platform path collision;
  • mencari README, Readme, readme;
  • audit file name normalization.

Hati-hati dengan filesystem case-insensitive seperti default macOS/Windows. Git index bisa membedakan path yang filesystem lokal sulit bedakan.

Audit:

git ls-files | awk '{print tolower($0)}' | sort | uniq -d

Jika ada duplicate lowercase path, tim lint harus menolak karena clone di OS lain bisa rusak/ambigu.


9. :(glob) — Glob Semantics

git ls-files ':(glob)src/**/*.java'
git grep '@Transactional' -- ':(glob)**/*.java'

Gunakan :(glob) saat kamu ingin pattern eksplisit.

Namun untuk banyak workflow, pathspec directory sederhana lebih cepat dan lebih jelas:

git diff -- src/main/java

lebih mudah dibaca daripada:

git diff -- ':(glob)src/main/java/**'

Gunakan glob saat perlu pola lintas directory atau extension filter.


10. :(attr:...) — Match by Git Attributes

Pathspec bisa memilih path berdasarkan attribute.

Misal .gitattributes:

*.pb.go generated
*.snap snapshot
*.md text

Query:

git ls-files ':(attr:generated)'
git diff -- ':(attr:snapshot)'

Use case advanced:

  • exclude generated files dari custom review report;
  • audit file yang punya custom merge driver;
  • pilih snapshot/golden files;
  • enforce generated-file policy.

Contoh:

# File generated yang berubah dalam branch ini
git diff --name-only origin/main...HEAD -- ':(attr:generated)'

Ini lebih robust daripada hardcode generated/ jika generated files tersebar.


11. Exclude Pathspec: :(exclude) / :!

Exclude pathspec menghapus path dari match set.

git diff -- . ':(exclude)vendor/**'

Short form sering ditemui:

git diff -- . ':!vendor/**'

Contoh review:

# Diff branch tanpa generated files
git diff origin/main...HEAD -- . ':(exclude)generated/**' ':(exclude)*.lock'

Hati-hati: exclude pathspec bekerja sebagai bagian dari command pathspec matching, bukan .gitignore.

Jika hanya ada exclude tanpa include, Git punya aturan khusus; lebih jelas sertakan include . atau :(top).

# More explicit
git grep TODO -- . ':(exclude)vendor/**'

12. --pathspec-from-file and NUL Safety

Untuk path banyak atau path dengan karakter aneh, jangan inject path ke command line.

Gunakan:

git add --pathspec-from-file=paths.txt

Untuk NUL-separated:

git add --pathspec-from-file=paths.txt --pathspec-file-nul

Generate dari command Git:

git diff --name-only -z origin/main...HEAD > /tmp/changed-paths.z

git add --pathspec-from-file=/tmp/changed-paths.z --pathspec-file-nul

Ini penting untuk repo enterprise karena path bisa mengandung spasi, newline, quote, bracket, unicode, dan karakter shell-sensitive.

Policy automation: gunakan NUL-separated pipeline untuk path list yang berasal dari repository content.


13. Pathspec vs .gitignore

Pathspec membatasi command.

.gitignore memengaruhi untracked file discovery.

Contoh:

build/
.env
git status

Git tidak menampilkan build/ dan .env sebagai untracked.

Tapi jika file sudah tracked:

git add .env
git commit -m "accidentally track env"

Lalu kamu menambah .env ke .gitignore, file itu tetap tracked.

git status
# perubahan .env tetap muncul jika file berubah

Untuk berhenti track file tapi tetap simpan lokal:

git rm --cached .env
git commit -m "Stop tracking local env file"

Untuk secret: jangan berhenti di sini. Secret yang pernah commit harus dirotasi dan history exposure harus ditangani.


14. Ignore Rule Sources and Precedence

Git membaca ignore pattern dari beberapa sumber.

Umumnya:

  1. command-line exclude pattern pada command tertentu;
  2. .gitignore di directory terkait;
  3. $GIT_DIR/info/exclude untuk local repo exclude;
  4. global excludes file dari config core.excludesFile.

Dalam satu level precedence, pattern terakhir yang cocok biasanya menang.

Practical usage:

SourceCommit ke repo?Use case
.gitignoreyesbuild artifacts, generated outputs common to project
.git/info/excludenolocal-only ignore untuk satu clone
global excludesnoOS/editor personal noise seperti .DS_Store

Jangan masukkan preference pribadi semua orang ke .gitignore repository.

Better:

git config --global core.excludesFile ~/.config/git/ignore

Isi global ignore:

.DS_Store
.idea/
.vscode/
*.swp

Kecuali project memang menetapkan IDE config sebagai bagian repository policy.


15. Gitignore Pattern Semantics

Contoh .gitignore:

# Ignore all log files
*.log

# Ignore build directory at any level
build/

# Ignore only root-level dist
/dist/

# Do not ignore this specific file
!important.log

Rules penting:

PatternMeaning
*.logmatch file dengan extension .log di berbagai directory
build/match directory bernama build
/dist/match dist di root .gitignore tersebut
docs/*.htmlmatch file langsung di docs, bukan recursive deep kecuali pattern pakai **
!filenegation: unignore jika parent directory tidak sudah excluded secara blocking

16. Negation Trap: You Cannot Re-include Inside Ignored Directory Easily

Misal:

build/
!build/keep.txt

Sering tidak bekerja seperti yang diharapkan, karena directory build/ sudah di-ignore sehingga Git tidak descend untuk melihat keep.txt.

Better:

build/*
!build/keep.txt

Jika perlu re-include nested path:

build/*
!build/keep/
!build/keep/**

Mental model:

Jika parent directory sendiri di-ignore penuh, Git mungkin tidak mengevaluasi child negation seperti yang kamu bayangkan.


17. Debugging Ignore: git check-ignore

Command utama:

git check-ignore -v path/to/file

Output menunjukkan file ignore mana dan line mana yang match.

Contoh:

git check-ignore -v .env
# .gitignore:12:.env .env

Untuk banyak file:

git check-ignore -v --stdin < paths.txt

Jika file tracked, ignore tidak berlaku untuk status tracking biasa. Pakai:

git ls-files --error-unmatch .env

Jika command exit 0, file tracked.


18. Tracked vs Untracked vs Ignored

Commands:

# tracked files
git ls-files

# untracked visible files
git ls-files --others --exclude-standard

# ignored files
git ls-files --others --ignored --exclude-standard

# tracked ignored-looking files
git ls-files -ci --exclude-standard

git ls-files -ci --exclude-standard berguna untuk menemukan file tracked yang sekarang match ignore rules.


19. assume-unchanged vs skip-worktree

Dua flag ini sering disalahgunakan untuk “ignore tracked file”.

19.1 assume-unchanged

git update-index --assume-unchanged path

Tujuannya performance hint: Git boleh mengasumsikan file tidak berubah.

Bukan mekanisme config lokal.

Unset:

git update-index --no-assume-unchanged path

List:

git ls-files -v | grep '^h'

19.2 skip-worktree

git update-index --skip-worktree path

Dipakai dalam sparse checkout dan kasus advanced agar Git menghindari update working tree path tertentu.

Unset:

git update-index --no-skip-worktree path

List:

git ls-files -v | grep '^S'

Policy: jangan jadikan dua flag ini sebagai workflow umum untuk file config lokal. Lebih baik:

  • commit template file: .env.example;
  • ignore real local file: .env;
  • gunakan config injection/env vars;
  • gunakan secret manager;
  • dokumentasikan bootstrap.

20. Pathspec in Partial Commit Workflow

Pathspec bisa membatasi staging.

git add -- src/domain src/application

Tapi pathspec-level staging masih kasar jika perubahan dalam file bercampur. Untuk hunk-level, gunakan:

git add -p -- src/domain/CaseService.java

Combine:

git diff -- src/domain
git add -p -- src/domain
git commit -m "Enforce case escalation invariant"

Failure mode:

git add src

Tanpa sadar ikut men-stage generated file, lockfile, atau local config di bawah src.

Safer:

git diff --name-only
 git diff --cached --name-only

Before commit:

git diff --cached --stat
git diff --cached --check
git diff --cached

21. Pathspec in Restore and Checkout

Restore selected paths from index or commit.

# Discard working tree change for path
git restore -- src/domain/Case.java

# Unstage path
git restore --staged -- src/domain/Case.java

# Restore path from old commit
git restore --source=v1.2.0 -- src/config.yml

Older syntax:

git checkout v1.2.0 -- src/config.yml

The -- matters.

Without it, Git may interpret argument as branch/ref.


22. Pathspec in Log: History Simplification

git log -- src/domain/Case.java

This does not simply filter already-listed commits. Git performs path-limited history traversal and may simplify history.

For rename:

git log --follow -- src/domain/Case.java

Limitations:

  • --follow works for one path at a time;
  • rename detection is heuristic;
  • large refactor can confuse archaeology;
  • path-limited log may hide merge context.

For forensic work:

git log --full-history --simplify-merges -- src/domain/Case.java

And compare with:

git log --graph --oneline --all -- src/domain/Case.java

23. Pathspec in Diff: Review Scoping

Examples:

# Diff only API contracts
git diff origin/main...HEAD -- ':(top)api'

# Diff Java code, exclude generated
git diff origin/main...HEAD -- ':(glob)**/*.java' ':(exclude)**/generated/**'

# Diff everything except lockfiles
git diff origin/main...HEAD -- . ':(exclude)*.lock'

But be careful: excluding paths in review can hide important coupling.

Example:

git diff origin/main...HEAD -- . ':(exclude)package-lock.json'

This may hide dependency drift.

Use exclude pathspec for focused inspection, not as replacement for full review.


24. Pathspec in Grep

git grep 'TODO' -- src
git grep 'password' -- ':(top)src' ':(exclude)src/test/fixtures/**'
git grep -n 'CaseStatus' -- ':(glob)**/*.java'

Search at old revision:

git grep 'legacyFlag' v1.2.0 -- src

This searches tree snapshot at v1.2.0, not working tree.

Useful for:

  • checking if config existed in old release;
  • auditing dangerous API usage;
  • finding migration boundary;
  • comparing codebase state across release tags.

25. Pathspec in ls-files: The Truth About Index

git ls-files is one of the best debugging tools for path state.

# tracked files matching pathspec
git ls-files -- src

# untracked files respecting ignore rules
git ls-files --others --exclude-standard -- src

# ignored files
git ls-files --others --ignored --exclude-standard -- src

# staged conflict entries
git ls-files -u -- src

# stage and flags
git ls-files -s -- src

If git status feels confusing, inspect with git ls-files.


26. Generated Files and Path Boundaries

Generated files create review noise and merge conflict risk.

Options:

TypeRecommended treatment
build outputignore, never commit
generated source needed for buildcommit only if build toolchain requires it
generated API clienteither commit with clear ownership or publish artifact/package
snapshots/golden filescommit, but tag via .gitattributes if possible
lockfilescommit for apps; policy depends for libraries

Use .gitattributes for classification:

*.snap snapshot
src/generated/** generated

Then query:

git diff --name-only origin/main...HEAD -- ':(attr:generated)'

Review policy:

  • generated-only PR can be auto-routed differently;
  • generated + manual change in same PR needs stronger scrutiny;
  • generated file should include source-of-truth pointer.

27. Secret Files and Ignore Rules

.gitignore helps prevent accidental add of untracked local secret files.

It does not protect against:

  • git add -f secret.env;
  • secret already tracked;
  • secret committed before ignore rule;
  • secret in reflog, stash, packfile, remote clone;
  • secret in CI logs or artifacts.

Recommended pattern:

.env
.env.*
!.env.example

But also use:

  • pre-commit secret scanning;
  • server-side/pre-receive checks;
  • CI secret scanning;
  • secret manager;
  • rotation playbook.

If secret leaked, rotate first. History rewrite is secondary containment, not primary remediation.


28. Sparse Checkout Interaction

Sparse checkout uses path patterns and index flags to restrict working tree materialization.

This interacts with pathspec because a path may exist in repository tree but not in working tree.

Example:

git sparse-checkout set services/billing

A file outside sparse cone may be absent locally, but still tracked in repository.

Commands that operate on object database can still inspect it:

git show HEAD:services/case/src/Case.java

Commands that operate on working tree may not see it normally.

Policy for large repo tooling:

  • distinguish tree queries from working tree queries;
  • use git ls-tree/git cat-file for repository-wide analysis;
  • use sparse-aware commands where possible;
  • do not assume missing working tree path means missing repository path.

29. Case Sensitivity and Unicode Path Risk

Git stores path bytes. Filesystems may impose case folding or Unicode normalization.

Risk examples:

src/User.java
src/user.java

Works on case-sensitive filesystem, problematic on case-insensitive filesystem.

Audit:

git ls-files | awk '{print tolower($0)}' | sort | uniq -d

Unicode normalization issue is harder. Enforce path naming policy:

  • ASCII for source file paths where possible;
  • lowercase/kebab/snake convention for directories;
  • no trailing spaces;
  • no control characters;
  • no newline in filenames;
  • no paths differing only by case.

For platform teams, add a repository lint.


30. Line Endings, Attributes, and Path Matching

.gitattributes is separate from .gitignore.

It controls attributes like:

*.sh text eol=lf
*.bat text eol=crlf
*.png binary
*.lock -diff

This affects normalization, diff drivers, merge drivers, and path attribute queries.

Pathspec can use attributes:

git ls-files ':(attr:binary)'

But attributes only work if attributes are set appropriately.

Do not encode everything in path names if attributes better capture semantics.


31. Operational Playbook: “Why Is This File Not Showing Up?”

Symptoms:

git status
# file not shown

Checklist:

Step 1 — Does file exist?

ls -la path/to/file

Step 2 — Is it tracked?

git ls-files --error-unmatch path/to/file

If tracked, .gitignore is not hiding it.

Step 3 — Is it ignored?

git check-ignore -v path/to/file

Step 4 — Is it outside sparse checkout?

git sparse-checkout list

Step 5 — Does pathspec exclude it?

Check command scope:

git status -- path/to/parent
git status -- . ':(exclude)path/to/file'

Step 6 — Is file mode/case issue involved?

git config core.ignorecase
git ls-files | grep -i 'file-name'

32. Operational Playbook: “Why Is This Ignored File Still Tracked?”

Because ignore rules do not apply to tracked files.

Diagnose:

git ls-files --error-unmatch path/to/file
git check-ignore -v path/to/file

If both are true, file is tracked and matches ignore rule.

To stop tracking:

git rm --cached path/to/file
git commit -m "Stop tracking local-only file"

If file contains secret:

  1. rotate secret;
  2. identify exposure;
  3. decide history rewrite necessity;
  4. notify consumers;
  5. add prevention controls.

33. Operational Playbook: “Pathspec Did Not Match Any Files”

Error:

error: pathspec 'foo' did not match any file(s) known to git

Possible causes:

  • file is untracked but command expects tracked path;
  • typo/case mismatch;
  • path relative to wrong directory;
  • sparse checkout hides path in working tree;
  • shell expanded glob unexpectedly;
  • branch/tag/path ambiguity;
  • path exists in another revision but not current index.

Debug:

pwd
git rev-parse --show-toplevel
git ls-files -- ':(top)foo'
git ls-tree -r --name-only HEAD | grep -F 'foo'
git status --ignored -- ':(top)foo'

If path exists in old revision:

git ls-tree -r --name-only v1.2.0 | grep -F 'foo'

34. Automation Patterns

34.1 Changed Java Files in PR

base=$(git merge-base origin/main HEAD)
git diff --name-only -z "$base" HEAD -- ':(glob)**/*.java' \
  | xargs -0 -r ./lint-java.sh

34.2 Changed Non-Generated Source Files

git diff --name-only origin/main...HEAD -- \
  ':(top).' \
  ':(exclude)generated/**' \
  ':(exclude)**/*.snap'

34.3 Files That Are Tracked But Should Be Ignored

git ls-files -ci --exclude-standard

34.4 Ignored Files Taking Disk Space

git ls-files --others --ignored --exclude-standard -z \
  | xargs -0 du -sh 2>/dev/null \
  | sort -h

34.5 Audit Dangerous Filenames

# newline in filename
git ls-files -z | tr '\0' '\n' | grep -n $'\n'

# case collision approximation
git ls-files | awk '{print tolower($0)}' | sort | uniq -d

The newline check above is intentionally simplistic; robust tooling should process -z data directly without converting to newline.


35. Repository Policy Template

Use something like this in an engineering handbook:

## Path and Ignore Policy

1. All commands in scripts that mix revisions and paths must use `--`.
2. Repository scripts must be runnable from any subdirectory or anchor to repo root.
3. Shell globs in Git commands must be quoted unless shell expansion is intended.
4. Automation processing file paths must use NUL-separated output/input where available.
5. `.gitignore` is for shared project artifacts, not personal preferences.
6. Personal OS/editor noise belongs in global excludes.
7. Local-only repository excludes belong in `.git/info/exclude`.
8. Tracked files are not made untracked by adding `.gitignore` rules.
9. Secrets must never rely on `.gitignore` as the only control.
10. Repository must reject paths differing only by case.
11. Generated files must be classified either by path or `.gitattributes`.
12. Large generated/build artifacts must not be committed unless explicitly approved.

36. Common Anti-Patterns

36.1 git add . Without Inspection

Bad:

git add .
git commit -m "update"

Better:

git status --short
git diff
git add -p
git diff --cached
git commit

36.2 Putting Personal IDE Rules in Project .gitignore

Bad if every engineer adds their editor noise.

Better:

  • project .gitignore: project artifacts;
  • global ignore: personal OS/editor artifacts.

36.3 Using Ignore Rules for Environment Strategy

Bad:

config/application.yml

Because the real issue is configuration design.

Better:

config/application.example.yml
config/application.local.yml ignored
runtime config injected from env/secret manager

36.4 Excluding Lockfiles From Review Always

Bad:

git diff origin/main...HEAD -- . ':(exclude)package-lock.json'

Lockfiles often are supply-chain evidence. Exclude only for focused temporary inspection.

36.5 Assuming .gitignore Removes Secrets From History

It does not.


37. Mini Lab: Pathspec Sandbox

Create repository:

mkdir pathspec-lab
cd pathspec-lab
git init
mkdir -p src/main src/generated docs build
printf 'hello\n' > README.md
printf 'class App {}\n' > src/main/App.java
printf 'generated\n' > src/generated/Client.java
printf 'draft\n' > 'docs/[draft]*.md'
printf 'tmp\n' > build/output.txt
cat > .gitignore <<'EOF'
build/
*.log
EOF
cat > .gitattributes <<'EOF'
src/generated/** generated
*.md text
EOF
git add README.md src docs .gitignore .gitattributes
git commit -m "Initial pathspec lab"

Try:

git ls-files ':(top)src'
git ls-files ':(glob)**/*.java'
git ls-files ':(attr:generated)'
git ls-files ':(literal)docs/[draft]*.md'
git status --ignored
git check-ignore -v build/output.txt

Modify:

printf 'change\n' >> src/main/App.java
printf 'change\n' >> src/generated/Client.java
printf 'secret\n' > .env

Try:

git diff -- ':(glob)**/*.java'
git diff -- . ':(exclude)src/generated/**'
git diff -- ':(attr:generated)'
git status --ignored

38. Mini Lab: Tracked Ignored File

printf 'SECRET=bad\n' > .env
git add .env
git commit -m "Accidentally track env"

echo '.env' >> .gitignore
git add .gitignore
git commit -m "Ignore env"

printf 'SECRET=changed\n' > .env
git status --short

You will still see .env modified because it is tracked.

Stop tracking:

git rm --cached .env
git commit -m "Stop tracking env"

Then:

git status --ignored --short

Lesson:

Ignore rules affect untracked discovery. They do not erase tracking state.


39. Mini Lab: Safe Path Automation

Create weird filenames:

mkdir weird
printf 'x\n' > 'weird/name with space.txt'
printf 'x\n' > 'weird/[bracket].txt'
printf 'x\n' > 'weird/star*.txt'
git add weird
git commit -m "Add weird filenames"

Now compare unsafe and safe:

# May be surprising depending on shell
git ls-files weird/*.txt

# Git receives pattern
git ls-files 'weird/*.txt'

# Literal
git ls-files ':(literal)weird/star*.txt'

# NUL-safe
git ls-files -z weird | xargs -0 -n1 printf '[%s]\n'

Lesson:

The shell is not Git. Quote intentionally.


40. Summary

Pathspec is Git's path selection language.

It lets you scope commands precisely:

  • from repo root with :(top);
  • literally with :(literal);
  • case-insensitively with :(icase);
  • by glob with :(glob);
  • by attributes with :(attr:...);
  • by exclusion with :(exclude) or :!;
  • safely from files with --pathspec-from-file.

Ignore matching is not the same thing.

.gitignore controls intentionally untracked files. It does not untrack files, erase history, secure secrets, or replace configuration architecture.

The high-level invariant:

Revision syntax selects where in history. Pathspec selects where in the tree. Ignore rules affect what untracked working-tree noise Git notices.

In the next part, we move from path selection to path behavior: .gitattributes, filters, text normalization, diff drivers, merge drivers, and how repository policy can change how Git interprets file content.

Lesson Recap

You just completed lesson 66 in build core. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.