Goit

Simple and lightweight Git web server
git clone http://git.omkov.net/Goit
Log | Tree | Refs | README | Download

AuthorJakob Wakeling <[email protected]>
Date2023-12-25 06:52:40
Commit28a8f1350e700754422e9fb338cd5021b529d9a8
Parentd0367aad8e31d8c388f418ef898228986d84d2d8

Support custom default branches

Diffstat

M res/admin/repo_edit.html | 4 ++++
M res/repo/create.html | 4 ++++
M res/repo/edit.html | 4 ++++
M src/admin/repos.go | 13 ++++++++-----
M src/goit/db.go | 22 ++++++++++++++++++++--
M src/goit/repo.go | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
M src/repo/create.go | 12 +++++++-----
M src/repo/edit.go | 14 +++++++++-----

8 files changed, 125 insertions, 47 deletions

diff --git a/res/admin/repo_edit.html b/res/admin/repo_edit.html
index dcad042..e5b9364 100644
--- a/res/admin/repo_edit.html
+++ b/res/admin/repo_edit.html
@@ -24,6 +24,10 @@
 					<td style="text-align:right; vertical-align:top;"><label for="description">Description</label></td>
 					<td><textarea name="description" spellcheck="false">{{.Edit.Description}}</textarea></td>
 				</tr>
+				<tr>
+					<td style="text-align: right;"><label for="branch">Default Branch</label></td>
+					<td><input type="text" name="branch" value="{{.Edit.DefaultBranch}}" placeholder="master"></td>
+				</tr>
 				<tr>
 					<td style="text-align: right;"><label for="visibility">Visibility:</label></td>
 					<td>
diff --git a/res/repo/create.html b/res/repo/create.html
index 918190f..21af36f 100644
--- a/res/repo/create.html
+++ b/res/repo/create.html
@@ -22,6 +22,10 @@
 					<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="branch">Default Branch</label></td>
+					<td><input type="text" name="branch" placeholder="master"></td>
+				</tr>
 				<tr>
 					<td style="text-align: right;"><label for="visibility">Visibility</label></td>
 					<td>
diff --git a/res/repo/edit.html b/res/repo/edit.html
index 4e46261..b09ea4d 100644
--- a/res/repo/edit.html
+++ b/res/repo/edit.html
@@ -24,6 +24,10 @@
 					<td style="text-align:right; vertical-align:top;"><label for="description">Description</label></td>
 					<td><textarea name="description" spellcheck="false">{{.Edit.Description}}</textarea></td>
 				</tr>
+				<tr>
+					<td style="text-align: right;"><label for="branch">Default Branch</label></td>
+					<td><input type="text" name="branch" value="{{.Edit.DefaultBranch}}" placeholder="master"></td>
+				</tr>
 				<tr>
 					<td style="text-align: right;"><label for="visibility">Visibility</label></td>
 					<td>
