commit f69874987a5dea73355979b0fbe38644d85eb0d0
| author | 斟酌 鵬兄 <tgckpg@gmail.com> |
| date | 2026-06-18T22:39:51Z |
| subject | Removed Slug() and use RepoBase() instead |
commit f69874987a5dea73355979b0fbe38644d85eb0d0
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date: 2026-06-18T22:39:51Z
Removed Slug() and use RepoBase() instead
---
.gitignore | 1 +
cmd/flatgit/main.go | 37 +++++++++++++++--
internal/buildinfo/buildinfo_gen.go | 4 +-
internal/config/config.go | 59 ++++++++++++++++----------
internal/config/config_test.go | 4 +-
internal/render/manifest.go | 2 +-
internal/webhook/webhook.go | 82 +++++++++++++++++++++++++++++++++++--
7 files changed, 157 insertions(+), 32 deletions(-)
diff --git a/.gitignore b/.gitignore
index 8b87ab2..5342bc1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
out/
bin/
build.env.work
+values.work.yaml
*.test
*.out
*.swp
diff --git a/cmd/flatgit/main.go b/cmd/flatgit/main.go
index 98eb00c..d0b141d 100644
--- a/cmd/flatgit/main.go
+++ b/cmd/flatgit/main.go
@@ -145,25 +145,56 @@ func daemonCmd(log *slog.Logger, args []string) error {
})
}
+func repoMatches(repo config.Repo, name string) bool {
+ name = strings.TrimSpace(name)
+ name = strings.Trim(name, "/")
+
+ if name == "" {
+ return true
+ }
+
+ fullName := strings.Trim(repo.FullName(), "/")
+ repoBase := strings.Trim(repo.RepoBase(), "/")
+
+ return strings.EqualFold(repo.Name, name) ||
+ strings.EqualFold(fullName, name) ||
+ strings.EqualFold(repoBase, name)
+}
+
func renderConfigured(ctx context.Context, log *slog.Logger, cfg *config.Config, repoName string, fetch bool) error {
git := gitcmd.New(cfg.Git.Command)
r := render.New(git, cfg.PublicURL, cfg.Render.MaxCommits)
+
+ matched := false
+
for i := range cfg.Repos {
repo := cfg.Repos[i]
- if repoName != "" && !strings.EqualFold(repo.Name, repoName) && !strings.EqualFold(repo.Slug(), repoName) && !strings.EqualFold(repo.FullName(), repoName) {
+
+ if repoName != "" && !repoMatches(repo, repoName) {
continue
}
- log.Info("rendering repo", "repo", repo.FullName())
+
+ matched = true
+
+ log.Info("rendering repo", "repo", repo.FullName(), "base", repo.RepoBase())
+
if fetch {
if err := git.EnsureMirror(ctx, repo, cfg.CloneTimeout(), cfg.FetchTimeout()); err != nil {
return err
}
}
+
if err := r.RenderRepo(ctx, repo); err != nil {
return err
}
- log.Info("rendered repo", "repo", repo.FullName(), "output", repo.OutputDir)
+
+ log.Info("rendered repo", "repo", repo.FullName(), "base", repo.RepoBase(), "output", repo.OutputDir)
+ }
+
+ if repoName != "" && !matched {
+ return fmt.Errorf("repo not configured: %s", repoName)
}
+
return nil
}
diff --git a/internal/buildinfo/buildinfo_gen.go b/internal/buildinfo/buildinfo_gen.go
index 0326bbb..89801d4 100644
--- a/internal/buildinfo/buildinfo_gen.go
+++ b/internal/buildinfo/buildinfo_gen.go
@@ -2,6 +2,6 @@ package buildinfo
const (
Version = "dev"
- GitRevision = "bdd09021663dcb4b6d0267105cd47964ff810c51"
- Timestamp = "20260618.214049"
+ GitRevision = "ae2ca700755df37c186d45857ad98648ad940781"
+ Timestamp = "20260618.222654"
)
diff --git a/internal/config/config.go b/internal/config/config.go
index d0eaa96..cbd894d 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
+ "net/url"
"os"
"path/filepath"
"strings"
@@ -43,6 +44,9 @@ type Repo struct {
MirrorDir string `json:"mirror_dir"`
OutputDir string `json:"output_dir"`
Description string `json:"description"`
+
+ fullName string
+ repoBase string
}
func Load(path string) (*Config, error) {
@@ -103,10 +107,10 @@ func (c *Config) ApplyDefaults() error {
r.DefaultBranch = "main"
}
if r.MirrorDir == "" {
- r.MirrorDir = filepath.Join(c.DataDir, "repos", r.Slug()+".git")
+ r.MirrorDir = filepath.Join(c.DataDir, "repos", r.RepoBase()+".git")
}
if r.OutputDir == "" {
- r.OutputDir = filepath.Join(c.DataDir, "www", r.Slug())
+ r.OutputDir = filepath.Join(c.DataDir, "www", r.RepoBase())
}
}
return nil
@@ -124,41 +128,54 @@ func (c *Config) WebRoot() string {
return filepath.Join(c.DataDir, "www")
}
-func (c *Config) RepoByName(name string) (*Repo, bool) {
- name = cleanName(name)
- for i := range c.Repos {
- if c.Repos[i].Name == name || c.Repos[i].Slug() == name || c.Repos[i].FullName() == name {
- return &c.Repos[i], true
+func (c Config) RepoByName(name string) (*Repo, bool) {
+ for _, repo := range c.Repos {
+ if repo.FullName() == name || repo.Name == name {
+ return &repo, true
}
}
- return nil, false
+ return &Repo{}, false
}
-func (c *Config) RepoByWebhookName(fullName string) (*Repo, bool) {
- fullName = strings.TrimSpace(fullName)
- for i := range c.Repos {
- r := &c.Repos[i]
- if fullName == r.FullName() || fullName == r.Name || fullName == r.Slug() {
- return r, true
+func (c Config) RepoByWebhookName(name string) (Repo, bool) {
+ for _, repo := range c.Repos {
+ if repo.FullName() == name {
+ return repo, true
}
- }
- return nil, false
-}
-func (r Repo) Slug() string {
- if r.Owner == "" {
- return cleanName(r.Name)
+ // Fallback for single-owner or old/simple configs.
+ if repo.Name == name {
+ return repo, true
+ }
}
- return cleanName(r.Owner + "_" + r.Name)
+
+ return Repo{}, false
}
func (r Repo) FullName() string {
+ if r.fullName != "" {
+ return r.fullName
+ }
+
if r.Owner == "" {
return r.Name
}
+
return r.Owner + "/" + r.Name
}
+func (r Repo) RepoBase() string {
+ if r.repoBase != "" {
+ return r.repoBase
+ }
+
+ if r.Owner == "" {
+ return "/" + url.PathEscape(r.Name) + "/"
+ }
+
+ return "/" + url.PathEscape(r.Owner) + "/" + url.PathEscape(r.Name) + "/"
+}
+
func parseDuration(s string, fallback time.Duration) time.Duration {
d, err := time.ParseDuration(s)
if err != nil {
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 73b1e97..6927f95 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -10,8 +10,8 @@ func TestDefaults(t *testing.T) {
if cfg.Addr != ":8080" {
t.Fatalf("addr = %q", cfg.Addr)
}
- if cfg.Repos[0].Slug() != "penguin_test-repo" {
- t.Fatalf("slug = %q", cfg.Repos[0].Slug())
+ if cfg.Repos[0].RepoBase() != "/penguin/test-repo/" {
+ t.Fatalf("slug = %q", cfg.Repos[0].RepoBase())
}
if cfg.Repos[0].MirrorDir == "" || cfg.Repos[0].OutputDir == "" {
t.Fatalf("paths were not defaulted")
diff --git a/internal/render/manifest.go b/internal/render/manifest.go
index da6f7df..faf0f72 100644
--- a/internal/render/manifest.go
+++ b/internal/render/manifest.go
@@ -74,7 +74,7 @@ func NewManifest(repo config.Repo, defaultBranch string, defaultCommit string) M
DefaultBranch: defaultBranch,
DefaultCommit: defaultCommit,
DefaultRefSlug: refSlug(repo.DefaultBranch),
- SitePath: "/" + repo.Slug() + "/",
+ SitePath: repo.RepoBase(),
},
Human: ManifestHumanRoutes{
Index: "./index.html",
diff --git a/internal/webhook/webhook.go b/internal/webhook/webhook.go
index 1d73e40..0c6d468 100644
--- a/internal/webhook/webhook.go
+++ b/internal/webhook/webhook.go
@@ -41,42 +41,118 @@ func (h *Handler) Register(mux *http.ServeMux) {
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ log := h.Logger
+
+ if log != nil {
+ log.Info("webhook request",
+ "method", r.Method,
+ "path", r.URL.Path,
+ "remote", r.RemoteAddr,
+ "event", r.Header.Get("X-Gitea-Event"),
+ "content_type", r.Header.Get("Content-Type"),
+ "delivery", r.Header.Get("X-Gitea-Delivery"),
+ "signature", r.Header.Get("X-Gitea-Signature") != "",
+ "signature_256", r.Header.Get("X-Gitea-Signature-256") != "",
+ )
+ }
+
if r.Method != http.MethodPost {
+ if log != nil {
+ log.Warn("webhook rejected", "reason", "method not allowed", "method", r.Method)
+ }
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
+
if h.MaxBody <= 0 {
h.MaxBody = 1 << 20
}
+
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, h.MaxBody))
if err != nil {
+ if log != nil {
+ log.Warn("webhook rejected", "reason", "bad body", "error", err)
+ }
http.Error(w, "bad body", http.StatusBadRequest)
return
}
+
+ if log != nil {
+ log.Info("webhook body read", "bytes", len(body))
+ }
+
if !verifySignature(h.Config.Webhook.Secret, body, r) {
+ if log != nil {
+ log.Warn("webhook rejected",
+ "reason", "bad signature",
+ "secret_configured", h.Config.Webhook.Secret != "",
+ "signature", r.Header.Get("X-Gitea-Signature"),
+ "signature_256", r.Header.Get("X-Gitea-Signature-256"),
+ )
+ }
http.Error(w, "bad signature", http.StatusUnauthorized)
return
}
var p payload
if err := json.Unmarshal(body, &p); err != nil {
+ if log != nil {
+ log.Warn("webhook rejected", "reason", "bad json", "error", err)
+ }
http.Error(w, "bad json", http.StatusBadRequest)
return
}
+
fullName := firstNonEmpty(p.Repository.FullName, p.Repository.Name)
+
+ if log != nil {
+ log.Info("webhook payload",
+ "ref", p.Ref,
+ "repo_name", p.Repository.Name,
+ "repo_full_name", p.Repository.FullName,
+ "selected_name", fullName,
+ )
+ }
+
if fullName == "" {
+ if log != nil {
+ log.Warn("webhook rejected", "reason", "missing repository name")
+ }
http.Error(w, "missing repository name", http.StatusBadRequest)
return
}
+
repo, ok := h.Config.RepoByWebhookName(fullName)
if !ok {
+ if log != nil {
+ var configured []string
+ for _, r := range h.Config.Repos {
+ configured = append(configured, r.FullName())
+ }
+
+ log.Warn("webhook rejected",
+ "reason", "repo not configured",
+ "payload_repo", fullName,
+ "configured_repos", configured,
+ )
+ }
+
http.Error(w, fmt.Sprintf("repo not configured: %s", fullName), http.StatusNotFound)
return
}
- queued := h.Queue.Enqueue(repo.Name)
- if h.Logger != nil {
- h.Logger.Info("webhook received", "repo", repo.FullName(), "ref", p.Ref, "queued", queued)
+
+ queued := h.Queue.Enqueue(repo.FullName())
+
+ if log != nil {
+ h.Logger.Info("webhook accepted",
+ "payload_repo", fullName,
+ "repo", repo.FullName(),
+ "queue_name", repo.Name,
+ "ref", p.Ref,
+ "queued", queued,
+ )
}
+
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusAccepted)
_, _ = fmt.Fprintf(w, `{"ok":true,"queued":%t,"repo":%q}`+"\n", queued, repo.FullName())