wocao
This commit is contained in:
+21
-31
@@ -9,17 +9,25 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
type Config struct {
|
||||
Root string
|
||||
RPCAddr string
|
||||
SSHAddr string
|
||||
ManageAddr string
|
||||
AuthURL string
|
||||
Root string
|
||||
// SSHAddr: git SSH listen (clone/push over SSH, keyed auth).
|
||||
SSHAddr string
|
||||
// HTTPAddr: plain HTTP listen for git smart-HTTP clone/push
|
||||
// (/:owner/:name.git/*) + the index page. This is the user-facing git
|
||||
// transport (nginx routes .git/* here). Empty = no HTTP listener.
|
||||
HTTPAddr string
|
||||
// GitRPCAddrs: Connect-RPC listen addresses (GitService + ManageService)
|
||||
// PLUS the binary /raw, /archive, /patch content endpoints. Others dial
|
||||
// these to read/write/modify repo content and drive repo lifecycle. May be
|
||||
// multiple (e.g. a unix socket for a local TUI and a tcp addr for the
|
||||
// console). Empty = no gitrpc listener.
|
||||
GitRPCAddrs []string
|
||||
// HookScript is an operator-provided executable invoked by every Git hook.
|
||||
// The hook name is its first argument; Git's original args, stdin and
|
||||
// environment are preserved. Empty disables hook effects.
|
||||
HookScript string
|
||||
DBUri string
|
||||
SkipAuth bool
|
||||
|
||||
preparedDbUri string
|
||||
prepared atomic.Bool
|
||||
@@ -31,20 +39,10 @@ func (c *Config) RepoRoot() string {
|
||||
return filepath.Join(c.Root, "repos")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
func (c *Config) TemplateDir() string {
|
||||
return filepath.Join(c.Root, "template")
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (c *Config) HookSock() string {
|
||||
return filepath.Join(c.Root, "hook.sock")
|
||||
}
|
||||
|
||||
|
||||
func (c *Config) Prepare() error {
|
||||
if c.prepared.Load() {
|
||||
return nil
|
||||
@@ -60,12 +58,10 @@ func (c *Config) Prepare() error {
|
||||
if err != nil {
|
||||
return errors.New("simplegit: ParseDBURI failed")
|
||||
}
|
||||
if c.RPCAddr == "" {
|
||||
return errors.New("simplegit: -rpc is required (the HTTP server is this binary's only listener)")
|
||||
}
|
||||
if c.ManageAddr == "" {
|
||||
return fmt.Errorf("*manageAddr == nil")
|
||||
}
|
||||
// Listeners are all optional: a daemon with only -ssh, only -http, only
|
||||
// -gitrpc, or any combination is valid (empty = that surface is disabled).
|
||||
// At least one of -http/-ssh/-gitrpc should be set for the daemon to do
|
||||
// anything useful, but that is the operator's choice, not a hard error.
|
||||
c.prepared.Store(true)
|
||||
return nil
|
||||
}
|
||||
@@ -91,20 +87,14 @@ func (c *Config) DBURI() string {
|
||||
return c.preparedDbUri
|
||||
}
|
||||
|
||||
|
||||
|
||||
func normalizeName(name string) string {
|
||||
return strings.TrimSuffix(name, ".git")
|
||||
}
|
||||
|
||||
|
||||
func relPath(owner, name string) string {
|
||||
return owner + "/" + normalizeName(name) + ".git"
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
func (s *Config) RepoPath(owner, name string) (string, error) {
|
||||
name = normalizeName(name)
|
||||
rel := relPath(owner, name)
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var gitHookNames = []string{
|
||||
"pre-receive", "update", "post-receive", "post-update", "proc-receive",
|
||||
"push-to-checkout", "reference-transaction", "pre-applypatch",
|
||||
"post-applypatch", "pre-commit", "pre-merge-commit", "prepare-commit-msg",
|
||||
"commit-msg", "post-commit", "pre-rebase", "post-checkout", "post-merge",
|
||||
"pre-push", "pre-auto-gc", "post-rewrite", "sendemail-validate",
|
||||
"fsmonitor-watchman", "p4-pre-submit", "p4-post-changelist",
|
||||
"p4-prepare-changelist", "p4-changelist",
|
||||
}
|
||||
|
||||
const hookWrapper = `#!/bin/sh
|
||||
# Generated by simplegit. The operator controls hook behavior with -hook-script.
|
||||
test -n "$SIMPLEGIT_HOOK_SCRIPT" || exit 0
|
||||
repo_dir=$(pwd -P)
|
||||
repo=${repo_dir#"$SIMPLEGIT_REPO_ROOT"/}
|
||||
repo=${repo%%.git}
|
||||
exec "$SIMPLEGIT_HOOK_SCRIPT" %s "$repo" "$@"
|
||||
`
|
||||
|
||||
// EnsureHookTemplate creates the git-init template used for new repositories.
|
||||
// All hook entrypoints delegate to one environment-specific external script.
|
||||
func EnsureHookTemplate(root string) (string, error) {
|
||||
dir := filepath.Join(root, "template")
|
||||
hooksDir := filepath.Join(dir, "hooks")
|
||||
if err := os.MkdirAll(hooksDir, 0o755); err != nil {
|
||||
return "", fmt.Errorf("create hook template: %w", err)
|
||||
}
|
||||
for _, name := range gitHookNames {
|
||||
path := filepath.Join(hooksDir, name)
|
||||
if err := os.WriteFile(path, []byte(fmt.Sprintf(hookWrapper, name)), 0o755); err != nil {
|
||||
return "", fmt.Errorf("write hook %s: %w", name, err)
|
||||
}
|
||||
}
|
||||
if err := refreshRepositoryHooks(filepath.Join(root, "repos"), hooksDir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
// refreshRepositoryHooks updates repositories created with an older template.
|
||||
func refreshRepositoryHooks(reposRoot, templateHooks string) error {
|
||||
if _, err := os.Stat(reposRoot); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return filepath.WalkDir(reposRoot, func(path string, entry fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !entry.IsDir() || !strings.HasSuffix(entry.Name(), ".git") {
|
||||
return nil
|
||||
}
|
||||
dst := filepath.Join(path, "hooks")
|
||||
if err := os.MkdirAll(dst, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range gitHookNames {
|
||||
body, err := os.ReadFile(filepath.Join(templateHooks, name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dst, name), body, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return filepath.SkipDir
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEnsureHookTemplatePassesTypeAndRepo(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
dir, err := EnsureHookTemplate(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body, err := os.ReadFile(filepath.Join(dir, "hooks", "post-receive"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
text := string(body)
|
||||
for _, want := range []string{`post-receive "$repo" "$@"`, `repo=${repo_dir#"$SIMPLEGIT_REPO_ROOT"/}`, `repo=${repo%.git}`} {
|
||||
if !strings.Contains(text, want) {
|
||||
t.Errorf("generated hook missing %q:\n%s", want, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureHookTemplateRefreshesExistingRepos(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
hook := filepath.Join(root, "repos", "alice", "demo.git", "hooks", "post-receive")
|
||||
if err := os.MkdirAll(filepath.Dir(hook), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(hook, []byte("old"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := EnsureHookTemplate(root); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body, err := os.ReadFile(hook)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(body), `post-receive "$repo"`) {
|
||||
t.Fatalf("existing hook was not refreshed: %s", body)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user