Skip to content

Commit eb61d2a

Browse files
committed
feat: auto-update .gitignore with synced symlink entries
- sync command appends symlink targets to .gitignore if missing - Adds header comment '# sync-agents (generated symlinks)' - Idempotent: never duplicates entries - Respects --targets (only adds relevant entries) - Respects --dry-run (shows what would be added) - Preserves existing .gitignore content
1 parent 673131e commit eb61d2a

2 files changed

Lines changed: 110 additions & 0 deletions

File tree

src/sh/sync-agents.sh

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,9 +315,65 @@ cmd_sync() {
315315
create_symlink "$AGENTS_MD" "$PROJECT_ROOT/CLAUDE.md" "$DRY_RUN"
316316
fi
317317

318+
# Update .gitignore with synced symlink entries
319+
update_gitignore
320+
318321
info "Sync complete."
319322
}
320323

324+
# --------------------------------------------------------------------------
325+
# .gitignore management
326+
# --------------------------------------------------------------------------
327+
328+
update_gitignore() {
329+
local gitignore="$PROJECT_ROOT/.gitignore"
330+
331+
# Build list of entries that should be ignored (synced symlinks)
332+
local entries=()
333+
for target in "${ACTIVE_TARGETS[@]}"; do
334+
local target_dir
335+
target_dir="$(resolve_target_dir "$target" "$PROJECT_ROOT")"
336+
local rel_path="${target_dir#"$PROJECT_ROOT"/}/"
337+
entries+=("$rel_path")
338+
done
339+
entries+=("CLAUDE.md")
340+
341+
if [[ "$DRY_RUN" == "true" ]]; then
342+
for entry in "${entries[@]}"; do
343+
if [[ ! -f "$gitignore" ]] || ! grep -qxF "$entry" "$gitignore"; then
344+
echo " would add to .gitignore: $entry"
345+
fi
346+
done
347+
return 0
348+
fi
349+
350+
# Create .gitignore if it doesn't exist
351+
[[ -f "$gitignore" ]] || touch "$gitignore"
352+
353+
local added=0
354+
for entry in "${entries[@]}"; do
355+
if ! grep -qxF "$entry" "$gitignore"; then
356+
# Add sync-agents header on first addition
357+
if [[ "$added" -eq 0 ]]; then
358+
# Check if header already exists
359+
if ! grep -qF "# sync-agents" "$gitignore"; then
360+
# Add a blank line separator if file is non-empty
361+
if [[ -s "$gitignore" ]]; then
362+
echo "" >> "$gitignore"
363+
fi
364+
echo "# sync-agents (generated symlinks)" >> "$gitignore"
365+
fi
366+
fi
367+
echo "$entry" >> "$gitignore"
368+
added=$((added + 1))
369+
fi
370+
done
371+
372+
if [[ "$added" -gt 0 ]]; then
373+
info "Added $added entries to .gitignore"
374+
fi
375+
}
376+
321377
cmd_status() {
322378
echo -e "${BOLD}sync-agents${RESET} v${VERSION}"
323379
echo ""

test/sync-agents.bats

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,60 @@ teardown() {
588588
[ ! -d "$TEST_DIR/.windsurf" ]
589589
}
590590

591+
# --------------------------------------------------------------------------
592+
# .gitignore
593+
# --------------------------------------------------------------------------
594+
595+
@test "sync adds symlink entries to .gitignore" {
596+
bash "$SCRIPT" -d "$TEST_DIR" init
597+
bash "$SCRIPT" -d "$TEST_DIR" sync
598+
[ -f "$TEST_DIR/.gitignore" ]
599+
grep -qxF ".claude/" "$TEST_DIR/.gitignore"
600+
grep -qxF ".windsurf/" "$TEST_DIR/.gitignore"
601+
grep -qxF ".cursor/" "$TEST_DIR/.gitignore"
602+
grep -qxF ".github/copilot/" "$TEST_DIR/.gitignore"
603+
grep -qxF "CLAUDE.md" "$TEST_DIR/.gitignore"
604+
}
605+
606+
@test "sync adds header comment to .gitignore" {
607+
bash "$SCRIPT" -d "$TEST_DIR" init
608+
bash "$SCRIPT" -d "$TEST_DIR" sync
609+
grep -qF "# sync-agents" "$TEST_DIR/.gitignore"
610+
}
611+
612+
@test "sync does not duplicate .gitignore entries" {
613+
bash "$SCRIPT" -d "$TEST_DIR" init
614+
bash "$SCRIPT" -d "$TEST_DIR" sync
615+
bash "$SCRIPT" -d "$TEST_DIR" sync
616+
local count
617+
count=$(grep -cxF "CLAUDE.md" "$TEST_DIR/.gitignore")
618+
[ "$count" -eq 1 ]
619+
}
620+
621+
@test "sync preserves existing .gitignore content" {
622+
bash "$SCRIPT" -d "$TEST_DIR" init
623+
echo "node_modules/" > "$TEST_DIR/.gitignore"
624+
bash "$SCRIPT" -d "$TEST_DIR" sync
625+
grep -qxF "node_modules/" "$TEST_DIR/.gitignore"
626+
grep -qxF "CLAUDE.md" "$TEST_DIR/.gitignore"
627+
}
628+
629+
@test "sync --targets only adds relevant entries to .gitignore" {
630+
bash "$SCRIPT" -d "$TEST_DIR" init
631+
bash "$SCRIPT" -d "$TEST_DIR" sync --targets claude
632+
grep -qxF ".claude/" "$TEST_DIR/.gitignore"
633+
grep -qxF "CLAUDE.md" "$TEST_DIR/.gitignore"
634+
! grep -qxF ".windsurf/" "$TEST_DIR/.gitignore"
635+
}
636+
637+
@test "sync --dry-run does not modify .gitignore" {
638+
bash "$SCRIPT" -d "$TEST_DIR" init
639+
bash "$SCRIPT" -d "$TEST_DIR" sync --dry-run
640+
if [ -f "$TEST_DIR/.gitignore" ]; then
641+
! grep -qxF "CLAUDE.md" "$TEST_DIR/.gitignore"
642+
fi
643+
}
644+
591645
# --------------------------------------------------------------------------
592646
# Inheritance
593647
# --------------------------------------------------------------------------

0 commit comments

Comments
 (0)