summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md3
-rw-r--r--main.go28
-rw-r--r--main_test.go22
3 files changed, 52 insertions, 1 deletions
diff --git a/README.md b/README.md
index afcc659..fb0e97f 100644
--- a/README.md
+++ b/README.md
@@ -98,7 +98,8 @@ All standard `html/template` actions and functions remain available, including
## Git ignore management
`-gitignore` atomically replaces a marked block in the site-root `.gitignore`
-with exact, root-anchored paths for the current generated outputs. Content
+with exact, root-anchored paths for the current generated outputs. Outputs below
+symlinked directories are omitted because Git does not traverse them. Content
outside the block is preserved, and stale output entries are removed. Without
the flag, Weft does not touch `.gitignore`.
diff --git a/main.go b/main.go
index 299724e..96c1115 100644
--- a/main.go
+++ b/main.go
@@ -858,12 +858,40 @@ func (s *site) gitignore(outputs []string) ([]byte, error) {
if strings.ContainsAny(output, "\r\n") {
return nil, fmt.Errorf("cannot add output to .gitignore: %q", output)
}
+ symlinked, err := s.hasSymlinkedParent(output)
+ if err != nil {
+ return nil, err
+ }
+ if symlinked {
+ continue
+ }
kept.WriteString("/" + escapeGitignore(output) + "\n")
}
kept.WriteString(gitignoreEnd + "\n")
return []byte(kept.String()), nil
}
+func (s *site) hasSymlinkedParent(output string) (bool, error) {
+ current := s.root
+ for _, part := range strings.Split(path.Dir(output), "/") {
+ if part == "." {
+ continue
+ }
+ current = filepath.Join(current, part)
+ info, err := os.Lstat(current)
+ if errors.Is(err, os.ErrNotExist) {
+ return false, nil
+ }
+ if err != nil {
+ return false, err
+ }
+ if info.Mode()&os.ModeSymlink != 0 {
+ return true, nil
+ }
+ }
+ return false, nil
+}
+
func escapeGitignore(value string) string {
return strings.NewReplacer(
`\`, `\\`, " ", `\ `, "#", `\#`, "!", `\!`,
diff --git a/main_test.go b/main_test.go
index 66d793f..f6c943e 100644
--- a/main_test.go
+++ b/main_test.go
@@ -354,6 +354,28 @@ func TestGitignoreTracksOnlyCurrentGeneratedOutputs(t *testing.T) {
}
}
+func TestGitignoreOmitsOutputsBelowSymlinks(t *testing.T) {
+ root := t.TempDir()
+ vault := t.TempDir()
+ writeTestFile(t, root, ".gitignore", gitignoreStart+"\n/notes/note.html\n"+gitignoreEnd+"\n")
+ writeTestFile(t, root, "layouts.tmpl", testLayouts())
+ writeTestFile(t, root, "local.md", "# Local\n")
+ writeTestFile(t, vault, "note.md", "# Note\n")
+ if err := os.Symlink(vault, filepath.Join(root, "notes")); err != nil {
+ t.Skipf("symlinks unavailable: %v", err)
+ }
+ if err := buildWithFallback(root, "https://example.com", "page", true); err != nil {
+ t.Fatal(err)
+ }
+ want := gitignoreStart + "\n/local.html\n" + gitignoreEnd + "\n"
+ if raw, _ := os.ReadFile(filepath.Join(root, ".gitignore")); string(raw) != want {
+ t.Fatalf(".gitignore = %q, want %q", raw, want)
+ }
+ if raw, _ := os.ReadFile(filepath.Join(root, manifestName)); !strings.Contains(string(raw), `"notes/note.html"`) {
+ t.Fatalf("manifest does not own symlinked output: %s", raw)
+ }
+}
+
func TestSortingFilteringSlicing(t *testing.T) {
a := &Page{Title: "A", Meta: map[string]any{"post_date": "2024-01-01", "kind": "x"}, ModTime: time.Unix(1, 0)}
b := &Page{Title: "B", Meta: map[string]any{"post_date": "2025-01-01", "kind": "y"}, ModTime: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)}