feat: show CI status on force-pushes (#8655)

If a change is part of a force-push and the commit(s) have a CI status, this will now be shown after the hashes.

`interactiveBorder` has been lowered as it was possible to activate the hover state for both commits. It would be unreasonable to test this within Playwright and thus this needs to be manually tested. On a pull request page that contains a force-push you will notice:
a) the (de)activation area for force-pushes is now smaller, and;
b) it is not possible to activate the hover state/popup for both commits.

ExecuteTemplate function from @gusted
https://codeberg.org/forgejo/forgejo/pulls/5168
https://codeberg.org/forgejo/forgejo/pulls/2884

Close #4932

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8655
Reviewed-by: Otto <otto@codeberg.org>
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
Co-authored-by: Squel <squeljur+git@gmail.com>
Co-committed-by: Squel <squeljur+git@gmail.com>
This commit is contained in:
Squel 2025-08-13 03:29:36 +02:00 committed by Gusted
commit 4abf9e9db4
7 changed files with 70 additions and 12 deletions

View file

@ -795,17 +795,16 @@ func (c *Comment) LoadPushCommits(ctx context.Context) (err error) {
} }
c.OldCommit = data.CommitIDs[0] c.OldCommit = data.CommitIDs[0]
c.NewCommit = data.CommitIDs[1] c.NewCommit = data.CommitIDs[1]
} else {
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, c.Issue.Repo)
if err != nil {
return err
}
defer closer.Close()
c.Commits = git_model.ParseCommitsWithStatus(ctx, gitRepo.GetCommitsFromIDs(data.CommitIDs), c.Issue.Repo)
c.CommitsNum = int64(len(c.Commits))
} }
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, c.Issue.Repo)
if err != nil {
return err
}
defer closer.Close()
c.Commits = git_model.ParseCommitsWithStatus(ctx, gitRepo.GetCommitsFromIDs(data.CommitIDs), c.Issue.Repo)
c.CommitsNum = int64(len(c.Commits))
return err return err
} }

View file

