From f26f71f1b298a62697fa38ed7026ac1bba3991da Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 4 May 2026 02:53:24 +0800 Subject: [PATCH 1/3] Refactor pull request view (5) (#37517) Clean up templates, remove various CSS patches. By the way, fix incorrect NewRequest URLs in tests. --- templates/base/footer.tmpl | 2 +- templates/devtest/flex-list.tmpl | 21 +++++-- templates/repo/commit_statuses.tmpl | 6 +- .../issue/view_content/pull_merge_box.tmpl | 62 ++++++++++--------- .../view_content/pull_merge_instruction.tmpl | 1 - .../view_content/update_branch_by_merge.tmpl | 1 - templates/repo/pulls/status.tmpl | 42 ++++++------- .../integration/api_actions_artifact_test.go | 8 +-- tests/integration/api_branch_test.go | 2 +- tests/integration/attachment_test.go | 12 ++-- tests/integration/editor_test.go | 24 +++---- tests/integration/integration_test.go | 8 ++- tests/integration/issue_test.go | 6 +- tests/integration/org_profile_test.go | 4 +- tests/integration/pull_create_test.go | 2 +- tests/integration/pull_merge_test.go | 9 +-- tests/integration/pull_review_test.go | 4 +- tests/integration/pull_status_test.go | 6 +- tests/integration/repo_branch_test.go | 4 +- tests/integration/timetracking_test.go | 4 +- web_src/css/repo.css | 49 +++------------ web_src/css/shared/flex-list.css | 17 ++--- web_src/js/features/repo-issue-pull.ts | 17 +++-- 23 files changed, 144 insertions(+), 167 deletions(-) diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl index b7443345ad..81bb4ad3ce 100644 --- a/templates/base/footer.tmpl +++ b/templates/base/footer.tmpl @@ -12,7 +12,7 @@ {{ctx.ScriptImport "js/index.js" "module"}} {{template "custom/footer" .}} diff --git a/templates/devtest/flex-list.tmpl b/templates/devtest/flex-list.tmpl index de59473a70..02f0df9ff9 100644 --- a/templates/devtest/flex-list.tmpl +++ b/templates/devtest/flex-list.tmpl @@ -101,21 +101,30 @@
item 2
-

Flex List (with "ui segment fitted")

-
-
+

Flex List (with "ui segment fitted", items have their own padding)

+
+
item 1
item 2
+
+
item nested 1
+
item nested 2
+
+
item 3
-

If parent provides border or padding:

-
+

If parent provides padding or items need their own flex and/or padding:

+
before divider
-
+
item 1
item 2
+
+
item nested 1
+
item nested 2
+
after divider
diff --git a/templates/repo/commit_statuses.tmpl b/templates/repo/commit_statuses.tmpl index 1bbfb33105..2f9f44fabe 100644 --- a/templates/repo/commit_statuses.tmpl +++ b/templates/repo/commit_statuses.tmpl @@ -9,6 +9,10 @@ {{end}}
- {{template "repo/pulls/status" (dict "CommitStatuses" .Statuses "CommitStatus" .Status)}} +
+
+ {{template "repo/pulls/status" (dict "CommitStatuses" .Statuses "CommitStatus" .Status)}} +
+
{{end}} diff --git a/templates/repo/issue/view_content/pull_merge_box.tmpl b/templates/repo/issue/view_content/pull_merge_box.tmpl index 768d3b3119..2bda4ad83f 100644 --- a/templates/repo/issue/view_content/pull_merge_box.tmpl +++ b/templates/repo/issue/view_content/pull_merge_box.tmpl @@ -27,18 +27,18 @@ {{- else if .Issue.PullRequest.IsStatusMergeable}}tw-text-green {{- else}}tw-text-red{{end}}">{{svg "octicon-git-merge" 40}}
- {{if .LatestCommitStatus}} -
- {{template "repo/pulls/status" (dict - "CommitStatus" .LatestCommitStatus - "CommitStatuses" .LatestCommitStatuses - "ShowHideChecks" true - "StatusCheckData" $statusCheckData - )}} -
- {{end}} - {{$showGeneralMergeForm := false}} -
+
+
+ {{if .LatestCommitStatus}} + {{template "repo/pulls/status" (dict + "CommitStatus" .LatestCommitStatus + "CommitStatuses" .LatestCommitStatuses + "ShowHideChecks" true + "StatusCheckData" $statusCheckData + )}} + {{end}} + + {{$showGeneralMergeForm := false}} {{if .Issue.PullRequest.HasMerged}} {{if .IsPullBranchDeletable}}
@@ -78,7 +78,7 @@ {{svg "octicon-x"}} {{ctx.Locale.Tr "repo.pulls.files_conflicted"}}
-
    +
      {{range .ConflictedFiles}}
    • {{.}}
    • {{else}} @@ -143,7 +143,7 @@ {{svg "octicon-x"}} {{ctx.Locale.TrN $.ChangedProtectedFilesNum "repo.pulls.blocked_by_changed_protected_files_1" "repo.pulls.blocked_by_changed_protected_files_n"}}
-
    +
      {{range .ChangedProtectedFiles}}
    • {{.}}
    • {{end}} @@ -200,7 +200,6 @@ {{template "repo/issue/view_content/update_branch_by_merge" $}} {{if .Issue.PullRequest.IsEmpty}} -
      {{svg "octicon-alert"}} {{ctx.Locale.Tr "repo.pulls.is_empty"}} @@ -209,13 +208,13 @@ {{if .AllowMerge}} {{/* user is allowed to merge */}} {{if $data.MergeFormProps}} -
      {{$showGeneralMergeForm = true}} {{/* The merge form is a Vue component. After mounted, it has a button for choosing merge style, so make it have min-height to avoid layout shifting */}} -
      +
      +
      +
      {{else}} {{/* no merge style was set in repo setting: not or ($prUnit.PullRequestsConfig.AllowMerge ...) */}} -
      {{svg "octicon-x"}} {{ctx.Locale.Tr "repo.pulls.no_merge_desc"}} @@ -227,7 +226,6 @@ {{end}} {{/* end if the repo was set to use any merge style */}} {{else}} {{/* user is not allowed to merge */}} -
      {{svg "octicon-info"}} {{ctx.Locale.Tr "repo.pulls.no_merge_access"}} @@ -260,7 +258,7 @@ {{svg "octicon-x"}} {{ctx.Locale.TrN $.ChangedProtectedFilesNum "repo.pulls.blocked_by_changed_protected_files_1" "repo.pulls.blocked_by_changed_protected_files_n"}}
      -
        +
          {{range .ChangedProtectedFiles}}
        • {{.}}
        • {{end}} @@ -296,20 +294,24 @@ * Then the Manually Merged form will be shown in the merge form */}} {{if and $.StillCanManualMerge (not $showGeneralMergeForm)}} -
          -
          {{/* another similar form is in PullRequestMergeForm.vue*/}} -
          - -
          - -
          +
          +
          {{/* another similar form is in PullRequestMergeForm.vue*/}} +
          + +
          + +
          +
          {{end}} {{if $data.ShowPullCommands}} - {{template "repo/issue/view_content/pull_merge_instruction" dict "PullRequest" .Issue.PullRequest "MergeBoxData" $data}} +
          + {{template "repo/issue/view_content/pull_merge_instruction" dict "PullRequest" .Issue.PullRequest "MergeBoxData" $data}} +
          {{end}} +
