mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-15 15:03:32 +02:00
Merge branch 'main' into lunny/giteabot
This commit is contained in:
commit
80b366975f
@ -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) {
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
33
routers/web/repo/pull_merge_box.go
Normal file
33
routers/web/repo/pull_merge_box.go
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -228,6 +228,24 @@ func checkJobsOfCurrentRunAttempt(ctx context.Context, run *actions_model.Action
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
// The resolver below only considers needs and job-level concurrency, so a run blocked
|
||||
// solely by run-level concurrency would have its jobs unblocked here. checkRunConcurrency
|
||||
// re-evaluates when the holding run finishes.
|
||||
if run.Status.IsBlocked() {
|
||||
attempt, has, err := run.GetLatestAttempt(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("GetLatestAttempt: %w", err)
|
||||
}
|
||||
if has {
|
||||
shouldBlock, err := shouldBlockRunByConcurrency(ctx, attempt)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("shouldBlockRunByConcurrency: %w", err)
|
||||
}
|
||||
if shouldBlock {
|
||||
return jobs, nil, nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
vars, err := actions_model.GetVariablesOfRun(ctx, run)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
|
||||
@ -228,3 +228,68 @@ func Test_checkRunConcurrency_NoDuplicateConcurrencyGroupCheck(t *testing.T) {
|
||||
assert.Equal(t, jobBBlocked.ID, jobs[0].ID)
|
||||
}
|
||||
}
|
||||
|
||||
// Test_checkJobsOfCurrentRunAttempt_RunLevelConcurrencyKeepsJobsBlocked verifies that
|
||||
// the resolver does not transition a job out of Blocked while another run still holds
|
||||
// the workflow-level concurrency group. Regression for #37446.
|
||||
func Test_checkJobsOfCurrentRunAttempt_RunLevelConcurrencyKeepsJobsBlocked(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
ctx := t.Context()
|
||||
|
||||
const group = "test-run-level-concurrency-keeps-blocked"
|
||||
|
||||
// Holder run: Running attempt in the concurrency group.
|
||||
holderRun := &actions_model.ActionRun{
|
||||
RepoID: 4, OwnerID: 1, TriggerUserID: 1,
|
||||
WorkflowID: "test.yml", Index: 9911, Ref: "refs/heads/main",
|
||||
Status: actions_model.StatusRunning,
|
||||
}
|
||||
assert.NoError(t, db.Insert(ctx, holderRun))
|
||||
holderAttempt := &actions_model.ActionRunAttempt{
|
||||
RepoID: 4, RunID: holderRun.ID, Attempt: 1,
|
||||
Status: actions_model.StatusRunning, ConcurrencyGroup: group,
|
||||
}
|
||||
assert.NoError(t, db.Insert(ctx, holderAttempt))
|
||||
_, err := db.Exec(ctx, "UPDATE `action_run` SET latest_attempt_id = ? WHERE id = ?", holderAttempt.ID, holderRun.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Blocked run: Blocked attempt in the same group, with one Blocked job that has
|
||||
// no needs and no job-level concurrency. Without the run-level guard in
|
||||
// checkJobsOfCurrentRunAttempt, the resolver would transition this job to Waiting.
|
||||
blockedRun := &actions_model.ActionRun{
|
||||
RepoID: 4, OwnerID: 1, TriggerUserID: 1,
|
||||
WorkflowID: "test.yml", Index: 9912, Ref: "refs/heads/main",
|
||||
Status: actions_model.StatusBlocked,
|
||||
}
|
||||
assert.NoError(t, db.Insert(ctx, blockedRun))
|
||||
blockedAttempt := &actions_model.ActionRunAttempt{
|
||||
RepoID: 4, RunID: blockedRun.ID, Attempt: 1,
|
||||
Status: actions_model.StatusBlocked, ConcurrencyGroup: group,
|
||||
}
|
||||
assert.NoError(t, db.Insert(ctx, blockedAttempt))
|
||||
_, err = db.Exec(ctx, "UPDATE `action_run` SET latest_attempt_id = ? WHERE id = ?", blockedAttempt.ID, blockedRun.ID)
|
||||
assert.NoError(t, err)
|
||||
blockedRun.LatestAttemptID = blockedAttempt.ID
|
||||
blockedJob := &actions_model.ActionRunJob{
|
||||
RunID: blockedRun.ID, RunAttemptID: blockedAttempt.ID, AttemptJobID: 1,
|
||||
RepoID: 4, OwnerID: 1, JobID: "job1", Name: "job1",
|
||||
Status: actions_model.StatusBlocked,
|
||||
WorkflowPayload: []byte(`
|
||||
name: test
|
||||
on: push
|
||||
jobs:
|
||||
job1:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo
|
||||
`),
|
||||
}
|
||||
assert.NoError(t, db.Insert(ctx, blockedJob))
|
||||
|
||||
_, updated, _, err := checkJobsOfCurrentRunAttempt(ctx, blockedRun)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, updated)
|
||||
|
||||
refreshed := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: blockedJob.ID})
|
||||
assert.Equal(t, actions_model.StatusBlocked, refreshed.Status)
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
{{ctx.ScriptImport "js/index.js" "module"}}
|
||||
{{template "custom/footer" .}}
|
||||
<script nonce="{{ctx.CspScriptNonce}}" type="module">
|
||||
if (!window.config?.frontendInited) alert("Frontend is not initialized, check console errors or asset files.")
|
||||
if (!window.config?.frontendInited && window.config?.runModeIsProd) alert("Frontend is not initialized, check console errors or asset files.");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -101,26 +101,27 @@
|
||||
<div class="item">item 2</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3>Flex List (with "ui segment fitted")</h3>
|
||||
<div class="ui attached segment fitted">
|
||||
<div class="flex-divided-list">
|
||||
<h3>Flex List (with "ui segment fitted", items have their own padding)</h3>
|
||||
<div class="ui fitted segment">
|
||||
<div class="flex-divided-list items-px-default">
|
||||
<div class="item">item 1</div>
|
||||
<div class="item">item 2</div>
|
||||
<div class="item">item 3</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>If parent provides border or padding:</h3>
|
||||
<div class="container-segmented tw-border tw-border-secondary">
|
||||
<h3>If parent provides padding or items need their own flex and/or padding:</h3>
|
||||
<div class="tw-border tw-border-secondary">
|
||||
<div class="tw-m-3">before divider</div>
|
||||
<div class="divider"></div>
|
||||
<div class="flex-divided-list">
|
||||
<div class="flex-divided-list flex-items-block items-px-default">
|
||||
<div class="item">item 1</div>
|
||||
<div class="item">item 2</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="tw-m-3">after divider</div>
|
||||
</div>
|
||||
<div class="container-padded tw-border tw-border-secondary tw-p-4 tw-my-2">
|
||||
<div class="tw-border tw-border-secondary tw-p-4 tw-my-2">
|
||||
<div>before divider</div>
|
||||
<div class="divider"></div>
|
||||
<div class="flex-divided-list">
|
||||
|
||||
@ -9,6 +9,8 @@
|
||||
</span>
|
||||
{{end}}
|
||||
<div class="tippy-target">
|
||||
{{template "repo/pulls/status" (dict "CommitStatuses" .Statuses "CommitStatus" .Status)}}
|
||||
<div class="flex-divided-list items-px-default">
|
||||
{{template "repo/pulls/status_items" (dict "CommitStatuses" .Statuses)}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@ -9,36 +9,17 @@
|
||||
>
|
||||
{{$statusCheckData := .StatusCheckData}}
|
||||
{{$requiredStatusCheckState := $statusCheckData.RequiredChecksState}}
|
||||
<div class="timeline-avatar {{if .Issue.PullRequest.HasMerged}}tw-text-purple
|
||||
{{- else if .Issue.IsClosed}}tw-text-text-light
|
||||
{{- else if .IsPullWorkInProgress}}tw-text-text-light
|
||||
{{- else if .IsFilesConflicted}}tw-text-text-light
|
||||
{{- else if .IsPullRequestBroken}}tw-text-red
|
||||
{{- else if .IsBlockedByApprovals}}tw-text-red
|
||||
{{- else if .IsBlockedByRejection}}tw-text-red
|
||||
{{- else if .IsBlockedByOfficialReviewRequests}}tw-text-red
|
||||
{{- else if .IsBlockedByOutdatedBranch}}tw-text-red
|
||||
{{- else if .IsBlockedByChangedProtectedFiles}}tw-text-red
|
||||
{{- else if and .EnableStatusCheck (or $requiredStatusCheckState.IsFailure $requiredStatusCheckState.IsError)}}tw-text-red
|
||||
{{- else if and .EnableStatusCheck (or (not $.LatestCommitStatus) $requiredStatusCheckState.IsPending $requiredStatusCheckState.IsWarning)}}tw-text-yellow
|
||||
{{- else if and .AllowMerge .RequireSigned (not .WillSign)}}tw-text-red
|
||||
{{- else if .Issue.PullRequest.IsChecking}}tw-text-yellow
|
||||
{{- else if .Issue.PullRequest.IsEmpty}}tw-text-text-light
|
||||
{{- else if .Issue.PullRequest.IsStatusMergeable}}tw-text-green
|
||||
{{- else}}tw-text-red{{end}}">{{svg "octicon-git-merge" 40}}</div>
|
||||
<div class="timeline-avatar {{$data.TimelineIconClass}}">{{svg "octicon-git-merge" 40}}</div>
|
||||
<div class="content">
|
||||
{{if .LatestCommitStatus}}
|
||||
<div class="ui attached segment fitted">
|
||||
{{template "repo/pulls/status" (dict
|
||||
"CommitStatus" .LatestCommitStatus
|
||||
"CommitStatuses" .LatestCommitStatuses
|
||||
"ShowHideChecks" true
|
||||
"StatusCheckData" $statusCheckData
|
||||
)}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{$showGeneralMergeForm := false}}
|
||||
<div class="ui attached segment merge-section {{if not $.LatestCommitStatus}}avatar-content-left-arrow{{end}} flex-items-block">
|
||||
<div class="ui segment fitted avatar-content-left-arrow">
|
||||
<div class="merge-section flex-divided-list flex-items-block items-px-default">
|
||||
{{if .LatestCommitStatus}}
|
||||
{{template "repo/issue/view_content/pull_merge_status_checks" (dict
|
||||
"CommitStatuses" .LatestCommitStatuses
|
||||
"StatusCheckData" $statusCheckData
|
||||
)}}
|
||||
{{end}}
|
||||
|
||||
{{if .Issue.PullRequest.HasMerged}}
|
||||
{{if .IsPullBranchDeletable}}
|
||||
<div class="item item-section text tw-flex-1">
|
||||
@ -78,7 +59,7 @@
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.files_conflicted"}}
|
||||
</div>
|
||||
<ul>
|
||||
<ul class="item">
|
||||
{{range .ConflictedFiles}}
|
||||
<li>{{.}}</li>
|
||||
{{else}}
|
||||
@ -113,7 +94,7 @@
|
||||
{{svg "octicon-alert"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.is_ancestor"}}
|
||||
</div>
|
||||
{{else if or .Issue.PullRequest.IsStatusMergeable .Issue.PullRequest.IsEmpty}}
|
||||
{{else}}
|
||||
{{if .IsBlockedByApprovals}}
|
||||
<div class="item">
|
||||
{{svg "octicon-x"}}
|
||||
@ -143,7 +124,7 @@
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.TrN $.ChangedProtectedFilesNum "repo.pulls.blocked_by_changed_protected_files_1" "repo.pulls.blocked_by_changed_protected_files_n"}}
|
||||
</div>
|
||||
<ul>
|
||||
<ul class="item">
|
||||
{{range .ChangedProtectedFiles}}
|
||||
<li>{{.}}</li>
|
||||
{{end}}
|
||||
@ -167,6 +148,15 @@
|
||||
{{svg "octicon-unlock"}}
|
||||
{{ctx.Locale.Tr (printf "repo.signing.wont_sign.%s" .WontSignReason)}}
|
||||
</div>
|
||||
{{else if not .Issue.PullRequest.IsStatusMergeable}}
|
||||
<div class="item tw-text-red">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.cannot_auto_merge_desc"}}
|
||||
</div>
|
||||
<div class="item">
|
||||
{{svg "octicon-info"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.cannot_auto_merge_helper"}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{$notAllOverridableChecksOk := $data.HasOverridableBlockers}}
|
||||
@ -197,119 +187,47 @@
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{template "repo/issue/view_content/update_branch_by_merge" $}}
|
||||
|
||||
{{if .Issue.PullRequest.IsEmpty}}
|
||||
<div class="divider"></div>
|
||||
<div class="item">
|
||||
{{svg "octicon-alert"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.is_empty"}}
|
||||
</div>
|
||||
<div class="item">
|
||||
{{svg "octicon-alert"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.is_empty"}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .AllowMerge}} {{/* user is allowed to merge */}}
|
||||
{{if $data.MergeFormProps}}
|
||||
<div class="divider"></div>
|
||||
{{$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 */}}
|
||||
<div id="pull-request-merge-form" class="tw-min-h-[40px]" data-merge-form-props="{{JsonUtils.EncodeToString $data.MergeFormProps}}"></div>
|
||||
{{else}}
|
||||
{{/* no merge style was set in repo setting: not or ($prUnit.PullRequestsConfig.AllowMerge ...) */}}
|
||||
<div class="divider"></div>
|
||||
<div class="item tw-text-red">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.no_merge_desc"}}
|
||||
</div>
|
||||
<div class="item">
|
||||
{{svg "octicon-info"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.no_merge_helper"}}
|
||||
</div>
|
||||
{{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 */}}
|
||||
<div class="divider"></div>
|
||||
<div class="item">
|
||||
{{svg "octicon-info"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.no_merge_access"}}
|
||||
</div>
|
||||
{{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}}
|
||||
<div class="item tw-text-red">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.blocked_by_approvals" .GrantedApprovals .ProtectedBranch.RequiredApprovals}}
|
||||
</div>
|
||||
{{else if .IsBlockedByRejection}}
|
||||
<div class="item tw-text-red">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.blocked_by_rejection"}}
|
||||
</div>
|
||||
{{else if .IsBlockedByOfficialReviewRequests}}
|
||||
<div class="item tw-text-red">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.blocked_by_official_review_requests"}}
|
||||
</div>
|
||||
{{else if .IsBlockedByOutdatedBranch}}
|
||||
<div class="item tw-text-red">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.blocked_by_outdated_branch"}}
|
||||
</div>
|
||||
{{else if .IsBlockedByChangedProtectedFiles}}
|
||||
<div class="item tw-text-red">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.TrN $.ChangedProtectedFilesNum "repo.pulls.blocked_by_changed_protected_files_1" "repo.pulls.blocked_by_changed_protected_files_n"}}
|
||||
</div>
|
||||
<ul>
|
||||
{{range .ChangedProtectedFiles}}
|
||||
<li>{{.}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{else if and .EnableStatusCheck (not $requiredStatusCheckState.IsSuccess)}}
|
||||
<div class="item tw-text-red">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.required_status_check_failed"}}
|
||||
</div>
|
||||
{{else if and .RequireSigned (not .WillSign)}}
|
||||
<div class="item tw-text-red">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.require_signed_wont_sign"}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="item tw-text-red">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.cannot_auto_merge_desc"}}
|
||||
</div>
|
||||
<div class="item">
|
||||
{{svg "octicon-info"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.cannot_auto_merge_helper"}}
|
||||
</div>
|
||||
{{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)}}
|
||||
<div class="divider"></div>
|
||||
<form class="ui form form-fetch-action" action="{{.Issue.Link}}/merge" method="post">{{/* another similar form is in PullRequestMergeForm.vue*/}}
|
||||
<div class="field">
|
||||
<input type="text" name="merge_commit_id" placeholder="{{ctx.Locale.Tr "repo.pulls.merge_commit_id"}}">
|
||||
</div>
|
||||
<button class="ui red button" type="submit" name="do" value="manually-merged">
|
||||
{{ctx.Locale.Tr "repo.pulls.merge_manually"}}
|
||||
</button>
|
||||
</form>
|
||||
{{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 */}}
|
||||
<div class="item">
|
||||
<div id="pull-request-merge-form" class="tw-min-h-[40px] tw-w-full" data-merge-form-props="{{JsonUtils.EncodeToString $data.MergeFormProps}}"></div>
|
||||
</div>
|
||||
{{else if and .AllowMerge .Issue.PullRequest.IsStatusMergeable}}
|
||||
{{/* no merge style was set in repo setting */}}
|
||||
<div class="item tw-text-red">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.no_merge_desc"}}
|
||||
</div>
|
||||
<div class="item">
|
||||
{{svg "octicon-info"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.no_merge_helper"}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if $data.ShowPullCommands}}
|
||||
{{template "repo/issue/view_content/pull_merge_instruction" dict "PullRequest" .Issue.PullRequest "MergeBoxData" $data}}
|
||||
<div class="item">
|
||||
{{template "repo/issue/view_content/pull_merge_instruction" dict "PullRequest" .Issue.PullRequest "MergeBoxData" $data}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
{{$data := $.MergeBoxData}}
|
||||
{{$pull := $.PullRequest}}
|
||||
<div class="divider"></div>
|
||||
<details>
|
||||
<summary>{{ctx.Locale.Tr "repo.pulls.cmd_instruction_hint"}}</summary>
|
||||
<div class="tw-mt-2">
|
||||
|
||||
@ -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}}
|
||||
<div class="item flex-left-right commit-status-toggle">
|
||||
<div>{{$statusCheckData.CommitStatusCheckPrompt ctx.Locale}}</div>
|
||||
<button data-global-click="onCommitStatusChecksToggle" class="btn interact-fg"
|
||||
data-show-all="{{ctx.Locale.Tr "repo.pulls.status_checks_show_all"}}"
|
||||
data-hide-all="{{ctx.Locale.Tr "repo.pulls.status_checks_hide_all"}}"
|
||||
>{{ctx.Locale.Tr "repo.pulls.status_checks_hide_all"}}</button>
|
||||
</div>
|
||||
|
||||
{{if $statusCheckData.RequireApprovalRunCount}}
|
||||
<div class="item flex-left-right" id="approve-status-checks">
|
||||
<div>
|
||||
<strong>{{ctx.Locale.Tr "repo.pulls.status_checks_need_approvals" $statusCheckData.RequireApprovalRunCount}}</strong>
|
||||
<p>{{ctx.Locale.Tr "repo.pulls.status_checks_need_approvals_helper"}}</p>
|
||||
</div>
|
||||
{{if $statusCheckData.CanApprove}}
|
||||
<button class="ui basic button link-action" data-url="{{$statusCheckData.ApproveLink}}">
|
||||
{{ctx.Locale.Tr "repo.pulls.status_checks_approve_all"}}
|
||||
</button>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="item tw-p-0">
|
||||
<div class="commit-status-list flex-divided-list items-px-default">
|
||||
{{template "repo/pulls/status_items" (dict "CommitStatuses" $commitStatuses "StatusCheckData" $statusCheckData)}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
@ -1,31 +1,30 @@
|
||||
{{if and (gt $.Issue.PullRequest.CommitsBehind 0) (not $.Issue.IsClosed) (not $.Issue.PullRequest.IsChecking) (not $.IsPullFilesConflicted) (not $.IsPullRequestBroken)}}
|
||||
<div class="divider"></div>
|
||||
<div class="item item-section">
|
||||
<div class="item-section-left flex-text-inline">
|
||||
{{$data := $.MergeBoxData}}
|
||||
{{$issue := $.Issue}}
|
||||
{{if $data.ShowUpdatePullInfo}}
|
||||
<div class="item flex-left-right">
|
||||
<div class="flex-text-block">
|
||||
{{svg "octicon-alert"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.outdated_with_base_branch"}}
|
||||
</div>
|
||||
<div class="item-section-right">
|
||||
{{if and $.UpdateAllowed $.UpdateByRebaseAllowed}}
|
||||
<div class="tw-inline-block">
|
||||
<div id="update-pr-branch-with-base" class="ui buttons">
|
||||
<button class="ui button" data-do="{{$.Issue.Link}}/update">
|
||||
<span class="button-text">
|
||||
{{ctx.Locale.Tr "repo.pulls.update_branch"}}
|
||||
</span>
|
||||
</button>
|
||||
<div class="ui dropdown icon button">
|
||||
{{svg "octicon-triangle-down"}}
|
||||
<div class="menu">
|
||||
<a class="item active selected" data-do="{{$.Issue.Link}}/update">{{ctx.Locale.Tr "repo.pulls.update_branch"}}</a>
|
||||
<a class="item" data-do="{{$.Issue.Link}}/update?style=rebase">{{ctx.Locale.Tr "repo.pulls.update_branch_rebase"}}</a>
|
||||
</div>
|
||||
<div>
|
||||
{{if and $data.UpdateAllowed $data.UpdateByRebaseAllowed}}
|
||||
<div id="update-pr-branch-with-base" class="ui buttons">
|
||||
<button class="ui button" data-do="{{$issue.Link}}/update">
|
||||
<span class="button-text">
|
||||
{{ctx.Locale.Tr "repo.pulls.update_branch"}}
|
||||
</span>
|
||||
</button>
|
||||
<div class="ui dropdown icon button">
|
||||
{{svg "octicon-triangle-down"}}
|
||||
<div class="menu">
|
||||
<a class="item active selected" data-do="{{$issue.Link}}/update">{{ctx.Locale.Tr "repo.pulls.update_branch"}}</a>
|
||||
<a class="item" data-do="{{$issue.Link}}/update?style=rebase">{{ctx.Locale.Tr "repo.pulls.update_branch_rebase"}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if and $.UpdateAllowed (not $.UpdateByRebaseAllowed)}}
|
||||
<form action="{{$.Issue.Link}}/update" method="post" class="ui update-branch-form">
|
||||
{{if and $data.UpdateAllowed (not $data.UpdateByRebaseAllowed)}}
|
||||
<form action="{{$issue.Link}}/update" method="post">
|
||||
<button class="ui compact button">
|
||||
<span class="ui text">{{ctx.Locale.Tr "repo.pulls.update_branch"}}</span>
|
||||
</button>
|
||||
|
||||
@ -1,63 +0,0 @@
|
||||
{{/* Template Attributes:
|
||||
* CommitStatus: summary of all commit status state
|
||||
* CommitStatuses: all commit status elements
|
||||
* ShowHideChecks: whether use a button to show/hide the checks
|
||||
* StatusCheckData: additional status check data, see backend pullCommitStatusCheckData struct
|
||||
*/}}
|
||||
{{$statusCheckData := .StatusCheckData}}
|
||||
{{if .CommitStatus}}
|
||||
<div class="commit-status-panel">
|
||||
<div class="ui top attached header commit-status-header">
|
||||
{{$statusCheckData.CommitStatusCheckPrompt ctx.Locale}}
|
||||
|
||||
{{if .ShowHideChecks}}
|
||||
<div class="ui right">
|
||||
<button class="commit-status-hide-checks btn interact-fg"
|
||||
data-show-all="{{ctx.Locale.Tr "repo.pulls.status_checks_show_all"}}"
|
||||
data-hide-all="{{ctx.Locale.Tr "repo.pulls.status_checks_hide_all"}}">
|
||||
{{ctx.Locale.Tr "repo.pulls.status_checks_hide_all"}}</button>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if and $statusCheckData $statusCheckData.RequireApprovalRunCount}}
|
||||
<div class="ui attached segment flex-left-right" id="approve-status-checks">
|
||||
<div>
|
||||
<strong>
|
||||
{{ctx.Locale.Tr "repo.pulls.status_checks_need_approvals" $statusCheckData.RequireApprovalRunCount}}
|
||||
</strong>
|
||||
<p>{{ctx.Locale.Tr "repo.pulls.status_checks_need_approvals_helper"}}</p>
|
||||
</div>
|
||||
{{if $statusCheckData.CanApprove}}
|
||||
<button class="ui basic button link-action" data-url="{{$statusCheckData.ApproveLink}}">
|
||||
{{ctx.Locale.Tr "repo.pulls.status_checks_approve_all"}}
|
||||
</button>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="commit-status-list">
|
||||
{{range .CommitStatuses}}
|
||||
<div class="commit-status-item">
|
||||
{{template "repo/commit_status" .}}
|
||||
<div class="status-context gt-ellipsis">{{.Context}} <span class="tw-text-text-light-2">{{.Description}}</span></div>
|
||||
<div class="ui status-details">
|
||||
{{if and $statusCheckData $statusCheckData.IsContextRequired}}
|
||||
{{if (call $statusCheckData.IsContextRequired .Context)}}<div class="ui label">{{ctx.Locale.Tr "repo.pulls.status_checks_requested"}}</div>{{end}}
|
||||
{{end}}
|
||||
<span>{{if .TargetURL}}<a href="{{.TargetURL}}">{{ctx.Locale.Tr "repo.pulls.status_checks_details"}}</a>{{end}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if $statusCheckData}}
|
||||
{{range $statusCheckData.MissingRequiredChecks}}
|
||||
<div class="commit-status-item">
|
||||
{{svg "octicon-dot-fill" 18 "commit-status icon tw-text-yellow"}}
|
||||
<div class="status-context gt-ellipsis">{{.}}</div>
|
||||
<div class="ui label">{{ctx.Locale.Tr "repo.pulls.status_checks_requested"}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
32
templates/repo/pulls/status_items.tmpl
Normal file
32
templates/repo/pulls/status_items.tmpl
Normal file
@ -0,0 +1,32 @@
|
||||
{{/* Template Attributes:
|
||||
* CommitStatuses: all commit status elements
|
||||
* StatusCheckData: optional, additional status check data, see backend pullCommitStatusCheckData struct
|
||||
*/}}
|
||||
{{$statusCheckData := $.StatusCheckData}}
|
||||
{{range $cs := $.CommitStatuses}}
|
||||
<div class="item commit-status-item">
|
||||
<div class="flex-text-block">
|
||||
{{template "repo/commit_status" $cs}}
|
||||
<div class="status-context gt-ellipsis">
|
||||
{{$cs.Context}} <span class="tw-text-text-light-2">{{$cs.Description}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-details">
|
||||
{{if and $statusCheckData $statusCheckData.IsContextRequired}}
|
||||
{{if (call $statusCheckData.IsContextRequired $cs.Context)}}
|
||||
<div class="ui label">{{ctx.Locale.Tr "repo.pulls.status_checks_requested"}}</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{if $cs.TargetURL}}<a href="{{$cs.TargetURL}}">{{ctx.Locale.Tr "repo.pulls.status_checks_details"}}</a>{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{range $missingCheck := $statusCheckData.MissingRequiredChecks}}
|
||||
<div class="item commit-status-item">
|
||||
<div class="flex-text-block">
|
||||
{{svg "octicon-dot-fill" 16 "commit-status icon tw-text-yellow"}}
|
||||
<div class="status-context gt-ellipsis">{{$missingCheck}}</div>
|
||||
</div>
|
||||
<div class="ui label">{{ctx.Locale.Tr "repo.pulls.status_checks_requested"}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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}
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -179,13 +179,8 @@
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.ui.fitted.segment:not(.horizontally) {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.ui.fitted.segment:not(.vertically) {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
.ui.fitted.segment {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ui.segments .segment,
|
||||
|
||||
@ -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);
|
||||
@ -1933,46 +1929,16 @@ tbody.commit-list {
|
||||
max-height: 240px; /* fit exactly 6 items, commit-status-item.height * 6 */
|
||||
overflow-x: hidden;
|
||||
transition: max-height .2s;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.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 {
|
||||
|
||||
@ -9,17 +9,23 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* items have dividers between them, the dividers align with items (use parent padding) */
|
||||
.flex-divided-list {
|
||||
list-style: none;
|
||||
/* items have dividers between them, the dividers align with items */
|
||||
.flex-divided-list,
|
||||
.flex-divided-list > .item.flex-divided-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.flex-divided-list > .item {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.flex-divided-list > .divider {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.flex-divided-list > .item + .item {
|
||||
border-top: 1px solid var(--color-secondary);
|
||||
}
|
||||
@ -99,21 +105,15 @@
|
||||
}
|
||||
|
||||
/* special rules to make the list work with existing UI elements */
|
||||
.container-segmented > .flex-divided-list > .item {
|
||||
padding-left: 1em;
|
||||
.flex-divided-list.items-px-default > .item {
|
||||
padding-left: 1em; /* matches ".ui.segment" padding */
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.ui.segment.fitted > .flex-divided-list > .item {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.container-padded > .flex-divided-list > .item:first-child,
|
||||
.ui.segment:not(.fitted) > .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 {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
@ -126,7 +126,3 @@
|
||||
.flex-divided-list + .divider {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.container-padded > .flex-divided-list + .divider {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@ -7,6 +7,8 @@ const props = defineProps<{
|
||||
mergeFormProps: any, // TODO: this is a huge object, need to be refactored in the future
|
||||
}>();
|
||||
|
||||
const mergeStyleManuallyMerged = 'manually-merged';
|
||||
|
||||
const mergeForm = props.mergeFormProps;
|
||||
|
||||
const mergeTitleFieldValue = shallowRef('');
|
||||
@ -29,10 +31,17 @@ const showMergeStyleMenu = shallowRef(false);
|
||||
const showActionForm = shallowRef(false);
|
||||
|
||||
const mergeButtonStyleClass = computed(() => {
|
||||
if (mergeStyle.value === mergeStyleManuallyMerged) return 'red';
|
||||
if (mergeForm.allOverridableChecksOk) return 'primary';
|
||||
return autoMergeWhenSucceed.value ? 'primary' : 'red';
|
||||
});
|
||||
|
||||
const mergeSelectStyleClass = computed(() => {
|
||||
if (mergeForm.emptyCommit) return '';
|
||||
if (mergeStyle.value === mergeStyleManuallyMerged) return 'red';
|
||||
return 'primary';
|
||||
});
|
||||
|
||||
const forceMerge = computed(() => {
|
||||
return mergeForm.canMergeNow && !mergeForm.allOverridableChecksOk;
|
||||
});
|
||||
@ -115,30 +124,32 @@ function clearMergeMessage() {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="field" v-if="mergeStyle === 'manually-merged'">
|
||||
<div class="field" v-if="mergeStyle === mergeStyleManuallyMerged">
|
||||
<input type="text" name="merge_commit_id" :placeholder="mergeForm.textMergeCommitId">
|
||||
</div>
|
||||
|
||||
<button class="ui button" :class="mergeButtonStyleClass" type="submit" name="do" :value="mergeStyle">
|
||||
{{ mergeStyleDetail.textDoMerge }}
|
||||
<template v-if="autoMergeWhenSucceed">
|
||||
{{ mergeForm.textAutoMergeButtonWhenSucceed }}
|
||||
</template>
|
||||
</button>
|
||||
<div class="flex-text-block tw-gap-3">
|
||||
<button class="ui button" :class="mergeButtonStyleClass" type="submit" name="do" :value="mergeStyle">
|
||||
{{ mergeStyleDetail.textDoMerge }}
|
||||
<template v-if="autoMergeWhenSucceed">
|
||||
{{ mergeForm.textAutoMergeButtonWhenSucceed }}
|
||||
</template>
|
||||
</button>
|
||||
|
||||
<button class="ui button merge-cancel" @click="toggleActionForm(false)">
|
||||
{{ mergeForm.textCancel }}
|
||||
</button>
|
||||
<button class="ui button merge-cancel" type="button" @click="toggleActionForm(false)">
|
||||
{{ mergeForm.textCancel }}
|
||||
</button>
|
||||
|
||||
<div class="ui checkbox tw-ml-1" v-if="mergeForm.isPullBranchDeletable">
|
||||
<input name="delete_branch_after_merge" type="checkbox" v-model="deleteBranchAfterMerge" id="delete-branch-after-merge">
|
||||
<label for="delete-branch-after-merge">{{ mergeForm.textDeleteBranch }}</label>
|
||||
<div class="ui checkbox" v-if="mergeForm.isPullBranchDeletable">
|
||||
<input name="delete_branch_after_merge" type="checkbox" v-model="deleteBranchAfterMerge" id="delete-branch-after-merge">
|
||||
<label for="delete-branch-after-merge">{{ mergeForm.textDeleteBranch }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div v-if="!showActionForm" class="tw-flex">
|
||||
<!-- the merge button -->
|
||||
<div class="ui buttons merge-button" :class="[mergeForm.emptyCommit ? '' : mergeForm.allOverridableChecksOk ? 'primary' : 'red']" @click="toggleActionForm(true)">
|
||||
<div class="ui buttons merge-button" :class="mergeSelectStyleClass" @click="toggleActionForm(true)">
|
||||
<button class="ui button">
|
||||
<svg-icon name="octicon-git-merge"/>
|
||||
<span class="button-text">
|
||||
|
||||
@ -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');
|
||||
@ -22,6 +23,7 @@ function initRepoPullRequestUpdate(el: HTMLElement) {
|
||||
}
|
||||
let data: Record<string, any> | undefined;
|
||||
try {
|
||||
// TODO: the response is indeed not JSON, need to fix (see backend UpdatePullRequest)
|
||||
data = await response?.json(); // the response is probably not a JSON
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@ -48,15 +50,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<HTMLElement>('.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<HTMLElement>('.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 +68,7 @@ async function initRepoPullRequestMergeForm(box: HTMLElement) {
|
||||
}
|
||||
|
||||
export function initRepoPullMergeBox(el: HTMLElement) {
|
||||
initRepoPullRequestCommitStatus(el);
|
||||
registerGlobalEventFunc('click', 'onCommitStatusChecksToggle', onCommitStatusChecksToggle);
|
||||
initRepoPullRequestUpdate(el);
|
||||
initRepoPullRequestMergeForm(el);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user