Read .graphifyignore files and return (anchor_dir, pattern) pairs. Patterns are returned outer-first so that inner (closer) rules are appended last and win via last-match-wins semantics — matching gitignore behavior exactly. Walk ceiling: the nearest VCS root if inside a repo, othe
(root: Path)
| 750 | |
| 751 | |
| 752 | def _load_graphifyignore(root: Path) -> list[tuple[Path, str]]: |
| 753 | """Read .graphifyignore files and return (anchor_dir, pattern) pairs. |
| 754 | |
| 755 | Patterns are returned outer-first so that inner (closer) rules are |
| 756 | appended last and win via last-match-wins semantics — matching gitignore |
| 757 | behavior exactly. |
| 758 | |
| 759 | Walk ceiling: the nearest VCS root if inside a repo, otherwise the scan |
| 760 | root itself (hermetic — no leakage across unrelated sibling projects). |
| 761 | """ |
| 762 | root = root.resolve() |
| 763 | ceiling = _find_vcs_root(root) or root |
| 764 | |
| 765 | # Collect ancestor dirs from ceiling down to root (outer → inner) |
| 766 | dirs: list[Path] = [] |
| 767 | current = root |
| 768 | while True: |
| 769 | dirs.append(current) |
| 770 | if current == ceiling: |
| 771 | break |
| 772 | current = current.parent |
| 773 | dirs.reverse() # ceiling first, scan root last |
| 774 | |
| 775 | patterns: list[tuple[Path, str]] = [] |
| 776 | for d in dirs: |
| 777 | # Merge .gitignore and .graphifyignore for this dir (#1363). Previously |
| 778 | # the presence of a .graphifyignore made graphify skip that dir's |
| 779 | # .gitignore entirely, so a file excluded only by .gitignore (e.g. a |
| 780 | # neutrally-named secret like prod-dump.sql) silently got indexed into |
| 781 | # the graph — whose artifacts embed file contents and are often |
| 782 | # committed. .gitignore is read first and .graphifyignore last, so |
| 783 | # .graphifyignore patterns (including `!` negations) win on conflict via |
| 784 | # last-match-wins; adding a .graphifyignore can only ever exclude MORE, |
| 785 | # never re-include a .gitignore-excluded file (#945 kept: a project with |
| 786 | # only a .gitignore still gets sensible defaults). |
| 787 | for fname in (".gitignore", ".graphifyignore"): |
| 788 | ignore_file = d / fname |
| 789 | if ignore_file.exists(): |
| 790 | for raw in ignore_file.read_text(encoding="utf-8", errors="ignore").splitlines(): |
| 791 | line = _parse_gitignore_line(raw) |
| 792 | if line: |
| 793 | patterns.append((d, line)) |
| 794 | return patterns |
| 795 | |
| 796 | |
| 797 | def _is_ignored( |