diff --git a/templates/repo/issue/view_content/pull_merge_instruction.tmpl b/templates/repo/issue/view_content/pull_merge_instruction.tmpl index ad85c00450..0f24b1b3ea 100644 --- a/templates/repo/issue/view_content/pull_merge_instruction.tmpl +++ b/templates/repo/issue/view_content/pull_merge_instruction.tmpl @@ -1,6 +1,5 @@ {{$data := $.MergeBoxData}} {{$pull := $.PullRequest}} -
{{ctx.Locale.Tr "repo.pulls.cmd_instruction_hint"}}
diff --git a/templates/repo/issue/view_content/update_branch_by_merge.tmpl b/templates/repo/issue/view_content/update_branch_by_merge.tmpl index f6fa66eba7..940da17db1 100644 --- a/templates/repo/issue/view_content/update_branch_by_merge.tmpl +++ b/templates/repo/issue/view_content/update_branch_by_merge.tmpl @@ -1,5 +1,4 @@ {{if and (gt $.Issue.PullRequest.CommitsBehind 0) (not $.Issue.IsClosed) (not $.Issue.PullRequest.IsChecking) (not $.IsPullFilesConflicted) (not $.IsPullRequestBroken)}} -
{{svg "octicon-alert"}} diff --git a/templates/repo/pulls/status.tmpl b/templates/repo/pulls/status.tmpl index 9f972be9d3..3a6c261423 100644 --- a/templates/repo/pulls/status.tmpl +++ b/templates/repo/pulls/status.tmpl @@ -6,22 +6,19 @@ */}} {{$statusCheckData := .StatusCheckData}} {{if .CommitStatus}} -
-
- {{$statusCheckData.CommitStatusCheckPrompt ctx.Locale}} - + {{if $statusCheckData}} +
+
{{$statusCheckData.CommitStatusCheckPrompt ctx.Locale}}
{{if .ShowHideChecks}} -
- -
+ data-hide-all="{{ctx.Locale.Tr "repo.pulls.status_checks_hide_all"}}" + >{{ctx.Locale.Tr "repo.pulls.status_checks_hide_all"}} {{end}}
- + {{end}} {{if and $statusCheckData $statusCheckData.RequireApprovalRunCount}} -
+
{{ctx.Locale.Tr "repo.pulls.status_checks_need_approvals" $statusCheckData.RequireApprovalRunCount}} @@ -36,28 +33,31 @@
{{end}} -
+
{{range .CommitStatuses}} -
- {{template "repo/commit_status" .}} -
{{.Context}} {{.Description}}
-
+
+
+ {{template "repo/commit_status" .}} +
{{.Context}} {{.Description}}
+
+
{{if and $statusCheckData $statusCheckData.IsContextRequired}} {{if (call $statusCheckData.IsContextRequired .Context)}}
{{ctx.Locale.Tr "repo.pulls.status_checks_requested"}}
{{end}} {{end}} - {{if .TargetURL}}{{ctx.Locale.Tr "repo.pulls.status_checks_details"}}{{end}} + {{if .TargetURL}}{{ctx.Locale.Tr "repo.pulls.status_checks_details"}}{{end}}
{{end}} {{if $statusCheckData}} {{range $statusCheckData.MissingRequiredChecks}} -
- {{svg "octicon-dot-fill" 18 "commit-status icon tw-text-yellow"}} -
{{.}}
+
+
+ {{svg "octicon-dot-fill" 16 "commit-status icon tw-text-yellow"}} +
{{.}}
+
{{ctx.Locale.Tr "repo.pulls.status_checks_requested"}}
{{end}} {{end}}
-
{{end}} diff --git a/tests/integration/api_actions_artifact_test.go b/tests/integration/api_actions_artifact_test.go index 0a4a63c551..4670bb0704 100644 --- a/tests/integration/api_actions_artifact_test.go +++ b/tests/integration/api_actions_artifact_test.go @@ -149,7 +149,7 @@ func TestActionsArtifactDownload(t *testing.T) { assert.Contains(t, listResp.Value[artifactIdx].FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts") idx := strings.Index(listResp.Value[artifactIdx].FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/") - url := listResp.Value[artifactIdx].FileContainerResourceURL[idx+1:] + "?itemPath=artifact-download" + url := listResp.Value[artifactIdx].FileContainerResourceURL[idx:] + "?itemPath=artifact-download" req = NewRequest(t, "GET", url). AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") resp = MakeRequest(t, req, http.StatusOK) @@ -245,7 +245,7 @@ func TestActionsArtifactDownloadMultiFiles(t *testing.T) { assert.Contains(t, fileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts") idx := strings.Index(fileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/") - url := fileContainerResourceURL[idx+1:] + "?itemPath=" + testArtifactName + url := fileContainerResourceURL[idx:] + "?itemPath=" + testArtifactName req = NewRequest(t, "GET", url). AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") resp = MakeRequest(t, req, http.StatusOK) @@ -323,7 +323,7 @@ func TestActionsArtifactOverwrite(t *testing.T) { listResp := DecodeJSON(t, resp, &listArtifactsResponse{}) idx := strings.Index(listResp.Value[0].FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/") - url := listResp.Value[0].FileContainerResourceURL[idx+1:] + "?itemPath=artifact-download" + url := listResp.Value[0].FileContainerResourceURL[idx:] + "?itemPath=artifact-download" req = NewRequest(t, "GET", url). AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") resp = MakeRequest(t, req, http.StatusOK) @@ -380,7 +380,7 @@ func TestActionsArtifactOverwrite(t *testing.T) { assert.Equal(t, "artifact-download", uploadedItem.Name) idx := strings.Index(uploadedItem.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/") - url := uploadedItem.FileContainerResourceURL[idx+1:] + "?itemPath=artifact-download" + url := uploadedItem.FileContainerResourceURL[idx:] + "?itemPath=artifact-download" req = NewRequest(t, "GET", url). AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") resp = MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/api_branch_test.go b/tests/integration/api_branch_test.go index 5bf5360549..d67ba98f7c 100644 --- a/tests/integration/api_branch_test.go +++ b/tests/integration/api_branch_test.go @@ -348,7 +348,7 @@ func TestAPIUpdateBranchReference(t *testing.T) { func testAPIRenameBranch(t *testing.T, doerName, ownerName, repoName, from, to string, expectedHTTPStatus int) *httptest.ResponseRecorder { token := getUserToken(t, doerName, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestWithJSON(t, "PATCH", "api/v1/repos/"+ownerName+"/"+repoName+"/branches/"+from, &api.RenameBranchRepoOption{ + req := NewRequestWithJSON(t, "PATCH", "/api/v1/repos/"+ownerName+"/"+repoName+"/branches/"+from, &api.RenameBranchRepoOption{ Name: to, }).AddTokenAuth(token) return MakeRequest(t, req, expectedHTTPStatus) diff --git a/tests/integration/attachment_test.go b/tests/integration/attachment_test.go index db6928beac..a00ba98b96 100644 --- a/tests/integration/attachment_test.go +++ b/tests/integration/attachment_test.go @@ -96,18 +96,18 @@ func testUploadAttachmentDeleteTemp(t *testing.T) { defer web.RouteMock(route_web.RouterMockPointBeforeWebRoutes, func(resp http.ResponseWriter, req *http.Request) { tmpFileCountDuringUpload = countTmpFile() })() - _ = testCreateIssueAttachment(t, session, "user2/repo1", "image.png", testGeneratePngBytes(), http.StatusOK) + _ = testCreateIssueAttachment(t, session, "/user2/repo1", "image.png", testGeneratePngBytes(), http.StatusOK) assert.Equal(t, 1, tmpFileCountDuringUpload, "the temp file should exist when uploaded size exceeds the parse form's max memory") assert.Equal(t, 0, countTmpFile(), "the temp file should be deleted after upload") } func testCreateAnonymousAttachment(t *testing.T) { session := emptyTestSession(t) - testCreateIssueAttachment(t, session, "user2/repo1", "image.png", testGeneratePngBytes(), http.StatusSeeOther) + testCreateIssueAttachment(t, session, "/user2/repo1", "image.png", testGeneratePngBytes(), http.StatusSeeOther) } func testCreateUser2IssueAttachment(t *testing.T) { - const repoURL = "user2/repo1" + const repoURL = "/user2/repo1" session := loginUser(t, "user2") uuid := testCreateIssueAttachment(t, session, repoURL, "image.png", testGeneratePngBytes(), http.StatusOK) @@ -177,7 +177,7 @@ func testGetAttachment(t *testing.T) { } func testDeleteAttachmentPermissions(t *testing.T) { - const repoURL = "user2/repo1" + const repoURL = "/user2/repo1" ownerSession := loginUser(t, "user2") readonlySession := loginUser(t, "user5") @@ -191,12 +191,12 @@ func testDeleteAttachmentPermissions(t *testing.T) { testCreateReleaseAttachment(t, readonlySession, repoURL, "reader-release.png", testGeneratePngBytes(), http.StatusNotFound) crossRepoUUID := testCreateIssueAttachment(t, ownerSession, repoURL, "cross-repo.png", testGeneratePngBytes(), http.StatusOK) - testDeleteIssueAttachment(t, ownerSession, "user2/repo2", crossRepoUUID, http.StatusBadRequest) + testDeleteIssueAttachment(t, ownerSession, "/user2/repo2", crossRepoUUID, http.StatusBadRequest) testDeleteIssueAttachment(t, ownerSession, repoURL, crossRepoUUID, http.StatusOK) releaseUUID := testCreateReleaseAttachment(t, ownerSession, repoURL, "reader-release.png", testGeneratePngBytes(), http.StatusOK) testDeleteReleaseAttachment(t, ownerSession, repoURL, releaseUUID, http.StatusOK) // test deleting release attachment from another repo - testDeleteReleaseAttachment(t, ownerSession, "user2/repo2", crossRepoUUID, http.StatusBadRequest) + testDeleteReleaseAttachment(t, ownerSession, "/user2/repo2", crossRepoUUID, http.StatusBadRequest) } diff --git a/tests/integration/editor_test.go b/tests/integration/editor_test.go index 18b3a9400d..4a06159690 100644 --- a/tests/integration/editor_test.go +++ b/tests/integration/editor_test.go @@ -124,7 +124,7 @@ func testEditorActionEdit(t *testing.T, session *TestSession, user, repo, editor resp := testEditorActionPostRequest(t, session, fmt.Sprintf("/%s/%s/%s/%s/%s", user, repo, editorAction, branch, filePath), params) assert.Equal(t, http.StatusOK, resp.Code) assert.NotEmpty(t, test.RedirectURL(resp)) - req := NewRequest(t, "GET", path.Join(user, repo, "raw/branch", newBranchName, params["tree_path"])) + req := NewRequest(t, "GET", "/"+path.Join(user, repo, "raw/branch", newBranchName, params["tree_path"])) resp = session.MakeRequest(t, req, http.StatusOK) assert.Equal(t, params["content"], resp.Body.String()) return resp @@ -330,18 +330,18 @@ index 0000000000..bbbbbbbbbb func testForkToEditFile(t *testing.T, session *TestSession, user, owner, repo, branch, filePath string) { forkToEdit := func(t *testing.T, session *TestSession, owner, repo, operation, branch, filePath string) { // visit the base repo, see the "Add File" button - req := NewRequest(t, "GET", path.Join(owner, repo)) + req := NewRequest(t, "GET", "/"+path.Join(owner, repo)) resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) AssertHTMLElement(t, htmlDoc, ".repo-add-file", 1) // attempt to edit a file, see the guideline page - req = NewRequest(t, "GET", path.Join(owner, repo, operation, branch, filePath)) + req = NewRequest(t, "GET", "/"+path.Join(owner, repo, operation, branch, filePath)) resp = session.MakeRequest(t, req, http.StatusOK) assert.Contains(t, resp.Body.String(), "Fork Repository to Propose Changes") // fork the repository - req = NewRequest(t, "POST", path.Join(owner, repo, "_fork", branch)) + req = NewRequest(t, "POST", "/"+path.Join(owner, repo, "_fork", branch)) resp = session.MakeRequest(t, req, http.StatusOK) assert.JSONEq(t, `{"redirect":""}`, resp.Body.String()) } @@ -351,7 +351,7 @@ func testForkToEditFile(t *testing.T, session *TestSession, user, owner, repo, b forkToEdit(t, session, owner, repo, "_edit", branch, filePath) // Archive the repository - req := NewRequestWithValues(t, "POST", path.Join(user, repo, "settings"), + req := NewRequestWithValues(t, "POST", "/"+path.Join(user, repo, "settings"), map[string]string{ "repo_name": repo, "action": "archive", @@ -360,12 +360,12 @@ func testForkToEditFile(t *testing.T, session *TestSession, user, owner, repo, b session.MakeRequest(t, req, http.StatusSeeOther) // Check editing archived repository is disabled - req = NewRequest(t, "GET", path.Join(owner, repo, "_edit", branch, filePath)).SetHeader("Accept", "text/html") + req = NewRequest(t, "GET", "/"+path.Join(owner, repo, "_edit", branch, filePath)).SetHeader("Accept", "text/html") resp := session.MakeRequest(t, req, http.StatusNotFound) assert.Contains(t, resp.Body.String(), "You have forked this repository but your fork is not editable.") // Unfork the repository - req = NewRequestWithValues(t, "POST", path.Join(user, repo, "settings"), + req = NewRequestWithValues(t, "POST", "/"+path.Join(user, repo, "settings"), map[string]string{ "repo_name": repo, "action": "convert_fork", @@ -381,7 +381,7 @@ func testForkToEditFile(t *testing.T, session *TestSession, user, owner, repo, b t.Run("CheckBaseRepoForm", func(t *testing.T) { // the base repo's edit form should have the correct action and upload links (pointing to the forked repo) - req := NewRequest(t, "GET", path.Join(owner, repo, "_upload", branch, filePath)+"?foo=bar") + req := NewRequest(t, "GET", "/"+path.Join(owner, repo, "_upload", branch, filePath)+"?foo=bar") resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) @@ -399,7 +399,7 @@ func testForkToEditFile(t *testing.T, session *TestSession, user, owner, repo, b }) t.Run("ViewBaseEditFormAndCommitToFork", func(t *testing.T) { - req := NewRequest(t, "GET", path.Join(owner, repo, "_edit", branch, filePath)) + req := NewRequest(t, "GET", "/"+path.Join(owner, repo, "_edit", branch, filePath)) resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) editRequestForm := map[string]string{ @@ -437,16 +437,16 @@ func testEditFileNotAllowed(t *testing.T) { for _, operation := range operations { t.Run(operation, func(t *testing.T) { // Branch does not exist - targetLink := path.Join("user2", "repo1", operation, "missing", "README.md") + targetLink := path.Join("/user2/repo1", operation, "missing", "README.md") sessionUser1.MakeRequest(t, NewRequest(t, "GET", targetLink), http.StatusNotFound) // Private repository - targetLink = path.Join("user2", "repo2", operation, "master", "Home.md") + targetLink = path.Join("/user2/repo2", operation, "master", "Home.md") sessionUser1.MakeRequest(t, NewRequest(t, "GET", targetLink), http.StatusOK) sessionUser4.MakeRequest(t, NewRequest(t, "GET", targetLink), http.StatusNotFound) // Empty repository - targetLink = path.Join("org41", "repo61", operation, "master", "README.md") + targetLink = path.Join("/org41/repo61", operation, "master", "README.md") sessionUser1.MakeRequest(t, NewRequest(t, "GET", targetLink), http.StatusNotFound) }) } diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index 62aea184f3..72157458b9 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -326,14 +326,18 @@ func NewRequestWithJSON(t testing.TB, method, urlStr string, v any) *RequestWrap func NewRequestWithBody(t testing.TB, method, urlStr string, body io.Reader) *RequestWrapper { t.Helper() - if !strings.HasPrefix(urlStr, "http") && !strings.HasPrefix(urlStr, "/") { - urlStr = "/" + urlStr + if !strings.HasPrefix(urlStr, "http:") && !strings.HasPrefix(urlStr, "https:") && !strings.HasPrefix(urlStr, "/") { + t.Fatalf("invalid url str: %s", urlStr) } req, err := http.NewRequest(method, urlStr, body) require.NoError(t, err) if req.URL.User != nil { req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(req.URL.User.String()))) } + req.RequestURI = req.URL.Path + if req.URL.RawQuery != "" { + req.RequestURI += "?" + req.URL.RawQuery + } return &RequestWrapper{req} } diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go index 29a1d665af..09475bad20 100644 --- a/tests/integration/issue_test.go +++ b/tests/integration/issue_test.go @@ -123,7 +123,7 @@ func TestNoLoginViewIssue(t *testing.T) { } func testNewIssue(t *testing.T, session *TestSession, user, repo, title, content string) string { - req := NewRequest(t, "GET", path.Join(user, repo, "issues", "new")) + req := NewRequest(t, "GET", "/"+path.Join(user, repo, "issues", "new")) resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) @@ -667,7 +667,7 @@ func TestUpdateIssueDeadline(t *testing.T) { assert.Equal(t, api.StateOpen, issueBefore.State()) session := loginUser(t, owner.Name) - urlStr := fmt.Sprintf("%s/%s/issues/%d/deadline", owner.Name, repoBefore.Name, issueBefore.Index) + urlStr := fmt.Sprintf("/%s/%s/issues/%d/deadline", owner.Name, repoBefore.Name, issueBefore.Index) req := NewRequestWithValues(t, "POST", urlStr, map[string]string{"deadline": "2022-04-06"}) session.MakeRequest(t, req, http.StatusOK) @@ -687,7 +687,7 @@ func TestIssueReferenceURL(t *testing.T) { issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - req := NewRequest(t, "GET", fmt.Sprintf("%s/issues/%d", repo.FullName(), issue.Index)) + req := NewRequest(t, "GET", fmt.Sprintf("%s/issues/%d", repo.Link(), issue.Index)) resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) diff --git a/tests/integration/org_profile_test.go b/tests/integration/org_profile_test.go index 73cafd85c2..69d7999bb9 100644 --- a/tests/integration/org_profile_test.go +++ b/tests/integration/org_profile_test.go @@ -66,7 +66,7 @@ func testOrgProfile(t *testing.T, u *url.URL) { createTestProfile(t, "org3", user.RepoNameProfilePrivate, contentPrivateReadme) // Anonymous User - req := NewRequest(t, "GET", "org3") + req := NewRequest(t, "GET", "/org3") resp := MakeRequest(t, req, http.StatusOK) bodyString := util.UnsafeBytesToString(resp.Body.Bytes()) assert.Contains(t, bodyString, contentPublicReadme) @@ -74,7 +74,7 @@ func testOrgProfile(t *testing.T, u *url.URL) { // Logged in but not member session := loginUser(t, "user24") - req = NewRequest(t, "GET", "org3") + req = NewRequest(t, "GET", "/org3") resp = session.MakeRequest(t, req, http.StatusOK) bodyString = util.UnsafeBytesToString(resp.Body.Bytes()) assert.Contains(t, bodyString, contentPublicReadme) diff --git a/tests/integration/pull_create_test.go b/tests/integration/pull_create_test.go index fa10158a57..48e8c96d38 100644 --- a/tests/integration/pull_create_test.go +++ b/tests/integration/pull_create_test.go @@ -26,7 +26,7 @@ import ( ) func testPullCreate(t *testing.T, session *TestSession, user, repo string, toSelf bool, targetBranch, sourceBranch, title string) *httptest.ResponseRecorder { - req := NewRequest(t, "GET", path.Join(user, repo)) + req := NewRequest(t, "GET", "/"+path.Join(user, repo)) resp := session.MakeRequest(t, req, http.StatusOK) // Click the PR button to create a pull diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index 3d24c4c326..83cf1d7605 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -80,7 +80,7 @@ func testPullMerge(t *testing.T, session *TestSession, user, repo, pullNum strin } func testPullCleanUp(t *testing.T, session *TestSession, user, repo, pullnum string) *httptest.ResponseRecorder { - req := NewRequest(t, "GET", path.Join(user, repo, "pulls", pullnum)) + req := NewRequest(t, "GET", "/"+path.Join(user, repo, "pulls", pullnum)) resp := session.MakeRequest(t, req, http.StatusOK) // Click the little button to create a pull @@ -322,11 +322,8 @@ func TestCantMergeWorkInProgress(t *testing.T) { req := NewRequest(t, "GET", test.RedirectURL(resp)) resp = session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - text := strings.TrimSpace(htmlDoc.doc.Find(".merge-section > .item").Last().Text()) - assert.NotEmpty(t, text, "Can't find WIP text") - - assert.Contains(t, text, translation.NewLocale("en-US").TrString("repo.pulls.cannot_merge_work_in_progress"), "Unable to find WIP text") - assert.Contains(t, text, "[wip]", "Unable to find WIP text") + wipToggleButtonCount := htmlDoc.Find(`.merge-section > .item button[data-global-init="initPullRequestWipToggle"]`).Length() + assert.Equal(t, 1, wipToggleButtonCount) }) } diff --git a/tests/integration/pull_review_test.go b/tests/integration/pull_review_test.go index 7edbd0b9ce..b38a037870 100644 --- a/tests/integration/pull_review_test.go +++ b/tests/integration/pull_review_test.go @@ -264,13 +264,13 @@ func testSubmitReview(t *testing.T, session *TestSession, owner, repo, pullNumbe "type": reviewType, } - submitURL := path.Join(owner, repo, "pulls", pullNumber, "files", "reviews", "submit") + submitURL := "/" + path.Join(owner, repo, "pulls", pullNumber, "files", "reviews", "submit") req := NewRequestWithValues(t, "POST", submitURL, options) return session.MakeRequest(t, req, expectedSubmitStatus) } func testIssueClose(t *testing.T, session *TestSession, owner, repo, issueNumber string) *httptest.ResponseRecorder { - closeURL := path.Join(owner, repo, "issues", issueNumber, "comments") + closeURL := "/" + path.Join(owner, repo, "issues", issueNumber, "comments") options := map[string]string{ "status": "close", diff --git a/tests/integration/pull_status_test.go b/tests/integration/pull_status_test.go index 3b8924b6e6..b2e6273bad 100644 --- a/tests/integration/pull_status_test.go +++ b/tests/integration/pull_status_test.go @@ -31,7 +31,7 @@ func TestPullCreate_CommitStatus(t *testing.T) { testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "status1", "README.md", "status1") - url := path.Join("user1", "repo1", "compare", "master...status1") + url := "/" + path.Join("user1", "repo1", "compare", "master...status1") req := NewRequestWithValues(t, "POST", url, map[string]string{ "title": "pull request from status1", @@ -121,7 +121,7 @@ func TestPullCreate_EmptyChangesWithDifferentCommits(t *testing.T) { testEditFileToNewBranch(t, session, "user1", "repo1", "master", "status1", "README.md", "status1") testEditFile(t, session, "user1", "repo1", "status1", "README.md", "# repo1\n\nDescription for repo1") - url := path.Join("user1", "repo1", "compare", "master...status1") + url := "/" + path.Join("user1", "repo1", "compare", "master...status1") req := NewRequestWithValues(t, "POST", url, map[string]string{ "title": "pull request from status1", @@ -143,7 +143,7 @@ func TestPullCreate_EmptyChangesWithSameCommits(t *testing.T) { session := loginUser(t, "user1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testCreateBranch(t, session, "user1", "repo1", "branch/master", "status1", http.StatusSeeOther) - url := path.Join("user1", "repo1", "compare", "master...status1") + url := "/" + path.Join("user1", "repo1", "compare", "master...status1") req := NewRequestWithValues(t, "POST", url, map[string]string{ "title": "pull request from status1", diff --git a/tests/integration/repo_branch_test.go b/tests/integration/repo_branch_test.go index 6cf9990d2e..792ffb6011 100644 --- a/tests/integration/repo_branch_test.go +++ b/tests/integration/repo_branch_test.go @@ -21,7 +21,7 @@ import ( ) func testCreateBranch(t testing.TB, session *TestSession, user, repo, oldRefSubURL, newBranchName string, expectedStatus int) string { - req := NewRequestWithValues(t, "POST", path.Join(user, repo, "branches/_new", oldRefSubURL), map[string]string{ + req := NewRequestWithValues(t, "POST", "/"+path.Join(user, repo, "branches/_new", oldRefSubURL), map[string]string{ "new_branch_name": newBranchName, }) resp := session.MakeRequest(t, req, expectedStatus) @@ -221,7 +221,7 @@ func prepareRepoPR(t *testing.T, baseSession, headSession *TestSession, baseRepo func checkRecentlyPushedNewBranches(t *testing.T, session *TestSession, repoPath string, expected []string) { branches := make([]string, 0, 2) - req := NewRequest(t, "GET", repoPath) + req := NewRequest(t, "GET", "/"+repoPath) resp := session.MakeRequest(t, req, http.StatusOK) doc := NewHTMLParser(t, resp.Body) doc.doc.Find(".ui.positive.message div a").Each(func(index int, branch *goquery.Selection) { diff --git a/tests/integration/timetracking_test.go b/tests/integration/timetracking_test.go index 3dec6db2e8..e4d8927856 100644 --- a/tests/integration/timetracking_test.go +++ b/tests/integration/timetracking_test.go @@ -37,7 +37,7 @@ func TestViewTimetrackingControls(t *testing.T) { } func testViewTimetrackingControls(t *testing.T, session *TestSession, user, repo, issue string, canTrackTime bool) { - req := NewRequest(t, "GET", path.Join(user, repo, "issues", issue)) + req := NewRequest(t, "GET", "/"+path.Join(user, repo, "issues", issue)) resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) @@ -45,7 +45,7 @@ func testViewTimetrackingControls(t *testing.T, session *TestSession, user, repo AssertHTMLElement(t, htmlDoc, ".issue-start-time", canTrackTime) AssertHTMLElement(t, htmlDoc, ".issue-add-time", canTrackTime) - issueLink := path.Join(user, repo, "issues", issue) + issueLink := "/" + path.Join(user, repo, "issues", issue) reqStart := NewRequest(t, "POST", path.Join(issueLink, "times", "stopwatch", "start")) if canTrackTime { session.MakeRequest(t, reqStart, http.StatusOK) diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 26ef377e62..d2407c898d 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -596,8 +596,10 @@ td .commit-summary { } } -.repository.view.issue .comment-list .comment .merge-section { - background-color: var(--color-box-body); +.repository.view.issue .comment-list .comment .merge-section .item + ul.item { + border-top: 0; + padding: 0 1em 0 52px; + margin-top: -0.5em; } .repository.view.issue .comment-list .comment .merge-section .item-section { @@ -605,15 +607,9 @@ td .commit-summary { flex-wrap: wrap; align-items: center; justify-content: space-between; - padding: 0; gap: 0.5em; } -.repository.view.issue .comment-list .comment .merge-section .divider { - margin-left: -1rem; - width: calc(100% + 2rem); -} - .merge-section-info code { border: 1px solid var(--color-light-border); border-radius: var(--border-radius); @@ -1937,42 +1933,11 @@ tbody.commit-list { .commit-status-item { height: 40px; - padding: 0 10px; display: flex; - gap: 8px; + gap: var(--gap-block); align-items: center; -} - -.commit-status-item + .commit-status-item { - border-top: 1px solid var(--color-secondary); -} - -.commit-status-item .commit-status { - flex-shrink: 0; -} - -.commit-status-item .status-context { - color: var(--color-text); - flex: 1; -} - -.commit-status-item .status-details { - display: flex; - align-items: center; - justify-content: flex-end; - gap: 8px; -} - -@media (max-width: 767.98px) { - .commit-status-item .status-details { - flex-direction: column; - align-items: flex-end; - justify-content: center; - } -} - -.commit-status-item .status-details > span { - padding-right: 0.5em; /* To match the alignment with the "required" label */ + justify-content: space-between; + flex-wrap: wrap; } .username-display { diff --git a/web_src/css/shared/flex-list.css b/web_src/css/shared/flex-list.css index 93a3a1b3f9..ea4314290a 100644 --- a/web_src/css/shared/flex-list.css +++ b/web_src/css/shared/flex-list.css @@ -10,10 +10,12 @@ } /* items have dividers between them, the dividers align with items (use parent padding) */ -.flex-divided-list { - list-style: none; +.flex-divided-list, +.flex-divided-list > .item.flex-divided-list { display: flex; flex-direction: column; + align-items: stretch; + gap: 0; } .flex-divided-list > .item { @@ -99,22 +101,21 @@ } /* special rules to make the list work with existing UI elements */ -.container-segmented > .flex-divided-list > .item { +.container-divided > .flex-divided-list > .item { padding-left: 1em; padding-right: 1em; } - -.ui.segment.fitted > .flex-divided-list > .item { - padding: 1em; +.container-divided > .flex-divided-list > .item.flex-divided-list { + padding: 0; } .container-padded > .flex-divided-list > .item:first-child, -.ui.segment:not(.fitted) > .flex-divided-list > .item:first-child { +.ui.segment:not(.container-divided) > .flex-divided-list > .item:first-child { padding-top: 0; } .container-padded > .flex-divided-list > .item:last-child, -.ui.segment:not(.fitted) > .flex-divided-list > .item:last-child { +.ui.segment:not(.container-divided) > .flex-divided-list > .item:last-child { padding-bottom: 0; } diff --git a/web_src/js/features/repo-issue-pull.ts b/web_src/js/features/repo-issue-pull.ts index 33e072a1a5..05aaf57796 100644 --- a/web_src/js/features/repo-issue-pull.ts +++ b/web_src/js/features/repo-issue-pull.ts @@ -2,6 +2,7 @@ import {createApp} from 'vue'; import {GET, POST} from '../modules/fetch.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; import {createElementFromHTML} from '../utils/dom.ts'; +import {registerGlobalEventFunc} from '../modules/observer.ts'; function initRepoPullRequestUpdate(el: HTMLElement) { const prUpdateButtonContainer = el.querySelector('#update-pr-branch-with-base'); @@ -48,15 +49,11 @@ function initRepoPullRequestUpdate(el: HTMLElement) { }); } -function initRepoPullRequestCommitStatus(el: HTMLElement) { - for (const btn of el.querySelectorAll('.commit-status-hide-checks')) { - const panel = btn.closest('.commit-status-panel')!; - const list = panel.querySelector('.commit-status-list')!; - btn.addEventListener('click', () => { - list.style.maxHeight = list.style.maxHeight ? '' : '0px'; // toggle - btn.textContent = btn.getAttribute(list.style.maxHeight ? 'data-show-all' : 'data-hide-all'); - }); - } +function onCommitStatusChecksToggle(btn: HTMLElement) { + const panel = btn.closest('.commit-status-toggle')!.parentElement!; + const list = panel.querySelector('.commit-status-list')!; + list.style.maxHeight = list.style.maxHeight ? '' : '0px'; // toggle + btn.textContent = btn.getAttribute(list.style.maxHeight ? 'data-show-all' : 'data-hide-all'); } async function initRepoPullRequestMergeForm(box: HTMLElement) { @@ -70,7 +67,7 @@ async function initRepoPullRequestMergeForm(box: HTMLElement) { } export function initRepoPullMergeBox(el: HTMLElement) { - initRepoPullRequestCommitStatus(el); + registerGlobalEventFunc('click', 'onCommitStatusChecksToggle', onCommitStatusChecksToggle); initRepoPullRequestUpdate(el); initRepoPullRequestMergeForm(el); From dd17521808437a00963bcc3e61bda85f5f782bdd Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 4 May 2026 14:15:33 +0800 Subject: [PATCH 2/3] Refactor pull request view (6) (#37522) Clean up legacy logic. * Use backend logic to choose PR timeline icon color * Always use the Vue form to merge, remove the "StillCanManualMerge" logic --- routers/web/repo/issue_view.go | 21 +-- routers/web/repo/pull.go | 21 ++- routers/web/repo/pull_merge_box.go | 33 ++++ routers/web/repo/pull_merge_form.go | 115 ++++++++----- templates/devtest/flex-list.tmpl | 18 +-- templates/repo/commit_statuses.tmpl | 6 +- .../issue/view_content/pull_merge_box.tmpl | 152 ++++-------------- .../pull_merge_status_checks.tmpl | 35 ++++ .../view_content/update_branch_by_merge.tmpl | 40 ++--- templates/repo/pulls/status.tmpl | 63 -------- templates/repo/pulls/status_items.tmpl | 32 ++++ web_src/css/modules/segment.css | 9 +- web_src/css/repo.css | 1 + web_src/css/shared/flex-list.css | 23 ++- .../js/components/PullRequestMergeForm.vue | 39 +++-- web_src/js/features/repo-issue-pull.ts | 1 + 16 files changed, 293 insertions(+), 316 deletions(-) create mode 100644 routers/web/repo/pull_merge_box.go create mode 100644 templates/repo/issue/view_content/pull_merge_status_checks.tmpl delete mode 100644 templates/repo/pulls/status.tmpl create mode 100644 templates/repo/pulls/status_items.tmpl diff --git a/routers/web/repo/issue_view.go b/routers/web/repo/issue_view.go index 852b880ab0..28fdfb34fa 100644 --- a/routers/web/repo/issue_view.go +++ b/routers/web/repo/issue_view.go @@ -835,14 +835,14 @@ func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue * } canDelete := false - allowMerge := false canWriteToHeadRepo := false pull_service.StartPullRequestCheckOnView(ctx, pull) if !prInfo.IsPullRequestBroken { + data.ShowUpdatePullInfo = pull.CommitsBehind > 0 && !issue.IsClosed && !pull.IsChecking() && !pull.IsFilesConflicted() && !prInfo.IsPullRequestBroken var err error - ctx.Data["UpdateAllowed"], ctx.Data["UpdateByRebaseAllowed"], err = pull_service.IsUserAllowedToUpdate(ctx, pull, ctx.Doer) + data.UpdateAllowed, data.UpdateByRebaseAllowed, err = pull_service.IsUserAllowedToUpdate(ctx, pull, ctx.Doer) if err != nil { ctx.ServerError("IsUserAllowedToUpdate", err) return @@ -888,7 +888,7 @@ func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue * if !canWriteToHeadRepo { // maintainers maybe allowed to push to head repo even if they can't write to it canWriteToHeadRepo = pull.AllowMaintainerEdit && perm.CanWrite(unit.TypeCode) } - allowMerge, err = pull_service.IsUserAllowedToMerge(ctx, pull, perm, ctx.Doer) + data.allowMerge, err = pull_service.IsUserAllowedToMerge(ctx, pull, perm, ctx.Doer) if err != nil { ctx.ServerError("IsUserAllowedToMerge", err) return @@ -903,7 +903,7 @@ func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue * data.ReloadingInterval = util.Iif(pull.IsChecking(), 2000, 0) data.ShowMergeInstructions = canWriteToHeadRepo data.ShowPullCommands = pull.HeadRepo != nil && !pull.HasMerged && !issue.IsClosed - ctx.Data["AllowMerge"] = allowMerge + ctx.Data["AllowMerge"] = data.allowMerge pb := prInfo.ProtectedBranchRule if pb != nil { @@ -947,18 +947,6 @@ func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue * prConfig := issue.Repo.MustGetUnit(ctx, unit.TypePullRequests).PullRequestsConfig() data.AutodetectManualMerge = prConfig.AutodetectManualMerge - stillCanManualMerge := func() bool { - if pull.HasMerged || issue.IsClosed || !ctx.IsSigned { - return false - } - if pull.IsStatusMergeable() || pull.IsWorkInProgress(ctx) || pull.IsChecking() { - return false - } - return allowMerge && prConfig.AllowManualMerge - } - - ctx.Data["StillCanManualMerge"] = stillCanManualMerge() - enableStatusCheck := pb != nil && pb.EnableStatusCheck ctx.Data["EnableStatusCheck"] = enableStatusCheck @@ -989,6 +977,7 @@ func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue * ctx.Data["PullMergeBoxData"] = prInfo.MergeBoxData prInfo.prepareMergeBoxFormProps(ctx) + prInfo.prepareMergeBoxIconColor() } func prepareIssueViewContent(ctx *context.Context, issue *issues_model.Issue) { diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 7406c1d122..404f77beb7 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -266,8 +266,15 @@ type pullMergeBoxData struct { ShowMergeBox bool ReloadingInterval int + TimelineIconClass string + HasOverridableBlockers bool - CanMergeNow bool + CanMergeNow bool // PR is mergeable, either no blocker, or doer is admin and can bypass the blockers + allowMerge bool // doer has permission to merge + + ShowUpdatePullInfo bool + UpdateAllowed bool + UpdateByRebaseAllowed bool MergeFormProps map[string]any ShowPullCommands bool @@ -302,6 +309,9 @@ type pullRequestViewInfo struct { StatusCheckData *pullCommitStatusCheckData CommitStatuses []*git_model.CommitStatus MergeBoxData *pullMergeBoxData + + enableStatusCheck bool + workInProgressPrefix string } func newPullRequestViewInfo() *pullRequestViewInfo { @@ -430,8 +440,8 @@ func (prInfo *pullRequestViewInfo) prepareViewFillCommitStatusInfoForOpen(ctx *c } pb := prInfo.ProtectedBranchRule - enableStatusCheck := pb != nil && pb.EnableStatusCheck - if !enableStatusCheck { + prInfo.enableStatusCheck = pb != nil && pb.EnableStatusCheck + if !prInfo.enableStatusCheck { return } @@ -549,9 +559,10 @@ func (prInfo *pullRequestViewInfo) prepareViewOpenPullInfo(ctx *context.Context) } // this one is used by both sidebar and merge-box + prInfo.workInProgressPrefix = pull.GetWorkInProgressPrefix(ctx) if pull.IsWorkInProgress(ctx) { - ctx.Data["IsPullWorkInProgress"] = true - ctx.Data["WorkInProgressPrefix"] = pull.GetWorkInProgressPrefix(ctx) + ctx.Data["IsPullWorkInProgress"] = prInfo.workInProgressPrefix != "" + ctx.Data["WorkInProgressPrefix"] = prInfo.workInProgressPrefix } } diff --git a/routers/web/repo/pull_merge_box.go b/routers/web/repo/pull_merge_box.go new file mode 100644 index 0000000000..4163d98cbf --- /dev/null +++ b/routers/web/repo/pull_merge_box.go @@ -0,0 +1,33 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +func (prInfo *pullRequestViewInfo) prepareMergeBoxIconColor() { + pull := prInfo.issue.PullRequest + mergeBoxData := prInfo.MergeBoxData + statusCheckData := prInfo.StatusCheckData + switch { + case pull.HasMerged: + prInfo.MergeBoxData.TimelineIconClass = "tw-text-purple" + case prInfo.issue.IsClosed, prInfo.workInProgressPrefix != "", pull.IsFilesConflicted(): + prInfo.MergeBoxData.TimelineIconClass = "tw-text-text-light" + case prInfo.IsPullRequestBroken, mergeBoxData.isBlockedByApprovals, mergeBoxData.isBlockedByRejection, + mergeBoxData.isBlockedByOfficialReviewRequests, mergeBoxData.isBlockedByOutdatedBranch, mergeBoxData.isBlockedByChangedProtectedFiles: + prInfo.MergeBoxData.TimelineIconClass = "tw-text-red" + case prInfo.enableStatusCheck && (statusCheckData.RequiredChecksState.IsFailure() || statusCheckData.RequiredChecksState.IsError()): + prInfo.MergeBoxData.TimelineIconClass = "tw-text-red" + case prInfo.enableStatusCheck && (statusCheckData.LatestCommitStatus == nil || statusCheckData.RequiredChecksState.IsPending() || statusCheckData.RequiredChecksState.IsWarning()): + prInfo.MergeBoxData.TimelineIconClass = "tw-text-yellow" + case mergeBoxData.allowMerge && mergeBoxData.requireSigned && !mergeBoxData.willSign: + prInfo.MergeBoxData.TimelineIconClass = "tw-text-red" + case pull.IsChecking(): + prInfo.MergeBoxData.TimelineIconClass = "tw-text-yellow" + case pull.IsEmpty(): + prInfo.MergeBoxData.TimelineIconClass = "tw-text-text-light" + case pull.IsStatusMergeable(): + prInfo.MergeBoxData.TimelineIconClass = "tw-text-green" + default: + prInfo.MergeBoxData.TimelineIconClass = "tw-text-red" + } +} diff --git a/routers/web/repo/pull_merge_form.go b/routers/web/repo/pull_merge_form.go index b390fd6934..0cc2bfc27b 100644 --- a/routers/web/repo/pull_merge_form.go +++ b/routers/web/repo/pull_merge_form.go @@ -16,6 +16,13 @@ import ( func (prInfo *pullRequestViewInfo) prepareMergeBoxFormProps(ctx *context.Context) { pull := prInfo.issue.PullRequest + if pull.HasMerged || prInfo.issue.IsClosed { + return + } + if !prInfo.MergeBoxData.allowMerge { + return + } + prConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypePullRequests).PullRequestsConfig() // Check correct values and select default @@ -69,7 +76,7 @@ func (prInfo *pullRequestViewInfo) prepareMergeBoxFormProps(ctx *context.Context } allOverridableChecksOk := !prInfo.MergeBoxData.HasOverridableBlockers - prInfo.MergeBoxData.MergeFormProps = map[string]any{ + mergeFormProps := map[string]any{ "baseLink": prInfo.issue.Link(), "textCancel": ctx.Locale.Tr("cancel"), "textDeleteBranch": ctx.Locale.Tr("repo.branch.delete", prInfo.headTarget), @@ -97,51 +104,75 @@ func (prInfo *pullRequestViewInfo) prepareMergeBoxFormProps(ctx *context.Context // if this pr can be merged now, then hide the auto merge generalHideAutoMerge := prInfo.MergeBoxData.CanMergeNow && allOverridableChecksOk - prInfo.MergeBoxData.MergeFormProps["mergeStyles"] = []any{ - map[string]any{ - "name": "merge", - "allowed": prConfig.AllowMerge, - "textDoMerge": ctx.Locale.Tr("repo.pulls.merge_pull_request"), - "mergeTitleFieldText": defaultMergeTitle, - "mergeMessageFieldText": defaultMergeBody, - "hideAutoMerge": generalHideAutoMerge, - }, - map[string]any{ - "name": "rebase", - "allowed": prConfig.AllowRebase, - "textDoMerge": ctx.Locale.Tr("repo.pulls.rebase_merge_pull_request"), - "hideMergeMessageTexts": true, - "hideAutoMerge": generalHideAutoMerge, - }, - map[string]any{ - "name": "rebase-merge", - "allowed": prConfig.AllowRebaseMerge, - "textDoMerge": ctx.Locale.Tr("repo.pulls.rebase_merge_commit_pull_request"), - "mergeTitleFieldText": defaultMergeTitle, - "mergeMessageFieldText": defaultMergeBody, - "hideAutoMerge": generalHideAutoMerge, - }, - map[string]any{ - "name": "squash", - "allowed": prConfig.AllowSquash, - "textDoMerge": ctx.Locale.Tr("repo.pulls.squash_merge_pull_request"), - "mergeTitleFieldText": defaultSquashMergeTitle, - "mergeMessageFieldText": defaultSquashMergeCommitMessages + defaultSquashMergeBody, - "hideAutoMerge": generalHideAutoMerge, - }, - map[string]any{ - "name": "fast-forward-only", - "allowed": prConfig.AllowFastForwardOnly && pull.CommitsBehind == 0, - "textDoMerge": ctx.Locale.Tr("repo.pulls.fast_forward_only_merge_pull_request"), - "hideMergeMessageTexts": true, - "hideAutoMerge": generalHideAutoMerge, - }, - map[string]any{ + var mergeStyles []any + if pull.IsStatusMergeable() { + mergeStyles = []any{ + map[string]any{ + "name": "merge", + "allowed": prConfig.AllowMerge, + "textDoMerge": ctx.Locale.Tr("repo.pulls.merge_pull_request"), + "mergeTitleFieldText": defaultMergeTitle, + "mergeMessageFieldText": defaultMergeBody, + "hideAutoMerge": generalHideAutoMerge, + }, + map[string]any{ + "name": "rebase", + "allowed": prConfig.AllowRebase, + "textDoMerge": ctx.Locale.Tr("repo.pulls.rebase_merge_pull_request"), + "hideMergeMessageTexts": true, + "hideAutoMerge": generalHideAutoMerge, + }, + map[string]any{ + "name": "rebase-merge", + "allowed": prConfig.AllowRebaseMerge, + "textDoMerge": ctx.Locale.Tr("repo.pulls.rebase_merge_commit_pull_request"), + "mergeTitleFieldText": defaultMergeTitle, + "mergeMessageFieldText": defaultMergeBody, + "hideAutoMerge": generalHideAutoMerge, + }, + map[string]any{ + "name": "squash", + "allowed": prConfig.AllowSquash, + "textDoMerge": ctx.Locale.Tr("repo.pulls.squash_merge_pull_request"), + "mergeTitleFieldText": defaultSquashMergeTitle, + "mergeMessageFieldText": defaultSquashMergeCommitMessages + defaultSquashMergeBody, + "hideAutoMerge": generalHideAutoMerge, + }, + map[string]any{ + "name": "fast-forward-only", + "allowed": prConfig.AllowFastForwardOnly && pull.CommitsBehind == 0, + "textDoMerge": ctx.Locale.Tr("repo.pulls.fast_forward_only_merge_pull_request"), + "hideMergeMessageTexts": true, + "hideAutoMerge": generalHideAutoMerge, + }, + } + } + + canUseManualMerge := func() bool { + if pull.IsWorkInProgress(ctx) || pull.IsChecking() { + return false + } + return prConfig.AllowManualMerge + } + // Manually Merged is not a well-known feature, it is used to mark a non-mergeable PR (already merged, conflicted) as merged + // To test it: + // Enable "Manually Merged" feature in the Repository Settings + // Create a pull request, either: + // - Merge the pull request branch locally and push the merged commit to Gitea + // - Make some conflicts between the base branch and the pull request branch + // Then the Manually Merged form will be shown in the merge form + if canUseManualMerge() { + mergeStyles = append(mergeStyles, map[string]any{ "name": "manually-merged", "allowed": prConfig.AllowManualMerge, "textDoMerge": ctx.Locale.Tr("repo.pulls.merge_manually"), "hideMergeMessageTexts": true, "hideAutoMerge": true, - }, + }) + } + + if len(mergeStyles) > 0 { + mergeFormProps["mergeStyles"] = mergeStyles + prInfo.MergeBoxData.MergeFormProps = mergeFormProps } } diff --git a/templates/devtest/flex-list.tmpl b/templates/devtest/flex-list.tmpl index 02f0df9ff9..2203020244 100644 --- a/templates/devtest/flex-list.tmpl +++ b/templates/devtest/flex-list.tmpl @@ -102,34 +102,26 @@

Flex List (with "ui segment fitted", items have their own padding)

-
-
+
+
item 1
item 2
-
-
item nested 1
-
item nested 2
-
item 3

If parent provides padding or items need their own flex and/or padding:

-
+
before divider
-
+
item 1
item 2
-
-
item nested 1
-
item nested 2
-
after divider
-
+
before divider
diff --git a/templates/repo/commit_statuses.tmpl b/templates/repo/commit_statuses.tmpl index 2f9f44fabe..4575827ceb 100644 --- a/templates/repo/commit_statuses.tmpl +++ b/templates/repo/commit_statuses.tmpl @@ -9,10 +9,8 @@ {{end}}
-
-
- {{template "repo/pulls/status" (dict "CommitStatuses" .Statuses "CommitStatus" .Status)}} -
+
+ {{template "repo/pulls/status_items" (dict "CommitStatuses" .Statuses)}}
{{end}} diff --git a/templates/repo/issue/view_content/pull_merge_box.tmpl b/templates/repo/issue/view_content/pull_merge_box.tmpl index 2bda4ad83f..610016e27c 100644 --- a/templates/repo/issue/view_content/pull_merge_box.tmpl +++ b/templates/repo/issue/view_content/pull_merge_box.tmpl @@ -9,36 +9,17 @@ > {{$statusCheckData := .StatusCheckData}} {{$requiredStatusCheckState := $statusCheckData.RequiredChecksState}} -
{{svg "octicon-git-merge" 40}}
+
{{svg "octicon-git-merge" 40}}
-
-
+
+
{{if .LatestCommitStatus}} - {{template "repo/pulls/status" (dict - "CommitStatus" .LatestCommitStatus + {{template "repo/issue/view_content/pull_merge_status_checks" (dict "CommitStatuses" .LatestCommitStatuses - "ShowHideChecks" true "StatusCheckData" $statusCheckData )}} {{end}} - {{$showGeneralMergeForm := false}} {{if .Issue.PullRequest.HasMerged}} {{if .IsPullBranchDeletable}}
@@ -113,7 +94,7 @@ {{svg "octicon-alert"}} {{ctx.Locale.Tr "repo.pulls.is_ancestor"}}
- {{else if or .Issue.PullRequest.IsStatusMergeable .Issue.PullRequest.IsEmpty}} + {{else}} {{if .IsBlockedByApprovals}}
{{svg "octicon-x"}} @@ -167,6 +148,15 @@ {{svg "octicon-unlock"}} {{ctx.Locale.Tr (printf "repo.signing.wont_sign.%s" .WontSignReason)}}
+ {{else if not .Issue.PullRequest.IsStatusMergeable}} +
+ {{svg "octicon-x"}} + {{ctx.Locale.Tr "repo.pulls.cannot_auto_merge_desc"}} +
+
+ {{svg "octicon-info"}} + {{ctx.Locale.Tr "repo.pulls.cannot_auto_merge_helper"}} +
{{end}} {{$notAllOverridableChecksOk := $data.HasOverridableBlockers}} @@ -197,112 +187,38 @@ {{end}} {{end}} - {{template "repo/issue/view_content/update_branch_by_merge" $}} - {{if .Issue.PullRequest.IsEmpty}} -
- {{svg "octicon-alert"}} - {{ctx.Locale.Tr "repo.pulls.is_empty"}} -
+
+ {{svg "octicon-alert"}} + {{ctx.Locale.Tr "repo.pulls.is_empty"}} +
{{end}} - {{if .AllowMerge}} {{/* user is allowed to merge */}} - {{if $data.MergeFormProps}} - {{$showGeneralMergeForm = true}} - {{/* The merge form is a Vue component. After mounted, it has a button for choosing merge style, so make it have min-height to avoid layout shifting */}} -
-
-
- {{else}} - {{/* no merge style was set in repo setting: not or ($prUnit.PullRequestsConfig.AllowMerge ...) */}} -
- {{svg "octicon-x"}} - {{ctx.Locale.Tr "repo.pulls.no_merge_desc"}} -
-
- {{svg "octicon-info"}} - {{ctx.Locale.Tr "repo.pulls.no_merge_helper"}} -
- {{end}} {{/* end if the repo was set to use any merge style */}} - {{else}} + {{template "repo/issue/view_content/update_branch_by_merge" (dict "MergeBoxData" $data "Issue" $.Issue)}} + + {{if not .AllowMerge}} {{/* user is allowed to merge */}} {{/* user is not allowed to merge */}}
{{svg "octicon-info"}} {{ctx.Locale.Tr "repo.pulls.no_merge_access"}}
- {{end}} {{/* end if user is allowed to merge or not */}} - {{else}} - {{/* Merge conflict without specific file. Suggest manual merge, only if all reviews and status checks OK. */}} - {{if .IsBlockedByApprovals}} -
- {{svg "octicon-x"}} - {{ctx.Locale.Tr "repo.pulls.blocked_by_approvals" .GrantedApprovals .ProtectedBranch.RequiredApprovals}} -
- {{else if .IsBlockedByRejection}} -
- {{svg "octicon-x"}} - {{ctx.Locale.Tr "repo.pulls.blocked_by_rejection"}} -
- {{else if .IsBlockedByOfficialReviewRequests}} -
- {{svg "octicon-x"}} - {{ctx.Locale.Tr "repo.pulls.blocked_by_official_review_requests"}} -
- {{else if .IsBlockedByOutdatedBranch}} -
- {{svg "octicon-x"}} - {{ctx.Locale.Tr "repo.pulls.blocked_by_outdated_branch"}} -
- {{else if .IsBlockedByChangedProtectedFiles}} -
- {{svg "octicon-x"}} - {{ctx.Locale.TrN $.ChangedProtectedFilesNum "repo.pulls.blocked_by_changed_protected_files_1" "repo.pulls.blocked_by_changed_protected_files_n"}} -
-
    - {{range .ChangedProtectedFiles}} -
  • {{.}}
  • - {{end}} -
- {{else if and .EnableStatusCheck (not $requiredStatusCheckState.IsSuccess)}} -
- {{svg "octicon-x"}} - {{ctx.Locale.Tr "repo.pulls.required_status_check_failed"}} -
- {{else if and .RequireSigned (not .WillSign)}} -
- {{svg "octicon-x"}} - {{ctx.Locale.Tr "repo.pulls.require_signed_wont_sign"}} -
- {{else}} -
- {{svg "octicon-x"}} - {{ctx.Locale.Tr "repo.pulls.cannot_auto_merge_desc"}} -
-
- {{svg "octicon-info"}} - {{ctx.Locale.Tr "repo.pulls.cannot_auto_merge_helper"}} -
{{end}} {{end}}{{/* end if: pull request status */}} - {{/* Manually Merged is not a well-known feature, it is used to mark a non-mergeable PR (already merged, conflicted) as merged - To test it: - * Enable "Manually Merged" feature in the Repository Settings - * Create a pull request, either: - * - Merge the pull request branch locally and push the merged commit to Gitea - * - Make some conflicts between the base branch and the pull request branch - * Then the Manually Merged form will be shown in the merge form - */}} - {{if and $.StillCanManualMerge (not $showGeneralMergeForm)}} + {{if $data.MergeFormProps}} + {{/* The merge form is a Vue component. After mounted, it has a button for choosing merge style, so make it have min-height to avoid layout shifting */}}
-
{{/* another similar form is in PullRequestMergeForm.vue*/}} -
- -
- -
+
+
+ {{else if and .AllowMerge .Issue.PullRequest.IsStatusMergeable}} + {{/* no merge style was set in repo setting */}} +
+ {{svg "octicon-x"}} + {{ctx.Locale.Tr "repo.pulls.no_merge_desc"}} +
+
+ {{svg "octicon-info"}} + {{ctx.Locale.Tr "repo.pulls.no_merge_helper"}}
{{end}} diff --git a/templates/repo/issue/view_content/pull_merge_status_checks.tmpl b/templates/repo/issue/view_content/pull_merge_status_checks.tmpl new file mode 100644 index 0000000000..24cf9d39e1 --- /dev/null +++ b/templates/repo/issue/view_content/pull_merge_status_checks.tmpl @@ -0,0 +1,35 @@ +{{/* Template Attributes: +* CommitStatuses: all commit status elements +* StatusCheckData: additional status check data, see backend pullCommitStatusCheckData struct +*/}} +{{$commitStatuses := $.CommitStatuses}} +{{$statusCheckData := $.StatusCheckData}} +{{if $statusCheckData}} +
+
{{$statusCheckData.CommitStatusCheckPrompt ctx.Locale}}
+ +
+ + {{if $statusCheckData.RequireApprovalRunCount}} +
+
+ {{ctx.Locale.Tr "repo.pulls.status_checks_need_approvals" $statusCheckData.RequireApprovalRunCount}} +

{{ctx.Locale.Tr "repo.pulls.status_checks_need_approvals_helper"}}

+
+ {{if $statusCheckData.CanApprove}} + + {{end}} +
+ {{end}} + +
+
+ {{template "repo/pulls/status_items" (dict "CommitStatuses" $commitStatuses "StatusCheckData" $statusCheckData)}} +
+
+{{end}} diff --git a/templates/repo/issue/view_content/update_branch_by_merge.tmpl b/templates/repo/issue/view_content/update_branch_by_merge.tmpl index 940da17db1..0d2a40031e 100644 --- a/templates/repo/issue/view_content/update_branch_by_merge.tmpl +++ b/templates/repo/issue/view_content/update_branch_by_merge.tmpl @@ -1,30 +1,30 @@ -{{if and (gt $.Issue.PullRequest.CommitsBehind 0) (not $.Issue.IsClosed) (not $.Issue.PullRequest.IsChecking) (not $.IsPullFilesConflicted) (not $.IsPullRequestBroken)}} -
-
+{{$data := $.MergeBoxData}} +{{$issue := $.Issue}} +{{if $data.ShowUpdatePullInfo}} +
+
{{svg "octicon-alert"}} {{ctx.Locale.Tr "repo.pulls.outdated_with_base_branch"}}
-
- {{if and $.UpdateAllowed $.UpdateByRebaseAllowed}} -
-
- -