skip to content

In a Git .gitignore file, what do a leading slash, a trailing slash, and ** match?

level: middleimportance: must knowfreq 66%

answer

  1. Where a slash appears changes everything
  2. Name matching versus path matching
  3. One wildcard crosses directory boundaries
  4. Trailing slash restricts what kind of entry
  5. Last matching line decides

basics

~20 s

A leading slash anchors a Git ignore pattern to the directory holding the .gitignore instead of matching at any depth. A trailing slash restricts the match to directories. Double asterisk matches across directory separators, which a single asterisk never does.

solid answer

~50 s

Three details decide almost every `.gitignore` question. First, anchoring: a pattern with no slash in it, such as `build`, matches that name at **any** depth below the file; the moment the pattern contains a slash anywhere except at the end, for instance `/build` or `doc/frotz`, it is anchored to the directory containing the `.gitignore`. Second, a trailing slash such as `logs/` restricts the match to directories, so a *file* named `logs` stays visible. Third, wildcards: `*` and `?` never cross a `/`, whereas `**` does, so `**/tmp` matches `tmp` at any depth, `tmp/**` matches everything under it, and `a/**/b` matches `b` under zero or more directories inside `a`. Within one file the **last** matching line wins, which is what makes `!` re-inclusion order-sensitive. Lines beginning with `#` are comments and trailing spaces are stripped unless backslash-escaped.

code

text · 7 lines
text
build
/dist
logs/
*.log
**/node_modules
src/**/*.generated.ts
doc/frotz

go deeper

for a junior

Recall the everyday shapes: *.log for a file type, build/ for a directory, and a leading slash to mean top level only. Knowing that a bare name matches at any depth already puts you ahead.

for a middle

Explain the slash rule as name matching versus path matching, that single asterisk stops at a separator while double asterisk does not, and that the last matching line in a file decides the result.

for a senior

Demonstrate that you verify rather than guess: git check-ignore -v names the file, line and pattern. Show judgment about keeping rules narrow and anchored so they do not silently hide real source files in nested directories.

for a principal

Own the standard: a small anchored root file plus per-directory files near the artifacts they cover, generated paths ignored at their source, and review discipline so an overly broad pattern never becomes the reason a file was missing from a release.

## What a pattern is matched against Each line of a `.gitignore` is a pattern in Git's own glob dialect, close to shell globbing but with its own anchoring rules. Patterns are evaluated relative to the directory containing the file, and they are applied to paths inside that directory tree only. ## Anchoring: the slash rule The single most misread rule is that the presence of a slash changes the match target. - **No slash at all** (`build`, `*.log`): the pattern is matched against the file or directory *name*, at any depth. `build` hides a top-level `build` and also `src/vendor/build`. - **A slash anywhere except the end** (`/build`, `doc/frotz`, `src/*.log`): the pattern becomes a full relative path pattern anchored at the directory of the `.gitignore`. `/build` hides only the top-level one; `doc/frotz` hides only that path, not `a/doc/frotz`. A leading slash is therefore just the cheapest way to say "this exact place, not everywhere". ## Trailing slash: directories only `logs/` matches a directory named `logs` and everything inside it, and never a regular file named `logs`. Dropping the slash makes the pattern match either kind. Directory-only patterns also have a performance consequence: once a directory is excluded, Git does not descend into it, which is why negations inside an excluded directory do not work. ## Wildcards `*` matches any run of characters *except* `/`; `?` matches exactly one character except `/`; bracket expressions like `[0-9]` and `[a-z]` work as in shell globs. `**` is the only construct that crosses separators, and it is meaningful in three positions: - **Leading** `**/foo` matches `foo` at any depth (the same as writing bare `foo`, but explicit). - **Trailing** `foo/**` matches everything inside `foo`, at any depth. - **Middle** `a/**/b` matches `b` directly under `a` and under any number of intermediate directories. ## Ordering: last match wins Git does not stop at the first hit. It applies every pattern in the file and the **last** one that matches decides the outcome. That is why a `!` negation must come after the rule it is undoing, and why appending a broad `*.log` at the bottom silently undoes carefully placed exceptions above it. ## Per-directory layering `.gitignore` files can sit in any directory. For a given path, Git reads the file in the path's own directory and every parent up to the top of the work tree, and files closer to the path win over files further away. A subdirectory can therefore relax or tighten what the root file said, which is how a `vendor/.gitignore` keeps its rules out of everyone else's way. ## Small syntax details that show up in interviews - A line starting with `#` is a comment; to ignore a file whose name really starts with `#`, escape it as `\#`. - Trailing whitespace is stripped unless escaped with a backslash, which is a real cause of "my pattern does nothing" reports. - A leading `!` means negation, so a filename genuinely starting with `!` must be written `\!important.txt`. - Blank lines match nothing and are conventionally used as separators. ## Verifying instead of guessing Rather than reasoning about a pattern in your head, ask Git: `git check-ignore -v <path>` prints the source file, line number and pattern responsible for excluding a path, and exits non-zero when nothing matches. That is the fastest way to settle an argument about anchoring.

  • Why does the pattern /build behave differently from build in a repository root .gitignore?
    `build` has no slash, so Git matches it as a *name* at any depth and hides `build`, `src/build` and `tools/build` alike. `/build` contains a slash, which anchors it to the directory holding the `.gitignore`, so only the top-level `build` is excluded. Anchoring is the usual fix when a broad name pattern swallows unrelated directories deeper in the tree.
  • What is the practical difference between logs/ and logs in a Git ignore file?
    `logs/` matches only a directory of that name, together with everything inside it; a regular file called `logs` remains visible to Git. `logs` without the slash matches either a file or a directory. The trailing-slash form is preferred for build and cache directories because it states the intent and cannot accidentally hide a file with the same name.

saying these in an interview costs you the question

  • Assumes every pattern is anchored to the repo root
  • Thinks a single asterisk matches across directories
  • Believes the first matching pattern wins
  • Says a trailing slash is decorative
  • Writes /build expecting nested build directories to be hidden

context