+ New

fierj

Public

Tiny personal git forge

d6f67c505fd621d68b069585a09a9188381b0aa9
diff --git a/git.go b/git.go
index b452e08..280be92 100644
--- a/git.go
+++ b/git.go
@@ -58,23 +58,72 @@ func (g *Git) cmd(args ...string) (string, error) {
 	return stdout.String(), nil
 }
 
-func InitRepo(cfg *Config, name string) error {
-	if strings.Contains(name, "/") || strings.Contains(name, "..") {
+func validateRepoName(name string) error {
+	if name == "" || strings.Contains(name, "/") || strings.Contains(name, "..") {
 		return fmt.Errorf("invalid repo name")
 	}
+	return nil
+}
+
+func repoNameFromURL(rawURL string) string {
+	s := rawURL
+	s = strings.TrimSuffix(s, ".git")
+	s = strings.TrimSuffix(s, "/")
+	if idx := strings.LastIndex(s, "/"); idx >= 0 {
+		s = s[idx+1:]
+	}
+	if idx := strings.LastIndex(s, ":"); idx >= 0 {
+		s = s[idx+1:]
+	}
+	return s
+}
+
+func InitRepo(cfg Config, name, description string) error {
+	if err := validateRepoName(name); err != nil {
+		return err
+	}
 	absRoot, err := filepath.Abs(cfg.Dir)
 	if err != nil {
 		return err
 	}
 	repoPath := filepath.Join(absRoot, name+".git")
-	g := &Git{Dir: absRoot}
-	_, err = g.cmd("init", "--bare", repoPath)
-	if err != nil {
-		return err
+	cmd := exec.Command("git", "init", "--bare", repoPath)
+	var stderr bytes.Buffer
+	cmd.Stderr = &stderr
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("git init failed: %v, stderr: %s", err, stderr.String())
+	}
+	if description != "" {
+		os.WriteFile(filepath.Join(repoPath, "description"), []byte(description+"\n"), 0644)
 	}
 	return nil
 }
 
+func ImportRepo(cfg Config, cloneURL, description string) (string, error) {
+	name := repoNameFromURL(cloneURL)
+	if err := validateRepoName(name); err != nil {
+		return "", err
+	}
+	absRoot, err := filepath.Abs(cfg.Dir)
+	if err != nil {
+		return "", err
+	}
+	repoPath := filepath.Join(absRoot, name+".git")
+	if _, err := os.Stat(repoPath); err == nil {
+		return "", fmt.Errorf("repository %s already exists", name)
+	}
+	cmd := exec.Command("git", "clone", "--bare", cloneURL, repoPath)
+	var stderr bytes.Buffer
+	cmd.Stderr = &stderr
+	if err := cmd.Run(); err != nil {
+		return "", fmt.Errorf("clone failed: %v, stderr: %s", err, stderr.String())
+	}
+	if description != "" {
+		os.WriteFile(filepath.Join(repoPath, "description"), []byte(description+"\n"), 0644)
+	}
+	return name, nil
+}
+
 func ListRepos(cfg Config) ([]*Git, error) {
 	entries, err := os.ReadDir(cfg.Dir)
 	if err != nil {
@@ -92,8 +141,18 @@ func ListRepos(cfg Config) ([]*Git, error) {
 	return repos, nil
 }
 
-func (g *Git) Repo() string        { return g.Name }
-func (g *Git) Description() string { return "" }
+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
+}
 
 func (g *Git) List(ref, path string) ([]TreeEntry, error) {
 	args := []string{"ls-tree", "-l", ref}
diff --git a/handlers.go b/handlers.go
index 6733fb9..1516859 100644
--- a/handlers.go
+++ b/handlers.go
@@ -257,6 +257,40 @@ func Diff(cfg Config, tmpl *template.Template) http.HandlerFunc {
 	}
 }
 
+func NewRepoGet(tmpl *template.Template) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		tmpl.ExecuteTemplate(w, "new_repo.html", nil)
+	}
+}
+
+func NewRepoPost(cfg Config, tmpl *template.Template) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		name := strings.TrimSpace(r.FormValue("name"))
+		desc := strings.TrimSpace(r.FormValue("description"))
+		importURL := strings.TrimSpace(r.FormValue("import_url"))
+
+		if importURL != "" {
+			importedName, err := ImportRepo(cfg, importURL, desc)
+			if err != nil {
+				renderError(w, tmpl, http.StatusBadRequest, "failed to import: "+err.Error())
+				return
+			}
+			http.Redirect(w, r, "/"+importedName, http.StatusSeeOther)
+			return
+		}
+
+		if name == "" {
+			renderError(w, tmpl, http.StatusBadRequest, "repository name is required")
+			return
+		}
+		if err := InitRepo(cfg, name, desc); err != nil {
+			renderError(w, tmpl, http.StatusBadRequest, "failed to create repository: "+err.Error())
+			return
+		}
+		http.Redirect(w, r, "/"+name, http.StatusSeeOther)
+	}
+}
+
 func Refs(cfg Config, tmpl *template.Template) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		git := &Git{Dir: cfg.Dir, Name: r.PathValue("repo")}
diff --git a/main.go b/main.go
index 047568e..39fa9f5 100644
--- a/main.go
+++ b/main.go
@@ -25,6 +25,8 @@ func main() {
 	mux := http.NewServeMux()
 
 	mux.HandleFunc("GET /", Repos(cfg, tmpl))
+	mux.HandleFunc("GET /new", NewRepoGet(tmpl))
+	mux.HandleFunc("POST /new", NewRepoPost(cfg, tmpl))
 	mux.HandleFunc("GET /{repo}", Tree(cfg, tmpl))
 	mux.HandleFunc("GET /{repo}/tree/{ref}/{path...}", Tree(cfg, tmpl))
 	mux.HandleFunc("GET /{repo}/blob/{ref}/{path...}", Blob(cfg, tmpl))