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-16 04:51:02
Commitfc3cc2ff51e7d7a6131587eb40f96215adb3a428
Parent1547a71d1d9e9b38a26ba2fd5073ef41efdb4d98

Allow editing of repositories' upstreams

Diffstat

M res/repo/edit.html | 11 +++++++++++
M res/style.css | 7 +++++--
M src/cron/cron.go | 1 +
M src/goit/repo.go | 39 ++++++++++++++++++++++++++++++++++++++-
M src/repo/edit.go | 38 ++++++++++++++++++++++++++++++++++----

5 files changed, 89 insertions, 7 deletions

diff --git a/res/repo/edit.html b/res/repo/edit.html
index 6f99739..4e46261 100644
--- a/res/repo/edit.html
+++ b/res/repo/edit.html
@@ -33,6 +33,17 @@
 						</select>
 					</td>
 				</tr>
+				<tr>
+					<td style="text-align: right;"><label for="upstream">Upstream</label></td>
+					<td><input type="text" name="upstream" value="{{.Edit.Upstream}}" spellcheck="false"></td>
+				</tr>
+				<tr>
+					<td style="text-align: right;"><label for="mirror">Mirror</label></td>
+					<td>
+						<input type="checkbox" name="mirror" value="mirror" {{if .Edit.IsMirror}}checked{{end}}>
+						<span id="mirror-warn">Enabling mirror will replace any existing repository data</span>
+					</td>
+				</tr>
 				<tr>
 					<td></td>
 					<td>
