您
never=從來沒有
+
[error]
occurred=發生錯誤
report_message=如果您確定這是一個 Gitea 的 bug,請到 GitHub 搜尋相關的問題,如果有需要您也可以建立新問題。
diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go
index c79c34ec42961..b196ce97740de 100644
--- a/routers/api/v1/repo/commits.go
+++ b/routers/api/v1/repo/commits.go
@@ -11,7 +11,6 @@ import (
"net/http"
"strconv"
- repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
@@ -268,17 +267,12 @@ func DownloadCommitDiffOrPatch(ctx *context.APIContext) {
// "$ref": "#/responses/string"
// "404":
// "$ref": "#/responses/notFound"
- repoPath := repo_model.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
- // TODO: use gitRepo from context
- if err := git.GetRawDiff(
- ctx,
- repoPath,
- ctx.Params(":sha"),
- git.RawDiffType(ctx.Params(":diffType")),
- ctx.Resp,
- ); err != nil {
+ sha := ctx.Params(":sha")
+ diffType := git.RawDiffType(ctx.Params(":diffType"))
+
+ if err := git.GetRawDiff(ctx.Repo.GitRepo, sha, diffType, ctx.Resp); err != nil {
if git.IsErrNotExist(err) {
- ctx.NotFound(ctx.Params(":sha"))
+ ctx.NotFound(sha)
return
}
ctx.Error(http.StatusInternalServerError, "DownloadCommitDiffOrPatch", err)
diff --git a/routers/api/v1/repo/main_test.go b/routers/api/v1/repo/main_test.go
index 19e524d014f76..1f91a24937186 100644
--- a/routers/api/v1/repo/main_test.go
+++ b/routers/api/v1/repo/main_test.go
@@ -9,10 +9,15 @@ import (
"testing"
"code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/setting"
+ webhook_service "code.gitea.io/gitea/services/webhook"
)
func TestMain(m *testing.M) {
+ setting.LoadForTest()
+ setting.NewQueueService()
unittest.MainTest(m, &unittest.TestOptions{
GiteaRootPath: filepath.Join("..", "..", "..", ".."),
+ SetUp: webhook_service.Init,
})
}
diff --git a/routers/init.go b/routers/init.go
index 88c393736ef48..403fab00cd3b2 100644
--- a/routers/init.go
+++ b/routers/init.go
@@ -145,7 +145,7 @@ func GlobalInitInstalled(ctx context.Context) {
mustInit(stats_indexer.Init)
mirror_service.InitSyncMirrors()
- webhook.InitDeliverHooks()
+ mustInit(webhook.Init)
mustInit(pull_service.Init)
mustInit(task.Init)
mustInit(repo_migrations.Init)
diff --git a/routers/web/events/events.go b/routers/web/events/events.go
index 02d20550afcd2..d8c6f38d02ae2 100644
--- a/routers/web/events/events.go
+++ b/routers/web/events/events.go
@@ -8,15 +8,10 @@ import (
"net/http"
"time"
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/context"
- "code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/eventsource"
"code.gitea.io/gitea/modules/graceful"
- "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers/web/auth"
)
@@ -71,8 +66,6 @@ func Events(ctx *context.Context) {
timer := time.NewTicker(30 * time.Second)
- stopwatchTimer := time.NewTicker(setting.UI.Notification.MinTimeout)
-
loop:
for {
select {
@@ -93,32 +86,6 @@ loop:
case <-shutdownCtx.Done():
go unregister()
break loop
- case <-stopwatchTimer.C:
- sws, err := models.GetUserStopwatches(ctx.Doer.ID, db.ListOptions{})
- if err != nil {
- log.Error("Unable to GetUserStopwatches: %v", err)
- continue
- }
- apiSWs, err := convert.ToStopWatches(sws)
- if err != nil {
- log.Error("Unable to APIFormat stopwatches: %v", err)
- continue
- }
- dataBs, err := json.Marshal(apiSWs)
- if err != nil {
- log.Error("Unable to marshal stopwatches: %v", err)
- continue
- }
- _, err = (&eventsource.Event{
- Name: "stopwatches",
- Data: string(dataBs),
- }).WriteTo(ctx.Resp)
- if err != nil {
- log.Error("Unable to write to EventStream for user %s: %v", ctx.Doer.Name, err)
- go unregister()
- break loop
- }
- ctx.Resp.Flush()
case event, ok := <-messageChan:
if !ok {
break loop
diff --git a/routers/web/repo/cherry_pick.go b/routers/web/repo/cherry_pick.go
index 926361ccd7686..6667d27410873 100644
--- a/routers/web/repo/cherry_pick.go
+++ b/routers/web/repo/cherry_pick.go
@@ -151,7 +151,7 @@ func CherryPickPost(ctx *context.Context) {
return
}
} else {
- if err := git.GetRawDiff(ctx, ctx.Repo.Repository.RepoPath(), sha, git.RawDiffType("patch"), buf); err != nil {
+ if err := git.GetRawDiff(ctx.Repo.GitRepo, sha, git.RawDiffType("patch"), buf); err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound("GetRawDiff", errors.New("commit "+ctx.Params(":sha")+" does not exist."))
return
diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go
index 2b1b5440d04aa..59d818672a8e1 100644
--- a/routers/web/repo/commit.go
+++ b/routers/web/repo/commit.go
@@ -7,13 +7,13 @@ package repo
import (
"errors"
+ "fmt"
"net/http"
"strings"
"code.gitea.io/gitea/models"
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
- repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset"
@@ -381,15 +381,24 @@ func Diff(ctx *context.Context) {
// RawDiff dumps diff results of repository in given commit ID to io.Writer
func RawDiff(ctx *context.Context) {
- var repoPath string
+ var gitRepo *git.Repository
if ctx.Data["PageIsWiki"] != nil {
- repoPath = ctx.Repo.Repository.WikiPath()
+ wikiRepo, err := git.OpenRepository(ctx, ctx.Repo.Repository.WikiPath())
+ if err != nil {
+ ctx.ServerError("OpenRepository", err)
+ return
+ }
+ defer wikiRepo.Close()
+ gitRepo = wikiRepo
} else {
- repoPath = repo_model.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
+ gitRepo = ctx.Repo.GitRepo
+ if gitRepo == nil {
+ ctx.ServerError("GitRepo not open", fmt.Errorf("no open git repo for '%s'", ctx.Repo.Repository.FullName()))
+ return
+ }
}
if err := git.GetRawDiff(
- ctx,
- repoPath,
+ gitRepo,
ctx.Params(":sha"),
git.RawDiffType(ctx.Params(":ext")),
ctx.Resp,
diff --git a/routers/web/repo/issue_stopwatch.go b/routers/web/repo/issue_stopwatch.go
index 7ef80e77254c8..83e4ecedbf2e2 100644
--- a/routers/web/repo/issue_stopwatch.go
+++ b/routers/web/repo/issue_stopwatch.go
@@ -9,7 +9,9 @@ import (
"strings"
"code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/eventsource"
)
// IssueStopwatch creates or stops a stopwatch for the given issue.
@@ -59,6 +61,18 @@ func CancelStopwatch(c *context.Context) {
return
}
+ stopwatches, err := models.GetUserStopwatches(c.Doer.ID, db.ListOptions{})
+ if err != nil {
+ c.ServerError("GetUserStopwatches", err)
+ return
+ }
+ if len(stopwatches) == 0 {
+ eventsource.GetManager().SendMessage(c.Doer.ID, &eventsource.Event{
+ Name: "stopwatches",
+ Data: "{}",
+ })
+ }
+
url := issue.HTMLURL()
c.Redirect(url, http.StatusSeeOther)
}
diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go
index 7998be53c2831..77744473f1ce3 100644
--- a/services/webhook/deliver.go
+++ b/services/webhook/deliver.go
@@ -15,7 +15,6 @@ import (
"io"
"net/http"
"net/url"
- "strconv"
"strings"
"sync"
"time"
@@ -26,6 +25,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/proxy"
+ "code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
"github.com/gobwas/glob"
@@ -202,10 +202,8 @@ func Deliver(ctx context.Context, t *webhook_model.HookTask) error {
return nil
}
-// DeliverHooks checks and delivers undelivered hooks.
-// FIXME: graceful: This would likely benefit from either a worker pool with dummy queue
-// or a full queue. Then more hooks could be sent at same time.
-func DeliverHooks(ctx context.Context) {
+// populateDeliverHooks checks and delivers undelivered hooks.
+func populateDeliverHooks(ctx context.Context) {
select {
case <-ctx.Done():
return
@@ -226,42 +224,9 @@ func DeliverHooks(ctx context.Context) {
return
default:
}
- if err = Deliver(ctx, t); err != nil {
- log.Error("deliver: %v", err)
- }
- }
-
- // Start listening on new hook requests.
- for {
- select {
- case <-ctx.Done():
- hookQueue.Close()
- return
- case repoIDStr := <-hookQueue.Queue():
- log.Trace("DeliverHooks [repo_id: %v]", repoIDStr)
- hookQueue.Remove(repoIDStr)
-
- repoID, err := strconv.ParseInt(repoIDStr, 10, 64)
- if err != nil {
- log.Error("Invalid repo ID: %s", repoIDStr)
- continue
- }
- tasks, err := webhook_model.FindRepoUndeliveredHookTasks(repoID)
- if err != nil {
- log.Error("Get repository [%d] hook tasks: %v", repoID, err)
- continue
- }
- for _, t := range tasks {
- select {
- case <-ctx.Done():
- return
- default:
- }
- if err = Deliver(ctx, t); err != nil {
- log.Error("deliver: %v", err)
- }
- }
+ if err := addToTask(t.RepoID); err != nil {
+ log.Error("DeliverHook failed [%d]: %v", t.RepoID, err)
}
}
}
@@ -297,8 +262,8 @@ func webhookProxy() func(req *http.Request) (*url.URL, error) {
}
}
-// InitDeliverHooks starts the hooks delivery thread
-func InitDeliverHooks() {
+// Init starts the hooks delivery thread
+func Init() error {
timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second
allowedHostListValue := setting.Webhook.AllowedHostList
@@ -316,5 +281,13 @@ func InitDeliverHooks() {
},
}
- go graceful.GetManager().RunWithShutdownContext(DeliverHooks)
+ hookQueue = queue.CreateUniqueQueue("webhook_sender", handle, "")
+ if hookQueue == nil {
+ return fmt.Errorf("Unable to create webhook_sender Queue")
+ }
+ go graceful.GetManager().RunWithShutdownFns(hookQueue.Run)
+
+ populateDeliverHooks(graceful.GetManager().HammerContext())
+
+ return nil
}
diff --git a/services/webhook/main_test.go b/services/webhook/main_test.go
index 25b9df0af6688..1dc2e1bd83fb3 100644
--- a/services/webhook/main_test.go
+++ b/services/webhook/main_test.go
@@ -9,12 +9,16 @@ import (
"testing"
"code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/setting"
_ "code.gitea.io/gitea/models"
)
func TestMain(m *testing.M) {
+ setting.LoadForTest()
+ setting.NewQueueService()
unittest.MainTest(m, &unittest.TestOptions{
GiteaRootPath: filepath.Join("..", ".."),
+ SetUp: Init,
})
}
diff --git a/services/webhook/webhook.go b/services/webhook/webhook.go
index a3efc7535fc3c..b15b8173f51fe 100644
--- a/services/webhook/webhook.go
+++ b/services/webhook/webhook.go
@@ -12,10 +12,11 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
webhook_model "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/sync"
"code.gitea.io/gitea/modules/util"
"github.com/gobwas/glob"
@@ -80,7 +81,7 @@ func IsValidHookTaskType(name string) bool {
}
// hookQueue is a global queue of web hooks
-var hookQueue = sync.NewUniqueQueue(setting.Webhook.QueueLength)
+var hookQueue queue.UniqueQueue
// getPayloadBranch returns branch for hook event, if applicable.
func getPayloadBranch(p api.Payloader) string {
@@ -101,14 +102,47 @@ func getPayloadBranch(p api.Payloader) string {
return ""
}
+// handle passed PR IDs and test the PRs
+func handle(data ...queue.Data) []queue.Data {
+ for _, datum := range data {
+ repoIDStr := datum.(string)
+ log.Trace("DeliverHooks [repo_id: %v]", repoIDStr)
+
+ repoID, err := strconv.ParseInt(repoIDStr, 10, 64)
+ if err != nil {
+ log.Error("Invalid repo ID: %s", repoIDStr)
+ continue
+ }
+
+ tasks, err := webhook_model.FindRepoUndeliveredHookTasks(repoID)
+ if err != nil {
+ log.Error("Get repository [%d] hook tasks: %v", repoID, err)
+ continue
+ }
+ for _, t := range tasks {
+ if err = Deliver(graceful.GetManager().HammerContext(), t); err != nil {
+ log.Error("deliver: %v", err)
+ }
+ }
+ }
+ return nil
+}
+
+func addToTask(repoID int64) error {
+ err := hookQueue.PushFunc(strconv.FormatInt(repoID, 10), nil)
+ if err != nil && err != queue.ErrAlreadyInQueue {
+ return err
+ }
+ return nil
+}
+
// PrepareWebhook adds special webhook to task queue for given payload.
func PrepareWebhook(w *webhook_model.Webhook, repo *repo_model.Repository, event webhook_model.HookEventType, p api.Payloader) error {
if err := prepareWebhook(w, repo, event, p); err != nil {
return err
}
- go hookQueue.Add(strconv.FormatInt(repo.ID, 10))
- return nil
+ return addToTask(repo.ID)
}
func checkBranch(w *webhook_model.Webhook, branch string) bool {
@@ -188,8 +222,7 @@ func PrepareWebhooks(repo *repo_model.Repository, event webhook_model.HookEventT
return err
}
- go hookQueue.Add(strconv.FormatInt(repo.ID, 10))
- return nil
+ return addToTask(repo.ID)
}
func prepareWebhooks(repo *repo_model.Repository, event webhook_model.HookEventType, p api.Payloader) error {
@@ -240,7 +273,5 @@ func ReplayHookTask(w *webhook_model.Webhook, uuid string) error {
return err
}
- go hookQueue.Add(strconv.FormatInt(t.RepoID, 10))
-
- return nil
+ return addToTask(t.RepoID)
}
diff --git a/templates/repo/diff/compare.tmpl b/templates/repo/diff/compare.tmpl
index 4517f0029c79c..743eaa2efa35c 100644
--- a/templates/repo/diff/compare.tmpl
+++ b/templates/repo/diff/compare.tmpl
@@ -191,7 +191,7 @@
{{.i18n.Tr "repo.pulls.has_pull_request" (Escape $.RepoLink) (Escape $.RepoRelPath) .PullRequest.Index | Safe}}
- {{RenderIssueTitle .PullRequest.Issue.Title $.RepoLink $.Repository.ComposeMetas}}
+ {{RenderIssueTitle $.Context .PullRequest.Issue.Title $.RepoLink $.Repository.ComposeMetas}}
#{{.PullRequest.Issue.Index}}
diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl
index 9e1d83b836bcb..c5efd3d2d4f66 100644
--- a/templates/repo/view_file.tmpl
+++ b/templates/repo/view_file.tmpl
@@ -128,6 +128,9 @@
{{.i18n.Tr "repo.issues.context.reference_issue"}}
{{end}}
+
diff --git a/web_src/js/features/imagediff.js b/web_src/js/features/imagediff.js
index 6fd2ba6c36b78..1745331768482 100644
--- a/web_src/js/features/imagediff.js
+++ b/web_src/js/features/imagediff.js
@@ -4,10 +4,12 @@ function getDefaultSvgBoundsIfUndefined(svgXml, src) {
const DefaultSize = 300;
const MaxSize = 99999;
- const svg = svgXml.rootElement;
-
- const width = svg.width.baseVal;
- const height = svg.height.baseVal;
+ const svg = svgXml.documentElement;
+ const width = svg?.width?.baseVal;
+ const height = svg?.height?.baseVal;
+ if (width === undefined || height === undefined) {
+ return null; // in case some svg is invalid or doesn't have the width/height
+ }
if (width.unitType === SVGLength.SVG_LENGTHTYPE_PERCENTAGE || height.unitType === SVGLength.SVG_LENGTHTYPE_PERCENTAGE) {
const img = new Image();
img.src = src;
@@ -29,6 +31,7 @@ function getDefaultSvgBoundsIfUndefined(svgXml, src) {
height: DefaultSize
};
}
+ return null;
}
export default function initImageDiff() {
@@ -88,6 +91,10 @@ export default function initImageDiff() {
info.$image.on('load', () => {
info.loaded = true;
setReadyIfLoaded();
+ }).on('error', () => {
+ info.loaded = true;
+ setReadyIfLoaded();
+ info.$boundsInfo.text('(image error)');
});
info.$image.attr('src', info.path);
diff --git a/web_src/js/features/repo-code.js b/web_src/js/features/repo-code.js
index a4b6e433a5927..d7b4baac83da6 100644
--- a/web_src/js/features/repo-code.js
+++ b/web_src/js/features/repo-code.js
@@ -15,10 +15,7 @@ function selectRange($list, $select, $from) {
// add hashchange to permalink
const $issue = $('a.ref-in-new-issue');
const $copyPermalink = $('a.copy-line-permalink');
-
- if ($copyPermalink.length === 0) {
- return;
- }
+ const $viewGitBlame = $('a.view_git_blame');
const updateIssueHref = function (anchor) {
if ($issue.length === 0) {
@@ -29,7 +26,22 @@ function selectRange($list, $select, $from) {
$issue.attr('href', href);
};
+ const updateViewGitBlameFragment = function (anchor) {
+ if ($viewGitBlame.length === 0) {
+ return;
+ }
+ let href = $viewGitBlame.attr('href');
+ href = `${href.replace(/#L\d+$|#L\d+-L\d+$/, '')}`;
+ if (anchor.length !== 0) {
+ href = `${href}#${anchor}`;
+ }
+ $viewGitBlame.attr('href', href);
+ };
+
const updateCopyPermalinkHref = function(anchor) {
+ if ($copyPermalink.length === 0) {
+ return;
+ }
let link = $copyPermalink.attr('data-clipboard-text');
link = `${link.replace(/#L\d+$|#L\d+-L\d+$/, '')}#${anchor}`;
$copyPermalink.attr('data-clipboard-text', link);
@@ -53,6 +65,7 @@ function selectRange($list, $select, $from) {
changeHash(`#L${a}-L${b}`);
updateIssueHref(`L${a}-L${b}`);
+ updateViewGitBlameFragment(`L${a}-L${b}`);
updateCopyPermalinkHref(`L${a}-L${b}`);
return;
}
@@ -61,6 +74,7 @@ function selectRange($list, $select, $from) {
changeHash(`#${$select.attr('rel')}`);
updateIssueHref($select.attr('rel'));
+ updateViewGitBlameFragment($select.attr('rel'));
updateCopyPermalinkHref($select.attr('rel'));
}
diff --git a/web_src/js/features/stopwatch.js b/web_src/js/features/stopwatch.js
index f86a80103871f..c47ba221241bd 100644
--- a/web_src/js/features/stopwatch.js
+++ b/web_src/js/features/stopwatch.js
@@ -127,6 +127,10 @@ function updateStopwatchData(data) {
const watch = data[0];
const btnEl = $('.active-stopwatch-trigger');
if (!watch) {
+ if (updateTimeInterval) {
+ clearInterval(updateTimeInterval);
+ updateTimeInterval = null;
+ }
btnEl.addClass('hidden');
} else {
const {repo_owner_name, repo_name, issue_index, seconds} = watch;