From 409ff55a293487868cfe034151d84de4cf4b4235 Mon Sep 17 00:00:00 2001
From: zeripath <art27@cantab.net>
Date: Wed, 20 Apr 2022 09:20:53 +0100
Subject: [PATCH] When updating mirror repo intervals by API reschedule next
 update too (#19429)

When a mirror repo interval is updated by the UI it is rescheduled with that interval
however the API does not do this. The API also lacks the enable_prune option.

This PR adds this functionality in to the API Edit Repo endpoint.

Signed-off-by: Andrew Thornton <art27@cantab.net>
---
 modules/structs/repo.go        |  2 +
 routers/api/v1/repo/repo.go    | 76 ++++++++++++++++++++++++----------
 routers/web/repo/setting.go    |  7 +---
 templates/swagger/v1_json.tmpl |  5 +++
 4 files changed, 61 insertions(+), 29 deletions(-)

diff --git a/modules/structs/repo.go b/modules/structs/repo.go
index c21879662..ef247ebc9 100644
--- a/modules/structs/repo.go
+++ b/modules/structs/repo.go
@@ -187,6 +187,8 @@ type EditRepoOption struct {
 	Archived *bool `json:"archived,omitempty"`
 	// set to a string like `8h30m0s` to set the mirror interval time
 	MirrorInterval *string `json:"mirror_interval,omitempty"`
+	// enable prune - remove obsolete remote-tracking references
+	EnablePrune *bool `json:"enable_prune,omitempty"`
 }
 
 // GenerateRepoOption options when creating repository using a template
diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go
index f64550259..7df586445 100644
--- a/routers/api/v1/repo/repo.go
+++ b/routers/api/v1/repo/repo.go
@@ -614,7 +614,7 @@ func Edit(ctx *context.APIContext) {
 	}
 
 	if opts.MirrorInterval != nil {
-		if err := updateMirrorInterval(ctx, opts); err != nil {
+		if err := updateMirror(ctx, opts); err != nil {
 			return
 		}
 	}
@@ -943,37 +943,67 @@ func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) e
 	return nil
 }
 
-// updateMirrorInterval updates the repo's mirror Interval
-func updateMirrorInterval(ctx *context.APIContext, opts api.EditRepoOption) error {
+// updateMirror updates a repo's mirror Interval and EnablePrune
+func updateMirror(ctx *context.APIContext, opts api.EditRepoOption) error {
 	repo := ctx.Repo.Repository
 
+	// only update mirror if interval or enable prune are provided
+	if opts.MirrorInterval == nil && opts.EnablePrune == nil {
+		return nil
+	}
+
+	// these values only make sense if the repo is a mirror
+	if !repo.IsMirror {
+		err := fmt.Errorf("repo is not a mirror, can not change mirror interval")
+		ctx.Error(http.StatusUnprocessableEntity, err.Error(), err)
+		return err
+	}
+
+	// get the mirror from the repo
+	mirror, err := repo_model.GetMirrorByRepoID(repo.ID)
+	if err != nil {
+		log.Error("Failed to get mirror: %s", err)
+		ctx.Error(http.StatusInternalServerError, "MirrorInterval", err)
+		return err
+	}
+
+	// update MirrorInterval
 	if opts.MirrorInterval != nil {
-		if !repo.IsMirror {
-			err := fmt.Errorf("repo is not a mirror, can not change mirror interval")
-			ctx.Error(http.StatusUnprocessableEntity, err.Error(), err)
-			return err
-		}
-		mirror, err := repo_model.GetMirrorByRepoID(repo.ID)
+
+		// MirrorInterval should be a duration
+		interval, err := time.ParseDuration(*opts.MirrorInterval)
 		if err != nil {
-			log.Error("Failed to get mirror: %s", err)
-			ctx.Error(http.StatusInternalServerError, "MirrorInterval", err)
-			return err
-		}
-		if interval, err := time.ParseDuration(*opts.MirrorInterval); err == nil {
-			mirror.Interval = interval
-			mirror.Repo = repo
-			if err := repo_model.UpdateMirror(mirror); err != nil {
-				log.Error("Failed to Set Mirror Interval: %s", err)
-				ctx.Error(http.StatusUnprocessableEntity, "MirrorInterval", err)
-				return err
-			}
-			log.Trace("Repository %s/%s Mirror Interval was Updated to %s", ctx.Repo.Owner.Name, repo.Name, interval)
-		} else {
 			log.Error("Wrong format for MirrorInternal Sent: %s", err)
 			ctx.Error(http.StatusUnprocessableEntity, "MirrorInterval", err)
 			return err
 		}
+
+		// Ensure the provided duration is not too short
+		if interval != 0 && interval < setting.Mirror.MinInterval {
+			err := fmt.Errorf("invalid mirror interval: %s is below minimum interval: %s", interval, setting.Mirror.MinInterval)
+			ctx.Error(http.StatusUnprocessableEntity, "MirrorInterval", err)
+			return err
+		}
+
+		mirror.Interval = interval
+		mirror.Repo = repo
+		mirror.ScheduleNextUpdate()
+		log.Trace("Repository %s Mirror[%d] Set Interval: %s NextUpdateUnix: %s", repo.FullName(), mirror.ID, interval, mirror.NextUpdateUnix)
 	}
+
+	// update EnablePrune
+	if opts.EnablePrune != nil {
+		mirror.EnablePrune = *opts.EnablePrune
+		log.Trace("Repository %s Mirror[%d] Set EnablePrune: %t", repo.FullName(), mirror.ID, mirror.EnablePrune)
+	}
+
+	// finally update the mirror in the DB
+	if err := repo_model.UpdateMirror(mirror); err != nil {
+		log.Error("Failed to Set Mirror Interval: %s", err)
+		ctx.Error(http.StatusUnprocessableEntity, "MirrorInterval", err)
+		return err
+	}
+
 	return nil
 }
 
diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting.go
index ef99eee15..09cb00177 100644
--- a/routers/web/repo/setting.go
+++ b/routers/web/repo/setting.go
@@ -32,7 +32,6 @@ import (
 	"code.gitea.io/gitea/modules/repository"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/structs"
-	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/typesniffer"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/validation"
@@ -195,11 +194,7 @@ func SettingsPost(ctx *context.Context) {
 		} else {
 			ctx.Repo.Mirror.EnablePrune = form.EnablePrune
 			ctx.Repo.Mirror.Interval = interval
-			if interval != 0 {
-				ctx.Repo.Mirror.NextUpdateUnix = timeutil.TimeStampNow().AddDuration(interval)
-			} else {
-				ctx.Repo.Mirror.NextUpdateUnix = 0
-			}
+			ctx.Repo.Mirror.ScheduleNextUpdate()
 			if err := repo_model.UpdateMirror(ctx.Repo.Mirror); err != nil {
 				ctx.Data["Err_Interval"] = true
 				ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form)
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index e35ba9890..de74fd8fa 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -15084,6 +15084,11 @@
           "type": "string",
           "x-go-name": "Description"
         },
+        "enable_prune": {
+          "description": "enable prune - remove obsolete remote-tracking references",
+          "type": "boolean",
+          "x-go-name": "EnablePrune"
+        },
         "external_tracker": {
           "$ref": "#/definitions/ExternalTracker"
         },