Using .gitignore - Python

  1. Which action best completes this change?
    Answer: Run git rm -r --cached .venv build, and then commit the removals and .gitignore. When working with Git, it's important to distinguish between three separate "layers": the working directory (your local files), the index/staging area (what Git tracks), and the committed history. Adding a path to .gitignore only prevents future untracked files from being added — it does nothing to files Git is already tracking. To stop tracking already-committed files, you must explicitly remove them from the index. The correct approach is C: running git rm -r --cached .venv build removes those directories from Git's index (stopping tracking) while leaving the actual files untouched in your local working directory. The --cached flag is the critical detail here — it tells Git to only remove from the index, not from disk. After that, committing both the removals and the updated .gitignore ensures teammates pulling the change also stop tracking those files, and the .gitignore prevents accidental re-addition. A is a common misconception — updating .gitignore alone has no effect on files already being tracked. Git simply continues monitoring them as before. B uses git reset --hard, which is destructive: it resets both the index and the working directory to the last commit, potentially wiping local changes — and still doesn't remove the files from tracking. D is dangerously close to correct but omits --cached, meaning git rm -r would delete the actual files from disk in addition to the index, violating the requirement to retain local copies. A useful mental shortcut: whenever you need to "untrack without delete," think --cached. That flag is your safety net for preserving local files while cleaning up Git's index.
  2. Which file remains eligible to be added normally with git add?
    Answer: packages/dist/notes.txt When reading .gitignore patterns, the most important skill is understanding anchoring — whether a pattern applies globally throughout the repository or only from the root. A leading slash (/) anchors the pattern to the repository root, while no leading slash means the pattern can match anywhere in the directory tree. The rule /dist/ starts with a slash, so it only blocks a dist/ folder directly at the root level. The rule *.whl has no leading slash, so it matches any .whl file anywhere in the repo. Choice A, packages/dist/notes.txt, is the correct answer. The folder here is packages/dist/, not the root-level /dist/, so the anchored /dist/ pattern does not apply. The file is also a .txt, not a .whl, so neither rule blocks it — it remains fully eligible for git add. Choice C, dist/release-notes.txt, looks similar but is actually inside the root-level dist/ folder, which is exactly what /dist/ targets. Despite being a .txt file, it's blocked by the directory rule. Choice D, wheels/cache.whl, is ignored because *.whl matches any .whl file regardless of location — the wheels/ subdirectory provides no protection. Choice B, packages/dist/app.whl, is a trap that combines both rules: it's in a non-root dist/ folder (so /dist/ doesn't apply), but it is a .whl file, which *.whl catches globally. As a study tip, always ask two questions about each .gitignore pattern: Is it anchored with a leading slash? and Is the pattern file-based or directory-based? Keeping those two dimensions separate will prevent most matching mistakes.
  3. Which ordered pair of .gitignore rules meets this requirement?
    Answer: artifacts/* followed by !artifacts/README.md When working with .gitignore, you need to understand two critical rules: order matters (later rules override earlier ones), and a negation rule cannot un-ignore a file if its parent directory is ignored entirely. That second point is the key to this question. Option B is correct because artifacts/* ignores the contents of the directory without ignoring the directory itself as a whole. This distinction is crucial — it leaves the door open for a negation rule. The following !artifacts/README.md then explicitly un-ignores that specific file, so Git tracks it while ignoring everything else inside artifacts/. This combination works exactly as intended. Option A fails because of that parent-directory trap. Writing artifacts/ ignores the entire directory as a unit. Git won't even look inside it, so !artifacts/README.md has no effect — you simply cannot negate files inside a fully-ignored directory. Option C reverses the order, putting the negation rule before the ignore rule. Since .gitignore processes rules top-to-bottom and later rules win, artifacts/* at the end would override the !artifacts/README.md above it, ignoring everything including README.md. Option D uses !artifacts/*.md instead of !artifacts/README.md. While it would un-ignore README.md, it would also un-ignore any other .md file in artifacts/, which is broader than the requirement. It doesn't precisely meet the goal. A useful rule of thumb: when you need to "commit one file inside an ignored folder," always prefer folder/* over folder/ — the slash-star pattern ignores contents, not the container, keeping negations possible.
  4. Which root .gitignore entries satisfy both requirements?
    Answer: /build/ and .venv/ When working with .gitignore patterns, the critical distinction is between anchored and unanchored patterns. A leading slash (/) anchors a pattern to the repository root, meaning it only matches at that specific level. Without a leading slash, the pattern matches at any depth in the directory tree. The scenario has two competing requirements: ignore the root-level build/ directory but preserve examples/build/, and ignore .venv/ directories that may appear anywhere in the project. Option B — /build/ and .venv/ — satisfies both. The leading slash in /build/ anchors it to the root, so examples/build/ remains tracked. Meanwhile, .venv/ without a leading slash matches virtual environments at any depth, exactly what you need. Option A fails because build/ (unanchored) matches every build/ directory in the repo, which would incorrectly ignore examples/build/ — destroying those committed teaching fixtures. Option C fails on the second pattern: /.venv/ is anchored to the root, so it would only ignore a .venv/ placed directly at the repo root. Any .venv/ created in a subdirectory would slip through unignored. Option D fails because **/build/ explicitly matches build/ at every depth (the ** glob means "zero or more directories"), making it even more aggressive than the unanchored build/ in A — definitely wiping out examples/build/. A useful rule of thumb: leading slash = root only, no prefix = everywhere. When a question describes directories that need different treatment at different depths, that's your signal to think carefully about anchoring.
  5. Which .gitignore configuration most precisely excludes the generated packaging artifacts?
    Answer: /build/, /dist/, and *.egg-info/ When working with .gitignore, precision matters — you want to exclude exactly the generated files without accidentally ignoring source files you need to keep. The goal here is to exclude three specific artifacts: a root-level build/ directory, a root-level dist/ directory, and any *.egg-info/ directory anywhere in the project. Option A accomplishes this perfectly. The leading / in /build/ and /dist/ anchors the pattern to the repository root, so only those specific top-level directories are ignored — not any nested build/ or dist/ folder that might legitimately exist elsewhere. The *.egg-info/ pattern (without a leading slash) correctly matches the generated src/acme_tools.egg-info/ directory wherever it appears, using the wildcard to catch any package name. Option B is dangerous because build and dist without a trailing slash or leading slash will match files and directories with those names anywhere in the project, and src/ would ignore the entire source directory — wiping out your tracked src/acme_tools/__init__.py entirely. Option C uses /build/* and /dist/*, which ignores the contents of those directories but not the directories themselves, leaving them partially tracked. Worse, *.py would ignore all Python files, destroying your source code tracking. Option D targets nonexistent files like build.py and dist.py, and .egg-info without a wildcard or trailing slash won't correctly match the acme_tools.egg-info/ directory pattern. A good study tip: always remember that a leading / anchors to the root, a trailing / restricts matches to directories only, and wildcards like * expand the pattern — knowing these three modifiers lets you write surgical .gitignore rules.
  6. Which configuration best separates project-wide and personal ignore requirements?
    Answer: Commit project artifact patterns in .gitignore and place editor patterns in the developer's global ignore file. When working with Git, you need to understand that not all ignored files belong in the same place. Git provides two distinct mechanisms for ignoring files: the committed .gitignore file (shared across the whole team) and the global gitignore file configured per-developer (typically at ~/.gitignore_global). The key question to ask is: who needs this rule? Answer B is correct because it cleanly separates ownership. Project artifacts like .venv/, build/, and dist/ are relevant to every contributor, so those patterns belong in .gitignore, which gets committed to the repository and automatically applies to everyone who clones it. The developer's personal editor backup files are a private preference — no one else on the team uses that editor — so those patterns belong in that developer's global ignore file, keeping the repository clean and unopinionated about personal tooling. Answer A fails because a global ignore file only applies to one machine. Other contributors would have no protection against accidentally committing the project artifacts, since nothing is committed to the repo. Answer C commits editor-specific patterns into .gitignore, which forces personal tool preferences onto every contributor — even those who don't use that editor. This pollutes the shared configuration with irrelevant noise and is considered bad practice. Answer D reverses the logic entirely: .git/info/exclude is a local, untracked file similar to a global ignore, so placing project-wide patterns there means teammates won't benefit from them. A useful rule of thumb: if the pattern matters to the project, commit it in .gitignore; if it matters only to you, keep it in your global ignore file.
  7. Which .gitignore rules ignore the virtual environments without excluding config/env/?
    Answer: .venv/ and venv/ When working with .gitignore patterns, you need to understand how Git matches directory names and how pattern specificity affects what gets ignored. The goal here is to ignore .venv/ and venv/ (virtual environment folders that appear at the project root) while keeping config/env/ tracked. Option D — using .venv/ and venv/ — achieves exactly this. These patterns match only directories named literally .venv or venv anywhere in the repository, and since neither pattern contains env as a standalone name, config/env/ remains unaffected. Here's why the other options fail. Option A includes env/ as a pattern, which would match config/env/ because Git checks every directory segment — config/env/ contains a folder named env, so it would be excluded, breaking your deployment config. Option B uses the wildcard *env*/, which matches any directory whose name contains "env" — this catches .venv/ and venv/, but also snags config/env/ for the same reason, making it far too broad. Option C uses the leading slash (/.venv/ and /venv/), which anchors the pattern to the repository root only. While this seems precise, the real virtual environment folders are at the root, so it would technically work — but the issue is that venv/ without a leading slash already works correctly, and the leading-slash syntax is a common distractor that students confuse with "more specific." As a study tip, remember that a leading / in .gitignore anchors the match to the root, while a bare name/ matches that directory name anywhere in the tree. Always check whether a pattern is too broad (wildcard) or accidentally matches tracked paths before using it.
  8. Assuming no other ignore rules apply, which directories are ignored by that rule?
    Answer: backend/build/ and backend/subproject/build/, but not either directory outside backend/. When working with .gitignore files, the key concept to understand is scope and anchoring. A .gitignore file only applies rules within the directory tree where it lives — not the entire repository. However, within that subtree, an unanchored pattern (one without a leading /) matches recursively at any depth. The backend/.gitignore file contains build/, which has no leading slash, making it an unanchored pattern. This means Git applies it to every directory named build/ found anywhere inside backend/. That includes backend/build/ (directly inside) and backend/subproject/build/ (nested deeper). This is exactly what answer C describes, making it correct. Answer A is wrong because it assumes the pattern only matches one level deep — directly beside the .gitignore file. An unanchored pattern without a leading / is not restricted to a single directory level; it matches recursively throughout the subtree. Answer B is wrong because it confuses a subdirectory .gitignore with the root .gitignore. Only the root-level .gitignore (or global config) applies repository-wide. The backend/.gitignore has no authority over build/ or frontend/build/, which exist outside the backend/ tree. Answer D is wrong because Git has no concept of "nested output taking precedence." This describes a rule that simply doesn't exist — it's a fabricated distractor. A useful mental model: think of each .gitignore as casting a net that drops downward from its location, catching all matches below it, but never reaching sideways or upward into sibling or parent directories.
  9. What should the developer expect when a colleague makes a fresh clone?
    Answer: The clone omits build/result.bin; the colleague must regenerate the build output locally. When working with Git, it's essential to understand what .gitignore actually does: it tells Git to ignore untracked files, meaning they are never added to the repository's history. They exist only in the local working tree of whoever generated them. In this scenario, build/result.bin was never tracked — git status confirms this by showing no changes, because Git is silently ignoring it per the .gitignore rule. Since the file was never committed, it has no presence in the repository's object store. When a colleague clones the repo, Git reconstructs the working tree entirely from committed history. Because build/result.bin was never part of that history, the clone simply won't include it. The colleague will need to run the build process themselves to regenerate it — making C the correct answer. A describes a fundamentally wrong model of Git. Ignored files are not stored in any hidden or silent layer of the repo; they are completely outside Git's awareness. B is also false — Git does not police your filesystem. Ignored files can exist locally indefinitely; Git just won't track, stage, or commit them. D invents a behavior that doesn't exist. Git doesn't track empty directory names or "ignored directory names" — in fact, Git doesn't track empty directories at all without a placeholder file like .gitkeep. A useful mental model: think of .gitignore as a filter that keeps certain files out of Git entirely. If it's out of Git, it's out of every clone. This rule applies to any question asking what gets transferred when cloning or pushing.
  10. What is the ignore status of dist/app.whl and tests/fixtures/sample.whl?
    Answer: The distribution wheel is ignored, but the fixture wheel is re-included by the later rule. When working with .gitignore, the critical concept is rule ordering with negation: Git reads rules top-to-bottom, and the last matching rule wins — not the first. Negation rules (prefixed with !) re-include files that were previously excluded, but only if they actually match the file's path. Here's how the two rules play out. The pattern *.whl matches any file ending in .whl anywhere in the repository. So both dist/app.whl and tests/fixtures/sample.whl are initially ignored by this rule. Then Git encounters !tests/fixtures/*.whl, which explicitly un-ignores .whl files inside tests/fixtures/. For tests/fixtures/sample.whl, this second rule matches — and since it comes later, it overrides the first, re-including the file. For dist/app.whl, the negation rule doesn't match at all, so the file stays ignored. Answer D correctly captures this: the distribution wheel is ignored, and the fixture wheel is re-included. Answer A is wrong because it describes the opposite of how Git works — it's the last matching rule, not the first, that determines a file's status. Answer B misunderstands negation scope: !tests/fixtures/*.whl only cancels the ignore for that specific directory, not globally for all .whl files. Answer C has the logic backwards — the fixture ends up un-ignored (re-included), not ignored; the distribution wheel is the one that stays ignored. As a study tip: whenever you see .gitignore negation rules, mentally simulate each file path against the rules in sequence, and remember that last match wins. This is the most common source of confusion in gitignore questions.

Start this quiz