New Sign in

fierj

Public

Tiny personal git forge

92c61a6f6f6247692cbf25b28420d5e8a7076fb7
diff --git a/git.go b/git.go
index 948e7d5..6c3967f 100644
--- a/git.go
+++ b/git.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"bytes"
+	"encoding/json"
 	"fmt"
 	"os"
 	"os/exec"
@@ -25,6 +26,44 @@ type Commit struct {
 	Message string
 }
 
+// RepoMeta is per-repository configuration stored in .fierj.json inside the bare repo.
+type RepoMeta struct {
+	Description       string   `json:"description,omitempty"`
+	IsPrivate         bool     `json:"is_private"`                    // requires login to view
+	AuthorizedKeys    []string `json:"authorized_keys,omitempty"`     // SSH public keys allowed to push
+	ProtectedBranches []string `json:"protected_branches,omitempty"` // branches that reject force-push
+}
+
+func (g *Git) metaPath() string {
+	return path.Join(g.Dir, g.Name+".git", ".fierj.json")
+}
+
+// LoadMeta reads the .fierj.json file, falling back to the git description file for Description.
+func (g *Git) LoadMeta() RepoMeta {
+	var m RepoMeta
+	if data, err := os.ReadFile(g.metaPath()); err == nil {
+		json.Unmarshal(data, &m)
+	}
+	if m.Description == "" {
+		if data, err := os.ReadFile(path.Join(g.Dir, g.Name+".git", "description")); err == nil {
+			d := strings.TrimSpace(string(data))
+			if d != "" && !strings.Contains(d, "Unnamed repository") {
+				m.Description = d
+			}
+		}
+	}
+	return m
+}
+
+// SaveMeta writes the .fierj.json file.
+func (g *Git) SaveMeta(m RepoMeta) error {
+	data, err := json.MarshalIndent(m, "", "  ")
+	if err != nil {
+		return err
+	}
+	return os.WriteFile(g.metaPath(), data, 0644)
+}
+
 type VCS interface {
 	List(ref, path string) ([]TreeEntry, error)
 	Blob(ref, path string) (string, error)
@@ -94,7 +133,8 @@ func InitRepo(cfg Config, name, description string) error {
 		return fmt.Errorf("git init failed: %v, stderr: %s", err, stderr.String())
 	}
 	if description != "" {
-		os.WriteFile(filepath.Join(repoPath, "description"), []byte(description+"\n"), 0644)
+		g := &Git{Dir: absRoot, Name: name}
+		g.SaveMeta(RepoMeta{Description: description})
 	}
 	return nil
 }
@@ -119,7 +159,8 @@ func ImportRepo(cfg Config, cloneURL, description string) (string, error) {
 		return "", fmt.Errorf("clone failed: %v, stderr: %s", err, stderr.String())
 	}
 	if description != "" {
-		os.WriteFile(filepath.Join(repoPath, "description"), []byte(description+"\n"), 0644)
+		g := &Git{Dir: absRoot, Name: name}
+		g.SaveMeta(RepoMeta{Description: description})
 	}
 	return name, nil
 }
@@ -143,15 +184,11 @@ func ListRepos(cfg Config) ([]*Git, error) {
 
 func (g *Git) Repo() string { return g.Name }
 func (g *Git) Description() string {
-	data, err := os.ReadFile(path.Join(g.Dir, g.Name+".git", "description"))
-	if err != nil {
-		return ""
-	}
-	desc := strings.TrimSpace(string(data))
-	if desc == "" || strings.Contains(desc, "Unnamed repository") {
-		return ""
-	}
-	return desc
+	return g.LoadMeta().Description
+}
+
+func (g *Git) IsPrivate() bool {
+	return g.LoadMeta().IsPrivate
 }
 
 func (g *Git) List(ref, path string) ([]TreeEntry, error) {