fierj
PublicTiny 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))