Skip to content

Commit 4507887

Browse files
committed
fix: collapse managed gitignore blocks
1 parent 7294e4b commit 4507887

4 files changed

Lines changed: 84 additions & 9 deletions

File tree

.gitignore

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,4 @@ memory/handoff-inbox/
6565
.brigade/runs/
6666
.brigade/scrub-cache/
6767
.brigade/work/
68-
.solo-mise/logs/
69-
.solo-mise/scrub-cache/
7068
# <<< brigade gitignore block <<<

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5454
- Handoff write failures now preserve final run artifacts, print the final answer, return nonzero, and mark `run.json` as `handoff-failed`.
5555
- Dogfood runs default to prompt-level read-only plus Codex's `danger-full-access` sandbox setting for trusted-workspace use so repo inspection works on hosts where native read-only sandboxing blocks shell inspection; `--native-read-only-sandbox` opts into stricter native enforcement.
5656

57+
### Fixed
58+
- `brigade init` now collapses mixed current and legacy managed `.gitignore` blocks into one regenerated Brigade block.
59+
5760
## [0.6.0] - 2026-05-24
5861

5962
### Added

src/brigade/install.py

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,20 +84,52 @@ def apply_gitignore(target: Path, selection: Selection) -> str:
8484
(GITIGNORE_BEGIN, GITIGNORE_END),
8585
(LEGACY_GITIGNORE_BEGIN, LEGACY_GITIGNORE_END),
8686
)
87-
for begin, end in markers:
88-
if begin not in existing or end not in existing:
89-
continue
90-
prefix, _, rest = existing.partition(begin)
91-
_, _, suffix = rest.partition(end)
92-
# Strip a trailing newline from prefix and a leading newline from suffix to avoid drift.
93-
new_text = prefix.rstrip("\n") + ("\n\n" if prefix.strip() else "") + block + suffix.lstrip("\n")
87+
new_text, replaced = _replace_managed_gitignore_blocks(existing, block, markers)
88+
if replaced:
9489
gi.write_text(new_text)
9590
return "updated"
9691
sep = "" if existing.endswith("\n") else "\n"
9792
gi.write_text(existing + sep + "\n" + block)
9893
return "updated"
9994

10095

96+
def _replace_managed_gitignore_blocks(
97+
existing: str,
98+
block: str,
99+
markers: tuple[tuple[str, str], ...],
100+
) -> tuple[str, bool]:
101+
"""Replace all complete known managed blocks with one regenerated block."""
102+
output: list[str] = []
103+
cursor = 0
104+
inserted = False
105+
replaced = False
106+
while True:
107+
next_block: tuple[int, int] | None = None
108+
for begin, end in markers:
109+
start = existing.find(begin, cursor)
110+
if start == -1:
111+
continue
112+
end_start = existing.find(end, start + len(begin))
113+
if end_start == -1:
114+
continue
115+
stop = end_start + len(end)
116+
if next_block is None or start < next_block[0]:
117+
next_block = (start, stop)
118+
if next_block is None:
119+
break
120+
start, stop = next_block
121+
output.append(existing[cursor:start])
122+
if not inserted:
123+
output.append(block)
124+
inserted = True
125+
cursor = stop
126+
replaced = True
127+
if not replaced:
128+
return existing, False
129+
output.append(existing[cursor:])
130+
return "".join(output), True
131+
132+
101133
def resolve_manifests(selection: Selection) -> Tuple[List[dict], List[str], List[str]]:
102134
"""Return (files, dirs, post_install_notes) for a Selection.
103135

tests/test_gitignore.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,48 @@ def test_init_replaces_legacy_solo_mise_gitignore_block(tmp_target: Path):
9696
assert gi.count(install_mod.GITIGNORE_BEGIN) == 1
9797

9898

99+
def test_init_collapses_current_and_legacy_gitignore_blocks(tmp_target: Path):
100+
tmp_target.mkdir()
101+
(tmp_target / ".gitignore").write_text(
102+
"\n".join(
103+
[
104+
"# user rules",
105+
"*.log",
106+
"",
107+
install_mod.GITIGNORE_BEGIN,
108+
"STALE_CURRENT_LINE",
109+
install_mod.GITIGNORE_END,
110+
"",
111+
"# between blocks",
112+
".cache/",
113+
"",
114+
install_mod.LEGACY_GITIGNORE_BEGIN,
115+
"STALE_LEGACY_LINE",
116+
install_mod.LEGACY_GITIGNORE_END,
117+
"",
118+
"# after block",
119+
".local-cache/",
120+
"",
121+
]
122+
)
123+
)
124+
125+
rc = install_selection(tmp_target, _repo_selection())
126+
127+
assert rc == 0
128+
gi = _read_gi(tmp_target)
129+
assert "# user rules" in gi
130+
assert "# between blocks" in gi
131+
assert ".cache/" in gi
132+
assert ".local-cache/" in gi
133+
assert "STALE_CURRENT_LINE" not in gi
134+
assert "STALE_LEGACY_LINE" not in gi
135+
assert install_mod.LEGACY_GITIGNORE_BEGIN not in gi
136+
assert install_mod.LEGACY_GITIGNORE_END not in gi
137+
assert gi.count(install_mod.GITIGNORE_BEGIN) == 1
138+
assert gi.count(install_mod.GITIGNORE_END) == 1
139+
140+
99141
def test_init_preserves_user_edits_outside_block(tmp_target: Path):
100142
tmp_target.mkdir()
101143
pre = "node_modules/\n# user rules\n*.swp\n"

0 commit comments

Comments
 (0)