@ -6,6 +6,8 @@
package templates package templates
import ( import (
"bytes"
"context"
"fmt" "fmt"
"html" "html"
"html/template" "html/template"
@ -29,6 +31,23 @@ func NewFuncMap() template.FuncMap {
return map[string]any{ return map[string]any{
"ctx": func() any { return nil }, // template context function "ctx": func() any { return nil }, // template context function
"ExecuteTemplate": func(ctx context.Context, tmplName string, args any) template.HTML {
h := HTMLRenderer()
tmpl, err := h.TemplateLookup(tmplName, ctx)
if err != nil {
panic("Template not found: " + tmplName)
}
buf := bytes.Buffer{}
if err := tmpl.Execute(&buf, args); err != nil {
panic("Error while executing template")
}
// We can safely return this as `template.HTML` as html/template will
// already make sure it's sanitized.
return template.HTML(buf.String())
},
"DumpVar": dumpVar, "DumpVar": dumpVar,
// ----------------------------------------------------------------- // -----------------------------------------------------------------

View file

@ -1804,7 +1804,7 @@ issues.time_spent_from_all_authors = `Total time spent: %s`
issues.due_date = Due date issues.due_date = Due date
issues.push_commit_1 = added %d commit %s issues.push_commit_1 = added %d commit %s
issues.push_commits_n = added %d commits %s issues.push_commits_n = added %d commits %s
issues.force_push_codes = `force-pushed %[1]s from <a class="%[7]s" href="%[3]s"><code>%[2]s</code></a> to <a class="%[7]s" href="%[5]s"><code>%[4]s</code></a> %[6]s` issues.force_push_codes = `force-pushed %[1]s from <a class="%[7]s" href="%[3]s"><code>%[2]s</code></a> %[8]s to <a class="%[7]s" href="%[5]s"><code>%[4]s</code></a> %[9]s %[6]s`
issues.force_push_compare = Compare issues.force_push_compare = Compare
issues.due_date_form = yyyy-mm-dd issues.due_date_form = yyyy-mm-dd
issues.due_date_form_edit = Edit issues.due_date_form_edit = Edit

View file

@ -572,8 +572,12 @@
{{if .IsForcePush}} {{if .IsForcePush}}
<span class="forced-push"> <span class="forced-push">
<span> <span>
{{template "shared/user/authorlink" .Poster}} {{
{{ctx.Locale.Tr "repo.issues.force_push_codes" $.Issue.PullRequest.HeadBranch (ShortSha .OldCommit) ($.Issue.Repo.CommitLink .OldCommit) (ShortSha .NewCommit) ($.Issue.Repo.CommitLink .NewCommit) $createdStr "ui sha"}} template "shared/user/authorlink" .Poster}}
{{ctx.Locale.Tr "repo.issues.force_push_codes" $.Issue.PullRequest.HeadBranch (ShortSha .OldCommit) ($.Issue.Repo.CommitLink .OldCommit) (ShortSha .NewCommit) ($.Issue.Repo.CommitLink .NewCommit) $createdStr "ui sha"
(ExecuteTemplate ctx "repo/commit_statuses" (dict "Status" (index .Commits 0).Status "Statuses" (index .Commits 0).Statuses))
(ExecuteTemplate ctx "repo/commit_statuses" (dict "Status" (index .Commits 1).Status "Statuses" (index .Commits 1).Statuses))
}}
</span> </span>
{{if $.Issue.PullRequest.BaseRepo.Name}} {{if $.Issue.PullRequest.BaseRepo.Name}}
<a href="{{$.Issue.PullRequest.BaseRepo.Link}}/compare/{{PathEscape .OldCommit}}..{{PathEscape .NewCommit}}" rel="nofollow" class="ui compare label">{{ctx.Locale.Tr "repo.issues.force_push_compare"}}</a> <a href="{{$.Issue.PullRequest.BaseRepo.Link}}/compare/{{PathEscape .OldCommit}}..{{PathEscape .NewCommit}}" rel="nofollow" class="ui compare label">{{ctx.Locale.Tr "repo.issues.force_push_compare"}}</a>

View file

@ -4,6 +4,7 @@
package integration package integration
import ( import (
"net/http"
"net/url" "net/url"
"testing" "testing"
@ -17,6 +18,7 @@ import (
"forgejo.org/modules/test" "forgejo.org/modules/test"
"forgejo.org/services/actions" "forgejo.org/services/actions"
"forgejo.org/services/automerge" "forgejo.org/services/automerge"
"forgejo.org/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -48,3 +50,14 @@ func TestActionsAutomerge(t *testing.T) {
}, },
) )
} }
func TestForcePushCommitStatus(t *testing.T) {
defer unittest.OverrideFixtures("tests/integration/fixtures/TestForcePushCommitStatus/")()
defer tests.PrepareTestEnv(t)()
req := NewRequest(t, "GET", "/user2/commitsonpr/pulls/1")
resp := MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
htmlDoc.AssertElement(t, ".forced-push [data-tippy='commit-statuses']:nth-of-type(3) svg.commit-status.octicon-dot-fill", true)
htmlDoc.AssertElement(t, ".forced-push [data-tippy='commit-statuses']:nth-of-type(5) svg.commit-status.octicon-check", true)
}

View file

@ -0,0 +1,22 @@
-
id: 1000
index: 1
repo_id: 58
state: "pending"
sha: "1978192d98bb1b65e11c2cf37da854fbf94bffd6"
target_url: https://example.com/builds/
description: My awesome CI-service
context: ci/awesomeness
context_hash: c65f4d64a3b14a3eced0c9b36799e66e1bd5ced7
creator_id: 2
-
id: 1001
index: 1
repo_id: 58
state: "success"
sha: "9b93963cf6de4dc33f915bb67f192d099c301f43"
target_url: https://example.com/builds/
description: My awesome CI-service
context: ci/awesomeness
context_hash: c65f4d64a3b14a3eced0c9b36799e66e1bd5ced7
creator_id: 2

View file

@ -22,6 +22,7 @@ export function initCommitStatuses() {
interactive: true, interactive: true,
role: 'dialog', role: 'dialog',
theme: 'box-with-header', theme: 'box-with-header',
interactiveBorder: element.closest('.forced-push') ? 0 : 20,
}); });
} }
} }