diff --git a/src/admin/repos.go b/src/admin/repos.go
index 7833bde..80f557c 100644
--- a/src/admin/repos.go
+++ b/src/admin/repos.go
@@ -110,9 +110,10 @@ func HandleRepoEdit(w http.ResponseWriter, r *http.Request) {
 		Title, Name string
 
 		Edit struct {
-			Id, Owner, Name, Description, Upstream string
-			IsPrivate, IsMirror                    bool
-			Message                                string
+			Id, Owner, Name, Description string
+			DefaultBranch, Upstream      string
+			IsPrivate, IsMirror          bool
+			Message                      string
 		}
 
 		Transfer struct{ Owner, Message string }
@@ -130,6 +131,7 @@ func HandleRepoEdit(w http.ResponseWriter, r *http.Request) {
 	data.Edit.Owner = owner.FullName + " (" + owner.Name + ")[" + fmt.Sprint(owner.Id) + "]"
 	data.Edit.Name = repo.Name
 	data.Edit.Description = repo.Description
+	data.Edit.DefaultBranch = repo.DefaultBranch
 	data.Edit.Upstream = repo.Upstream
 	data.Edit.IsPrivate = repo.IsPrivate
 	data.Edit.IsMirror = repo.IsMirror
@@ -139,6 +141,7 @@ func HandleRepoEdit(w http.ResponseWriter, r *http.Request) {
 		case "edit":
 			data.Edit.Name = r.FormValue("reponame")
 			data.Edit.Description = r.FormValue("description")
+			data.Edit.DefaultBranch = util.If(r.FormValue("branch") == "", "master", r.FormValue("branch"))
 			data.Edit.Upstream = r.FormValue("upstream")
 			data.Edit.IsPrivate = r.FormValue("visibility") == "private"
 			data.Edit.IsMirror = r.FormValue("mirror") == "mirror"
@@ -156,8 +159,8 @@ func HandleRepoEdit(w http.ResponseWriter, r *http.Request) {
 			} else if len(data.Edit.Description) > 256 {
 				data.Edit.Message = "Description cannot exceed 256 characters"
 			} else if err := goit.UpdateRepo(repo.Id, goit.Repo{
-				Name: data.Edit.Name, Description: data.Edit.Description, Upstream: data.Edit.Upstream,
-				IsPrivate: data.Edit.IsPrivate, IsMirror: data.Edit.IsMirror,
+				Name: data.Edit.Name, Description: data.Edit.Description, DefaultBranch: data.Edit.DefaultBranch,
+				Upstream: data.Edit.Upstream, IsPrivate: data.Edit.IsPrivate, IsMirror: data.Edit.IsMirror,
 			}); err != nil {
 				log.Println("[/admin/repo/edit]", err.Error())
 				goit.HttpError(w, http.StatusInternalServerError)
diff --git a/src/goit/db.go b/src/goit/db.go
index 546e84e..e3abea7 100644
--- a/src/goit/db.go
+++ b/src/goit/db.go
@@ -32,7 +32,7 @@ import (
 */
 
 func dbUpdate(db *sql.DB) error {
-	latestVersion := 1
+	latestVersion := 2
 
 	var version int
 	if err := db.QueryRow("PRAGMA user_version").Scan(&version); err != nil {
@@ -68,6 +68,7 @@ func dbUpdate(db *sql.DB) error {
 				name TEXT UNIQUE NOT NULL,
 				name_lower TEXT UNIQUE NOT NULL,
 				description TEXT NOT NULL,
+				default_branch TEXT NOT NULL,
 				upstream TEXT NOT NULL,
 				is_private BOOLEAN NOT NULL,
 				is_mirror BOOLEAN NOT NULL
@@ -83,8 +84,25 @@ func dbUpdate(db *sql.DB) error {
 
 	for {
 		switch version {
+		case 1: /* 1 -> 2 */
+			log.Println("Migrating database from version 1 to 2")
+
+			if _, err := db.Exec(
+				"ALTER TABLE repos ADD COLUMN default_branch TEXT NOT NULL DEFAULT 'master'",
+			); err != nil {
+				return err
+			}
+
+			version = 2
 		default: /* No required migrations */
-			return nil
+			goto done
 		}
 	}
+
+done:
+	if _, err := db.Exec(fmt.Sprint("PRAGMA user_version = ", version)); err != nil {
+		return err
+	}
+
+	return nil
 }
diff --git a/src/goit/repo.go b/src/goit/repo.go
index f0d86da..32efa11 100644
--- a/src/goit/repo.go
+++ b/src/goit/repo.go
@@ -14,22 +14,26 @@ import (
 	"github.com/Jamozed/Goit/src/util"
 	"github.com/go-git/go-git/v5"
 	gitconfig "github.com/go-git/go-git/v5/config"
+	"github.com/go-git/go-git/v5/plumbing"
 )
 
 type Repo struct {
-	Id          int64  `json:"id"`
-	OwnerId     int64  `json:"owner_id"`
-	Name        string `json:"name"`
-	Description string `json:"description"`
-	Upstream    string `json:"upstream"`
-	IsPrivate   bool   `json:"is_private"`
-	IsMirror    bool   `json:"is_mirror"`
+	Id            int64  `json:"id"`
+	OwnerId       int64  `json:"owner_id"`
+	Name          string `json:"name"`
+	Description   string `json:"description"`
+	DefaultBranch string `json:"default_branch"`
+	Upstream      string `json:"upstream"`
+	IsPrivate     bool   `json:"is_private"`
+	IsMirror      bool   `json:"is_mirror"`
 }
 
 func GetRepos() ([]Repo, error) {
 	repos := []Repo{}
 
-	rows, err := db.Query("SELECT id, owner_id, name, description, upstream, is_private, is_mirror FROM repos")
+	rows, err := db.Query(
+		"SELECT id, owner_id, name, description, default_branch, upstream, is_private, is_mirror FROM repos",
+	)
 	if err != nil {
 		return nil, err
 	}
@@ -39,7 +43,7 @@ func GetRepos() ([]Repo, error) {
 	for rows.Next() {
 		r := Repo{}
 		if err := rows.Scan(
-			&r.Id, &r.OwnerId, &r.Name, &r.Description, &r.Upstream, &r.IsPrivate, &r.IsMirror,
+			&r.Id, &r.OwnerId, &r.Name, &r.Description, &r.DefaultBranch, &r.Upstream, &r.IsPrivate, &r.IsMirror,
 		); err != nil {
 			return nil, err
 		}
@@ -58,8 +62,11 @@ func GetRepo(rid int64) (*Repo, error) {
 	r := &Repo{}
 
 	if err := db.QueryRow(
-		"SELECT id, owner_id, name, description, upstream, is_private, is_mirror FROM repos WHERE id = ?", rid,
-	).Scan(&r.Id, &r.OwnerId, &r.Name, &r.Description, &r.Upstream, &r.IsPrivate, &r.IsMirror); err != nil {
+		`SELECT id, owner_id, name, description, default_branch, upstream, is_private, is_mirror FROM repos
+		WHERE id = ?`, rid,
+	).Scan(
+		&r.Id, &r.OwnerId, &r.Name, &r.Description, &r.DefaultBranch, &r.Upstream, &r.IsPrivate, &r.IsMirror,
+	); err != nil {
 		if !errors.Is(err, sql.ErrNoRows) {
 			return nil, err
 		}
@@ -74,8 +81,11 @@ func GetRepoByName(name string) (*Repo, error) {
 	r := &Repo{}
 
 	if err := db.QueryRow(
-		"SELECT id, owner_id, name, description, upstream, is_private, is_mirror FROM repos WHERE name = ?", name,
-	).Scan(&r.Id, &r.OwnerId, &r.Name, &r.Description, &r.Upstream, &r.IsPrivate, &r.IsMirror); err != nil {
+		`SELECT id, owner_id, name, description, default_branch, upstream, is_private, is_mirror FROM repos
+		WHERE name = ?`, name,
+	).Scan(
+		&r.Id, &r.OwnerId, &r.Name, &r.Description, &r.DefaultBranch, &r.Upstream, &r.IsPrivate, &r.IsMirror,
+	); err != nil {
 		if !errors.Is(err, sql.ErrNoRows) {
 			return nil, err
 		}
@@ -93,9 +103,9 @@ func CreateRepo(repo Repo) (int64, error) {
 	}
 
 	res, err := tx.Exec(
-		`INSERT INTO repos (owner_id, name, name_lower, description, upstream, is_private, is_mirror)
-		VALUES (?, ?, ?, ?, ?, ?, ?)`, repo.OwnerId, repo.Name, strings.ToLower(repo.Name), repo.Description,
-		repo.Upstream, repo.IsPrivate, repo.IsMirror,
+		`INSERT INTO repos (owner_id, name, name_lower, description, default_branch, upstream, is_private, is_mirror)
+		VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, repo.OwnerId, repo.Name, strings.ToLower(repo.Name), repo.Description,
+		repo.DefaultBranch, repo.Upstream, repo.IsPrivate, repo.IsMirror,
 	)
 	if err != nil {
 		tx.Rollback()
@@ -108,7 +118,9 @@ func CreateRepo(repo Repo) (int64, error) {
 		return -1, err
 	}
 
-	if err := tx.Commit(); err != nil {
+	ref := plumbing.NewSymbolicReference(plumbing.HEAD, plumbing.NewBranchReferenceName(repo.DefaultBranch))
+	if err := r.Storer.SetReference(ref); err != nil {
+		tx.Rollback()
 		os.RemoveAll(RepoPath(repo.Name, true))
 		return -1, err
 	}
@@ -120,12 +132,18 @@ func CreateRepo(repo Repo) (int64, error) {
 			Mirror: util.If(repo.IsMirror, true, false),
 			Fetch:  []gitconfig.RefSpec{gitconfig.RefSpec("+refs/heads/*:refs/heads/*")},
 		}); err != nil {
-			log.Println("[repo/upstream]", err.Error())
+			tx.Rollback()
+			os.RemoveAll(RepoPath(repo.Name, true))
+			return -1, err
 		}
 	}
 
-	rid, _ := res.LastInsertId()
+	if err := tx.Commit(); err != nil {
+		os.RemoveAll(RepoPath(repo.Name, true))
+		return -1, err
+	}
 
+	rid, _ := res.LastInsertId()
 	return rid, nil
 }
 
@@ -175,9 +193,9 @@ func UpdateRepo(rid int64, repo Repo) error {
 	}
 
 	if _, err := tx.Exec(
-		`UPDATE repos SET name = ?, name_lower = ?, description = ?, upstream = ?, is_private = ?, is_mirror = ?
-		WHERE id = ?`, repo.Name, strings.ToLower(repo.Name), repo.Description, repo.Upstream, repo.IsPrivate,
-		repo.IsMirror, rid,
+		`UPDATE repos SET name = ?, name_lower = ?, description = ?, default_branch = ?, upstream = ?, is_private = ?,
+		is_mirror = ? WHERE id = ?`, repo.Name, strings.ToLower(repo.Name), repo.Description, repo.DefaultBranch,
+		repo.Upstream, repo.IsPrivate, repo.IsMirror, rid,
 	); err != nil {
 		tx.Rollback()
 		return err
@@ -195,13 +213,32 @@ func UpdateRepo(rid int64, repo Repo) error {
 		}
 	}
 
-	/* If the upstream URL has been removed, remove the remote */
-	if repo.Upstream == "" && old.Upstream != "" {
-		r, err := git.PlainOpen(RepoPath(repo.Name, true))
-		if err != nil {
+	var r *git.Repository
+	if repo.DefaultBranch != old.DefaultBranch {
+		if r == nil {
+			r, err = git.PlainOpen(RepoPath(repo.Name, true))
+			if err != nil {
+				tx.Rollback()
+				return err
+			}
+		}
+
+		ref := plumbing.NewSymbolicReference(plumbing.HEAD, plumbing.NewBranchReferenceName(repo.DefaultBranch))
+		if err := r.Storer.SetReference(ref); err != nil {
 			tx.Rollback()
 			return err
 		}
+	}
+
+	/* If the upstream URL has been removed, remove the remote */
+	if repo.Upstream == "" && old.Upstream != "" {
+		if r == nil {
+			r, err = git.PlainOpen(RepoPath(repo.Name, true))
+			if err != nil {
+				tx.Rollback()
+				return err
+			}
+		}
 
 		if err := r.DeleteRemote("origin"); err != nil {
 			tx.Rollback()
@@ -211,10 +248,12 @@ func UpdateRepo(rid int64, repo Repo) error {
 
 	/* If the upstream URL has been added or changed, update the remote */
 	if repo.Upstream != "" && repo.Upstream != old.Upstream {
-		r, err := git.PlainOpen(RepoPath(repo.Name, true))
-		if err != nil {
-			tx.Rollback()
-			return err
+		if r == nil {
+			r, err = git.PlainOpen(RepoPath(repo.Name, true))
+			if err != nil {
+				tx.Rollback()
+				return err
+			}
 		}
 
 		if err := r.DeleteRemote("origin"); err != nil && !errors.Is(err, git.ErrRemoteNotFound) {
diff --git a/src/repo/create.go b/src/repo/create.go
index 43fba60..6f380ea 100644
--- a/src/repo/create.go
+++ b/src/repo/create.go
@@ -29,9 +29,10 @@ func HandleCreate(w http.ResponseWriter, r *http.Request) {
 	}
 
 	data := struct {
-		Title, Message         string
-		Name, Description, Url string
-		IsPrivate, IsMirror    bool
+		Title, Message      string
+		Name, Description   string
+		DefaultBranch, Url  string
+		IsPrivate, IsMirror bool
 
 		CsrfField template.HTML
 	}{
@@ -43,6 +44,7 @@ func HandleCreate(w http.ResponseWriter, r *http.Request) {
 	if r.Method == http.MethodPost {
 		data.Name = r.FormValue("reponame")
 		data.Description = r.FormValue("description")
+		data.DefaultBranch = util.If(r.FormValue("branch") == "", "master", r.FormValue("branch"))
 		data.Url = r.FormValue("url")
 		data.IsPrivate = r.FormValue("visibility") == "private"
 		data.IsMirror = r.FormValue("mirror") == "mirror"
@@ -58,8 +60,8 @@ func HandleCreate(w http.ResponseWriter, r *http.Request) {
 		} else if exists {
 			data.Message = "Name \"" + data.Name + "\" is taken"
 		} else if rid, err := goit.CreateRepo(goit.Repo{
-			OwnerId: user.Id, Name: data.Name, Description: data.Description, Upstream: data.Url,
-			IsPrivate: data.IsPrivate, IsMirror: data.IsMirror,
+			OwnerId: user.Id, Name: data.Name, Description: data.Description, DefaultBranch: data.DefaultBranch,
+			Upstream: data.Url, IsPrivate: data.IsPrivate, IsMirror: data.IsMirror,
 		}); err != nil {
 			log.Println("[/repo/create]", err.Error())
 			goit.HttpError(w, http.StatusInternalServerError)
diff --git a/src/repo/edit.go b/src/repo/edit.go
index a12c81c..a709f73 100644
--- a/src/repo/edit.go
+++ b/src/repo/edit.go
@@ -15,6 +15,7 @@ import (
 
 	"github.com/Jamozed/Goit/src/cron"
 	"github.com/Jamozed/Goit/src/goit"
+	"github.com/Jamozed/Goit/src/util"
 	"github.com/go-chi/chi/v5"
 	"github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/plumbing"
@@ -60,9 +61,10 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) {
 		Title string
 
 		Edit struct {
-			Id, Owner, Name, Description, Upstream string
-			IsPrivate, IsMirror                    bool
-			Message                                string
+			Id, Owner, Name, Description string
+			DefaultBranch, Upstream      string
+			IsPrivate, IsMirror          bool
+			Message                      string
 		}
 
 		Transfer struct{ Owner, Message string }
@@ -80,6 +82,7 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) {
 	data.Edit.Owner = owner.FullName + " (" + owner.Name + ")[" + fmt.Sprint(owner.Id) + "]"
 	data.Edit.Name = repo.Name
 	data.Edit.Description = repo.Description
+	data.Edit.DefaultBranch = repo.DefaultBranch
 	data.Edit.Upstream = repo.Upstream
 	data.Edit.IsPrivate = repo.IsPrivate
 	data.Edit.IsMirror = repo.IsMirror
@@ -112,6 +115,7 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) {
 		case "edit":
 			data.Edit.Name = r.FormValue("reponame")
 			data.Edit.Description = r.FormValue("description")
+			data.Edit.DefaultBranch = util.If(r.FormValue("branch") == "", "master", r.FormValue("branch"))
 			data.Edit.Upstream = r.FormValue("upstream")
 			data.Edit.IsPrivate = r.FormValue("visibility") == "private"
 			data.Edit.IsMirror = r.FormValue("mirror") == "mirror"
@@ -129,8 +133,8 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) {
 			} else if len(data.Edit.Description) > 256 {
 				data.Edit.Message = "Description cannot exceed 256 characters"
 			} else if err := goit.UpdateRepo(repo.Id, goit.Repo{
-				Name: data.Edit.Name, Description: data.Edit.Description, Upstream: data.Edit.Upstream,
-				IsPrivate: data.Edit.IsPrivate, IsMirror: data.Edit.IsMirror,
+				Name: data.Edit.Name, Description: data.Edit.Description, DefaultBranch: data.Edit.DefaultBranch,
+				Upstream: data.Edit.Upstream, IsPrivate: data.Edit.IsPrivate, IsMirror: data.Edit.IsMirror,
 			}); err != nil {
 				log.Println("[/repo/edit]", err.Error())
 				goit.HttpError(w, http.StatusInternalServerError)