Author | Jakob Wakeling <[email protected]> |
Date | 2023-07-26 10:38:15 |
Commit | 20732d36486576a28971edc4b0b94b050805faae |
Parent | 6f00d17f424fc5a475bb6121163d4b9b625e1e14 |
Properly create repositories through web interface
Diffstat
M | main.go | | | 2 | +- |
M | res/admin/repos.html | | | 2 | +- |
M | res/admin/users.html | | | 2 | +- |
M | res/repo/create.html | | | 47 | ++++++++++++++++++++++++++++++++++------------- |
M | res/repo/log.html | | | 6 | +++--- |
M | res/style.css | | | 13 | +++++++++++++ |
M | src/repo.go | | | 95 | +++++++++++++++++++++++++++++++++---------------------------------------------- |
A | src/repo/create.go | | | 56 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | src/repo/log.go | | | 22 | ++++++++++------------ |
M | src/repo/tree.go | | | 92 | ++++++++++++++++++++++++++++++++++++++++--------------------------------------- |
10 files changed, 206 insertions, 131 deletions
diff --git a/main.go b/main.go index 2beaf53..be5cb26 100644 --- a/main.go +++ b/main.go @@ -28,7 +28,7 @@ func main() { h.Path("/user/login").Methods("GET", "POST").HandlerFunc(goit.HandleUserLogin) h.Path("/user/logout").Methods("GET", "POST").HandlerFunc(goit.HandleUserLogout) h.Path("/user/sessions").Methods("GET", "POST").HandlerFunc(goit.HandleUserSessions) - h.Path("/repo/create").Methods("GET", "POST").HandlerFunc(goit.HandleRepoCreate) + h.Path("/repo/create").Methods("GET", "POST").HandlerFunc(repo.HandleCreate) h.Path("/repo/delete").Methods("DELETE").HandlerFunc(repo.HandleDelete) h.Path("/admin").Methods("GET").HandlerFunc(goit.HandleAdminIndex) h.Path("/admin/users").Methods("GET").HandlerFunc(goit.HandleAdminUsers) diff --git a/res/admin/repos.html b/res/admin/repos.html index e2da387..a0e2997 100644 --- a/res/admin/repos.html +++ b/res/admin/repos.html @@ -11,7 +11,7 @@ </tr> <tr><td> <a href="/admin/users">Users</a> - | <a href="/admin/repos">Repos</a> + | <a href="/admin/repos">Repositories</a> </td></tr> </table> </header><hr> diff --git a/res/admin/users.html b/res/admin/users.html index d37db37..acc0f0d 100644 --- a/res/admin/users.html +++ b/res/admin/users.html @@ -11,7 +11,7 @@ </tr> <tr><td> <a href="/admin/users">Users</a> - | <a href="/admin/repos">Repos</a> + | <a href="/admin/repos">Repositories</a> </td></tr> </table> </header><hr> diff --git a/res/repo/create.html b/res/repo/create.html index 46641ad..7f5f495 100644 --- a/res/repo/create.html +++ b/res/repo/create.html @@ -12,17 +12,38 @@ <tr><td></td></tr> </table> </header> - <form action="/repo/create" method="post"> - <label for="reponame">Name:</label> - <input type="text" name="reponame"><br> - <label for="description">Description</label> - <input type="text" name="description"><br> - <label for="visibility">Visibility:</label> - <select name="visibility"> - <option value="public">Public</option> - <option value="private">Private</option> - </select><br> - <input type="submit" value="Create"> - </form> - <p>{{.Message}}</p> + <main> + <form action="/repo/create" method="post"> + <table> + <tr> + <td style="text-align: right;"><label for="reponame">Name</label></td> + <td><input type="text" name="reponame"></td> + </tr> + <tr> + <td style="text-align: right; vertical-align: top;"><label for="description">Description</label></td> + <td><textarea name="description"></textarea></td> + </tr> + <tr> + <td style="text-align: right;"><label for="visibility">Visibility</label></td> + <td> + <select name="visibility"> + <option value="public">Public</option> + <option value="private">Private</option> + </select> + </td> + </tr> + <tr> + <td></td> + <td> + <input type="submit" value="Create"> + <a href="/" style="color: inherit;">Cancel</a> + </td> + </tr> + <tr> + <td></td> + <td style="color: #AA0000">{{.Message}}</td> + </tr> + </table> + </form> + </main> </body> diff --git a/res/repo/log.html b/res/repo/log.html index f88337e..e814904 100644 --- a/res/repo/log.html +++ b/res/repo/log.html @@ -21,9 +21,9 @@ <td>{{.Date}}</a></td> <td><a href="/{{$.Name}}/commit/{{.Hash}}">{{.Message}}</a></td> <td>{{.Author}}</td> - <td align="right">{{.Files}}</td> - <td align="right" style="color: #008800">{{.Additions}}</td> - <td align="right" style="color: #AA0000">{{.Deletions}}</td> + <td style="text-align: right;">{{.Files}}</td> + <td style="text-align: right;" style="color: #008800">{{.Additions}}</td> + <td style="text-align: right;" style="color: #AA0000">{{.Deletions}}</td> </tr> {{end}} {{else}} diff --git a/res/style.css b/res/style.css index e14b47f..6e14231 100644 --- a/res/style.css +++ b/res/style.css @@ -10,3 +10,16 @@ table td { padding: 0 0.4rem; } table td:empty::after { content: "\00a0"; } main table tr:hover td { background-color: #222222; } + +form table input { border: 2px solid #333333; border-radius: 3px; background-color: #111111; padding: 2px; } +form table input[type="text"] { color: #888888; width: 24em; } +form table input[type="submit"] { color: #FF7E00; width: 6em; } + +form table select { + border: 2px solid #333333; border-radius: 3px; background-color: #111111; color: #888888; padding: 2px; + width: 24em; +} +form table textarea { + border: 2px solid #333333; border-radius: 3px; background-color: #111111; color: #888888; max-height: 18rem; + min-height: 6rem; padding: 2px; resize: vertical; width: 24em; +} diff --git a/src/repo.go b/src/repo.go index 3deed7c..f6ea254 100644 --- a/src/repo.go +++ b/src/repo.go @@ -83,49 +83,6 @@ func HandleIndex(w http.ResponseWriter, r *http.Request) { } } -func HandleRepoCreate(w http.ResponseWriter, r *http.Request) { - ok, uid := AuthCookie(w, r, true) - if !ok { - HttpError(w, http.StatusUnauthorized) - return - } - - data := struct{ Title, Message string }{Title: "Repository - Create"} - - if r.Method == http.MethodPost { - reponame := r.FormValue("reponame") - description := r.FormValue("description") - private := r.FormValue("visibility") == "private" - - if reponame == "" { - data.Message = "Name cannot be empty" - } else if util.SliceContains(reserved, reponame) { - data.Message = "Name \"" + reponame + "\" is reserved" - } else if exists, err := RepoExists(reponame); err != nil { - log.Println("[/repo/create]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } else if exists { - data.Message = "Name \"" + reponame + "\" already exists" - } else if _, err := db.Exec( - `INSERT INTO repos (owner_id, name, name_lower, description, default_branch, is_private) - VALUES (?, ?, ?, ?, ?, ?)`, - uid, reponame, strings.ToLower(reponame), description, "master", private, - ); err != nil { - log.Println("[/repo/create]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } else { - http.Redirect(w, r, "/"+reponame, http.StatusFound) - return - } - } - - if err := Tmpl.ExecuteTemplate(w, "repo/create", data); err != nil { - log.Println("[/repo/create]", err.Error()) - } -} - func HandleRepoRefs(w http.ResponseWriter, r *http.Request) { reponame := mux.Vars(r)["repo"] @@ -216,6 +173,46 @@ func GetRepoByName(name string) (*Repo, error) { return r, nil } +func CreateRepo(repo Repo) error { + tx, err := db.Begin() + if err != nil { + return err + } + + if _, err := tx.Exec( + `INSERT INTO repos (owner_id, name, name_lower, description, default_branch, is_private) + VALUES (?, ?, ?, ?, ?, ?)`, + repo.OwnerId, repo.Name, strings.ToLower(repo.Name), repo.Description, repo.DefaultBranch, repo.IsPrivate, + ); err != nil { + tx.Rollback() + return err + } + + if _, err := git.PlainInit(RepoPath(repo.Name), true); err != nil { + tx.Rollback() + return err + } + + if err := tx.Commit(); err != nil { + os.RemoveAll(RepoPath(repo.Name)) + return err + } + + return nil +} + +func DelRepo(name string) error { + if err := os.RemoveAll(RepoPath(name)); err != nil { + return err + } + + if _, err := db.Exec("DELETE FROM repos WHERE name = ?", name); err != nil { + return err + } + + return nil +} + func RepoExists(name string) (bool, error) { if err := db.QueryRow( "SELECT name FROM repos WHERE name_lower = ?", strings.ToLower(name), @@ -261,15 +258,3 @@ func RepoSize(name string) (uint64, error) { return uint64(size), err } - -func DelRepo(name string) error { - if err := os.RemoveAll(RepoPath(name)); err != nil { - return err - } - - if _, err := db.Exec("DELETE FROM repos WHERE name = ?", name); err != nil { - return err - } - - return nil -} diff --git a/src/repo/create.go b/src/repo/create.go new file mode 100644 index 0000000..f0c158a --- /dev/null +++ b/src/repo/create.go @@ -0,0 +1,56 @@ +package repo + +import ( + "log" + "net/http" + + goit "github.com/Jamozed/Goit/src" + "github.com/Jamozed/Goit/src/util" +) + +var reserved []string = []string{"admin", "repo", "static", "user"} + +func HandleCreate(w http.ResponseWriter, r *http.Request) { + ok, uid := goit.AuthCookie(w, r, true) + if !ok { + goit.HttpError(w, http.StatusUnauthorized) + return + } + + data := struct { + Title, Message string + Name, Description string + IsPrivate bool + }{Title: "Repository - Create"} + + if r.Method == http.MethodPost { + data.Name = r.FormValue("reponame") + data.Description = r.FormValue("description") + data.IsPrivate = r.FormValue("visibility") == "private" + + if data.Name == "" { + data.Message = "Name cannot be empty" + } else if util.SliceContains(reserved, data.Name) { + data.Message = "Name \"" + data.Name + "\" is reserved" + } else if exists, err := goit.RepoExists(data.Name); err != nil { + log.Println("[/repo/create]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } else if exists { + data.Message = "Name \"" + data.Name + "\" is taken" + } else if err := goit.CreateRepo(goit.Repo{ + OwnerId: uid, Name: data.Name, Description: data.Description, IsPrivate: data.IsPrivate, + }); err != nil { + log.Println("[/repo/create]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } else { + http.Redirect(w, r, "/"+data.Name, http.StatusFound) + return + } + } + + if err := goit.Tmpl.ExecuteTemplate(w, "repo/create", data); err != nil { + log.Println("[/repo/create]", err.Error()) + } +} diff --git a/src/repo/log.go b/src/repo/log.go index a971568..01f63ef 100644 --- a/src/repo/log.go +++ b/src/repo/log.go @@ -1,6 +1,7 @@ package repo import ( + "errors" "fmt" "log" "net/http" @@ -10,6 +11,7 @@ import ( goit "github.com/Jamozed/Goit/src" "github.com/Jamozed/Goit/src/util" "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" "github.com/gorilla/mux" ) @@ -53,21 +55,17 @@ func HandleLog(w http.ResponseWriter, r *http.Request) { return } - ref, err := gr.Head() - if err != nil { - log.Println("[/repo/log]", err.Error()) - goit.HttpError(w, http.StatusInternalServerError) - return - } - - iter, err := gr.Log(&git.LogOptions{From: ref.Hash()}) - if err != nil { + if ref, err := gr.Head(); err != nil { + if !errors.Is(err, plumbing.ErrReferenceNotFound) { + log.Println("[/repo/log]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } + } else if iter, err := gr.Log(&git.LogOptions{From: ref.Hash()}); err != nil { log.Println("[/repo/log]", err.Error()) goit.HttpError(w, http.StatusInternalServerError) return - } - - if err := iter.ForEach(func(c *object.Commit) error { + } else if err := iter.ForEach(func(c *object.Commit) error { var files, additions, deletions int if stats, err := c.Stats(); err != nil { diff --git a/src/repo/tree.go b/src/repo/tree.go index 3a61555..3bd72fa 100644 --- a/src/repo/tree.go +++ b/src/repo/tree.go @@ -12,6 +12,7 @@ import ( "github.com/Jamozed/Goit/src/util" "github.com/dustin/go-humanize" "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" "github.com/gorilla/mux" ) @@ -49,67 +50,68 @@ func HandleTree(w http.ResponseWriter, r *http.Request) { return } - ref, err := gr.Head() - if err != nil { - log.Println("[/repo/tree]", err.Error()) - goit.HttpError(w, http.StatusInternalServerError) - return - } - - commit, err := gr.CommitObject(ref.Hash()) - if err != nil { - log.Println("[/repo/tree]", err.Error()) - goit.HttpError(w, http.StatusInternalServerError) - return - } - - tree, err := commit.Tree() - if err != nil { - log.Println("[/repo/tree]", err.Error()) - goit.HttpError(w, http.StatusInternalServerError) - return - } - - if treepath != "" { - data.Files = append(data.Files, row{Mode: "d---------", Name: "..", Path: path.Dir(treepath)}) - - tree, err = tree.Tree(treepath) - if errors.Is(err, object.ErrDirectoryNotFound) { - goit.HttpError(w, http.StatusNotFound) + if ref, err := gr.Head(); err != nil { + if !errors.Is(err, plumbing.ErrReferenceNotFound) { + log.Println("[/repo/tree]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) return - } else if err != nil { + } + } else { + commit, err := gr.CommitObject(ref.Hash()) + if err != nil { log.Println("[/repo/tree]", err.Error()) goit.HttpError(w, http.StatusInternalServerError) return } - } - sort.SliceStable(tree.Entries, func(i, j int) bool { - if tree.Entries[i].Mode&0o40000 != 0 && tree.Entries[j].Mode&0o40000 == 0 { - return true + tree, err := commit.Tree() + if err != nil { + log.Println("[/repo/tree]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return } - return tree.Entries[i].Name < tree.Entries[j].Name - }) + if treepath != "" { + data.Files = append(data.Files, row{Mode: "d---------", Name: "..", Path: path.Dir(treepath)}) - for _, v := range tree.Entries { - size := "" - - if v.Mode&0o40000 == 0 { - file, err := tree.File(v.Name) - if err != nil { + tree, err = tree.Tree(treepath) + if errors.Is(err, object.ErrDirectoryNotFound) { + goit.HttpError(w, http.StatusNotFound) + return + } else if err != nil { log.Println("[/repo/tree]", err.Error()) goit.HttpError(w, http.StatusInternalServerError) return } - - size = humanize.IBytes(uint64(file.Size)) } - data.Files = append(data.Files, row{ - Mode: util.ModeString(uint32(v.Mode)), Name: v.Name, Path: path.Join(treepath, v.Name), Size: size, - B: util.If(strings.HasSuffix(size, " B"), true, false), + sort.SliceStable(tree.Entries, func(i, j int) bool { + if tree.Entries[i].Mode&0o40000 != 0 && tree.Entries[j].Mode&0o40000 == 0 { + return true + } + + return tree.Entries[i].Name < tree.Entries[j].Name }) + + for _, v := range tree.Entries { + size := "" + + if v.Mode&0o40000 == 0 { + file, err := tree.File(v.Name) + if err != nil { + log.Println("[/repo/tree]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + size = humanize.IBytes(uint64(file.Size)) + } + + data.Files = append(data.Files, row{ + Mode: util.ModeString(uint32(v.Mode)), Name: v.Name, Path: path.Join(treepath, v.Name), Size: size, + B: util.If(strings.HasSuffix(size, " B"), true, false), + }) + } } if err := goit.Tmpl.ExecuteTemplate(w, "repo/tree", data); err != nil {