diff --git a/res/style.css b/res/style.css
index e5bb2d1..d84a048 100644
--- a/res/style.css
+++ b/res/style.css
@@ -22,8 +22,8 @@ table input[type="text"] { color: #888888; width: 24em; }
 table input[type="password"] { color: #888888; width: 24em; }
 table input[type="submit"] { color: #FF7E00; padding: 2px 1.6em; }
 table input[type="checkbox"] {
-	appearance: none; border: 2px solid #333333; border-radius: 3px; display:block; height: 1.375rem; margin: 0;
-	padding: 2px; width: 1.375rem;
+	appearance: none; border: 2px solid #333333; border-radius: 3px; display: inline-block; height: 1.375rem;
+	margin: 0; padding: 2px; vertical-align: top; width: 1.375rem;
 }
 table input[type="checkbox"]:hover { background-color: #222222; cursor: pointer; }
 table input[type="checkbox"]::after { content: ""; display: none; position:relative; }
@@ -40,6 +40,9 @@ table textarea {
 	max-height: 18rem; min-height: 6rem; padding: 2px; resize: vertical; width: 24em;
 }
 
+table #mirror-warn { color: #FF7E00; display: none; vertical-align: middle; }
+table input[type="checkbox"]:checked ~ #mirror-warn { display: inline; }
+
 .term-fg1 { font-weight: bold; } /* Bold */
 .term-fg2 { color: #888888; } /* Faint */
 .term-fg3 { font-style: italic; } /* Italic */
diff --git a/src/cron/cron.go b/src/cron/cron.go
index f768fd3..e9568f4 100644
--- a/src/cron/cron.go
+++ b/src/cron/cron.go
@@ -168,6 +168,7 @@ func (c *Cron) Add(rid int64, schedule Schedule, fn func()) uint64 {
 	job.next = job.schedule.Next(time.Now().UTC())
 	c.jobs = append(c.jobs, job)
 
+	log.Println("[cron] added job", job.id, "for", job.rid)
 	return job.id
 }
 
diff --git a/src/goit/repo.go b/src/goit/repo.go
index ac52cfa..7c22f94 100644
--- a/src/goit/repo.go
+++ b/src/goit/repo.go
@@ -189,9 +189,46 @@ 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 {
+			tx.Rollback()
+			return err
+		}
+
+		if err := r.DeleteRemote("origin"); err != nil {
+			tx.Rollback()
+			return err
+		}
+	}
+
+	/* 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 err := r.DeleteRemote("origin"); err != nil && !errors.Is(err, git.ErrRemoteNotFound) {
+			tx.Rollback()
+			return err
+		}
+
+		if _, err := r.CreateRemote(&gitconfig.RemoteConfig{
+			Name:   "origin",
+			URLs:   []string{repo.Upstream},
+			Mirror: util.If(repo.IsMirror, true, false),
+			Fetch:  []gitconfig.RefSpec{gitconfig.RefSpec("+refs/heads/*:refs/heads/*")},
+		}); err != nil {
+			log.Println("[repo/update]", err.Error())
+		}
+	}
+
 	if err := tx.Commit(); err != nil {
 		os.Rename(RepoPath(repo.Name, true), RepoPath(old.Name, true))
-		log.Println("[repo/update]", "error while renaming, check repo \""+old.Name+"\"/\""+repo.Name+"\"")
+		log.Println("[repo/update]", "error while editing, check repo \""+old.Name+"\"/\""+repo.Name+"\"")
 		return err
 	}
 
diff --git a/src/repo/edit.go b/src/repo/edit.go
index c67d406..05598b9 100644
--- a/src/repo/edit.go
+++ b/src/repo/edit.go
@@ -12,6 +12,7 @@ import (
 	"path/filepath"
 	"slices"
 
+	"github.com/Jamozed/Goit/src/cron"
 	"github.com/Jamozed/Goit/src/goit"
 	"github.com/Jamozed/Goit/src/util"
 	"github.com/go-chi/chi/v5"
@@ -60,9 +61,9 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) {
 		Editable                      bool
 
 		Edit struct {
-			Id, Owner, Name, Description string
-			IsPrivate                    bool
-			Message                      string
+			Id, Owner, Name, Description, Upstream string
+			IsPrivate, IsMirror                    bool
+			Message                                string
 		}
 
 		Transfer struct{ Owner, Message string }
@@ -83,7 +84,9 @@ 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.Upstream = repo.Upstream
 	data.Edit.IsPrivate = repo.IsPrivate
+	data.Edit.IsMirror = repo.IsMirror
 
 	gr, err := git.PlainOpen(goit.RepoPath(repo.Name, true))
 	if err != nil {
@@ -113,7 +116,9 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) {
 		case "edit":
 			data.Edit.Name = r.FormValue("reponame")
 			data.Edit.Description = r.FormValue("description")
+			data.Edit.Upstream = r.FormValue("upstream")
 			data.Edit.IsPrivate = r.FormValue("visibility") == "private"
+			data.Edit.IsMirror = r.FormValue("mirror") == "mirror"
 
 			if data.Edit.Name == "" {
 				data.Edit.Message = "Name cannot be empty"
@@ -128,12 +133,37 @@ 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, IsPrivate: data.Edit.IsPrivate,
+				Name: data.Edit.Name, Description: data.Edit.Description, 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)
 				return
 			} else {
+				if (data.Edit.Upstream == "" && repo.Upstream != "") || !data.Edit.IsMirror {
+					goit.Cron.RemoveFor(repo.Id)
+					goit.Cron.Update()
+				} else if data.Edit.Upstream != "" && data.Edit.IsMirror &&
+					(data.Edit.Upstream != repo.Upstream || !repo.IsMirror) {
+					goit.Cron.RemoveFor(repo.Id)
+
+					goit.Cron.Add(repo.Id, cron.Immediate, func() {
+						if err := goit.Pull(repo.Id); err != nil {
+							log.Println("[cron:import]", err.Error())
+						}
+						log.Println("[cron:import] imported", data.Name)
+					})
+
+					goit.Cron.Add(repo.Id, cron.Daily, func() {
+						if err := goit.Pull(repo.Id); err != nil {
+							log.Println("[cron:mirror]", err.Error())
+						}
+						log.Println("[cron:mirror] updated", data.Edit.Name)
+					})
+
+					goit.Cron.Update()
+				}
+
 				http.Redirect(w, r, "/"+data.Edit.Name+"/edit", http.StatusFound)
 				return
 			}