summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.forgejo/workflows/build-release.yml6
-rw-r--r--go.mod2
-rw-r--r--go.sum4
-rw-r--r--models/actions/run_job.go44
-rw-r--r--models/actions/run_job_status_test.go85
-rw-r--r--models/fixtures/action_run.yml19
-rw-r--r--models/fixtures/branch.yml12
-rw-r--r--models/forgejo_migrations/v25.go25
-rw-r--r--models/migrations/migrations.go4
-rw-r--r--models/migrations/v1_23/v303.go33
-rw-r--r--models/repo/git.go9
-rw-r--r--models/repo/repo_unit.go20
-rw-r--r--models/repo/repo_unit_test.go49
-rw-r--r--models/system/setting.go8
-rw-r--r--modules/repository/create.go5
-rw-r--r--modules/setting/repository.go3
-rw-r--r--modules/structs/repo.go3
-rw-r--r--options/locale/locale_en-US.ini1
-rw-r--r--routers/api/v1/repo/repo.go4
-rw-r--r--routers/web/repo/actions/actions.go9
-rw-r--r--routers/web/repo/actions/actions_test.go33
-rw-r--r--routers/web/repo/actions/main_test.go14
-rw-r--r--routers/web/repo/issue.go15
-rw-r--r--routers/web/repo/setting/setting.go1
-rw-r--r--services/convert/pull.go5
-rw-r--r--services/convert/repository.go3
-rw-r--r--services/forms/repo_form.go1
-rw-r--r--services/webhook/notifier.go4
-rw-r--r--templates/package/content/container.tmpl6
-rw-r--r--templates/repo/actions/status.tmpl4
-rw-r--r--templates/repo/issue/view_content/update_branch_by_merge.tmpl18
-rw-r--r--templates/repo/settings/units/pulls.tmpl23
-rw-r--r--templates/swagger/v1_json.tmpl9
-rw-r--r--tests/integration/pull_update_test.go100
-rw-r--r--web_src/js/components/ActionRunStatus.vue5
-rw-r--r--web_src/js/components/RepoActionView.vue4
-rw-r--r--web_src/js/svg.js2
37 files changed, 549 insertions, 43 deletions
diff --git a/.forgejo/workflows/build-release.yml b/.forgejo/workflows/build-release.yml
index c6cae8909d..433b085969 100644
--- a/.forgejo/workflows/build-release.yml
+++ b/.forgejo/workflows/build-release.yml
@@ -14,6 +14,12 @@
# secrets.CASCADE_DESTINATION_TOKEN: <generated from code.forgejo.org/forgejo-ci> scope read:user, write:repository, write:issue
# vars.CASCADE_DESTINATION_DOER: forgejo-ci
#
+# vars.SKIP_END_TO_END: `true` or `false`
+# It must be `false` (or absent) so https://code.forgejo.org/forgejo/end-to-end is run
+# with the newly built release.
+# It must be set to `true` when a release is missing, for instance because it was
+# removed and failed to upload.
+#
on:
push:
tags: 'v[0-9]+.[0-9]+.*'
diff --git a/go.mod b/go.mod
index f6ea30bad5..8b158d6fce 100644
--- a/go.mod
+++ b/go.mod
@@ -109,7 +109,7 @@ require (
golang.org/x/sys v0.28.0
golang.org/x/text v0.21.0
google.golang.org/grpc v1.69.2
- google.golang.org/protobuf v1.36.0
+ google.golang.org/protobuf v1.36.1
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v3 v3.0.1
diff --git a/go.sum b/go.sum
index d21a2935e5..4629eba1df 100644
--- a/go.sum
+++ b/go.sum
@@ -2157,8 +2157,8 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ=
-google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
+google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/models/actions/run_job.go b/models/actions/run_job.go
index 2319af8e08..de4b6aab66 100644
--- a/models/actions/run_job.go
+++ b/models/actions/run_job.go
@@ -153,28 +153,34 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
}
func AggregateJobStatus(jobs []*ActionRunJob) Status {
- allDone := true
- allWaiting := true
- hasFailure := false
+ allSuccessOrSkipped := len(jobs) != 0
+ allSkipped := len(jobs) != 0
+ var hasFailure, hasCancelled, hasWaiting, hasRunning, hasBlocked bool
for _, job := range jobs {
- if !job.Status.IsDone() {
- allDone = false
- }
- if job.Status != StatusWaiting && !job.Status.IsDone() {
- allWaiting = false
- }
- if job.Status == StatusFailure || job.Status == StatusCancelled {
- hasFailure = true
- }
+ allSuccessOrSkipped = allSuccessOrSkipped && (job.Status == StatusSuccess || job.Status == StatusSkipped)
+ allSkipped = allSkipped && job.Status == StatusSkipped
+ hasFailure = hasFailure || job.Status == StatusFailure
+ hasCancelled = hasCancelled || job.Status == StatusCancelled
+ hasWaiting = hasWaiting || job.Status == StatusWaiting
+ hasRunning = hasRunning || job.Status == StatusRunning
+ hasBlocked = hasBlocked || job.Status == StatusBlocked
}
- if allDone {
- if hasFailure {
- return StatusFailure
- }
+ switch {
+ case allSkipped:
+ return StatusSkipped
+ case allSuccessOrSkipped:
return StatusSuccess
- }
- if allWaiting {
+ case hasCancelled:
+ return StatusCancelled
+ case hasFailure:
+ return StatusFailure
+ case hasRunning:
+ return StatusRunning
+ case hasWaiting:
return StatusWaiting
+ case hasBlocked:
+ return StatusBlocked
+ default:
+ return StatusUnknown // it shouldn't happen
}
- return StatusRunning
}
diff --git a/models/actions/run_job_status_test.go b/models/actions/run_job_status_test.go
new file mode 100644
index 0000000000..04fd9ceba7
--- /dev/null
+++ b/models/actions/run_job_status_test.go
@@ -0,0 +1,85 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package actions
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestAggregateJobStatus(t *testing.T) {
+ testStatuses := func(expected Status, statuses []Status) {
+ t.Helper()
+ var jobs []*ActionRunJob
+ for _, v := range statuses {
+ jobs = append(jobs, &ActionRunJob{Status: v})
+ }
+ actual := AggregateJobStatus(jobs)
+ if !assert.Equal(t, expected, actual) {
+ var statusStrings []string
+ for _, s := range statuses {
+ statusStrings = append(statusStrings, s.String())
+ }
+ t.Errorf("AggregateJobStatus(%v) = %v, want %v", statusStrings, statusNames[actual], statusNames[expected])
+ }
+ }
+
+ cases := []struct {
+ statuses []Status
+ expected Status
+ }{
+ // unknown cases, maybe it shouldn't happen in real world
+ {[]Status{}, StatusUnknown},
+ {[]Status{StatusUnknown, StatusSuccess}, StatusUnknown},
+ {[]Status{StatusUnknown, StatusSkipped}, StatusUnknown},
+ {[]Status{StatusUnknown, StatusFailure}, StatusFailure},
+ {[]Status{StatusUnknown, StatusCancelled}, StatusCancelled},
+ {[]Status{StatusUnknown, StatusWaiting}, StatusWaiting},
+ {[]Status{StatusUnknown, StatusRunning}, StatusRunning},
+ {[]Status{StatusUnknown, StatusBlocked}, StatusBlocked},
+
+ // success with other status
+ {[]Status{StatusSuccess}, StatusSuccess},
+ {[]Status{StatusSuccess, StatusSkipped}, StatusSuccess}, // skipped doesn't affect success
+ {[]Status{StatusSuccess, StatusFailure}, StatusFailure},
+ {[]Status{StatusSuccess, StatusCancelled}, StatusCancelled},
+ {[]Status{StatusSuccess, StatusWaiting}, StatusWaiting},
+ {[]Status{StatusSuccess, StatusRunning}, StatusRunning},
+ {[]Status{StatusSuccess, StatusBlocked}, StatusBlocked},
+
+ // any cancelled, then cancelled
+ {[]Status{StatusCancelled}, StatusCancelled},
+ {[]Status{StatusCancelled, StatusSuccess}, StatusCancelled},
+ {[]Status{StatusCancelled, StatusSkipped}, StatusCancelled},
+ {[]Status{StatusCancelled, StatusFailure}, StatusCancelled},
+ {[]Status{StatusCancelled, StatusWaiting}, StatusCancelled},
+ {[]Status{StatusCancelled, StatusRunning}, StatusCancelled},
+ {[]Status{StatusCancelled, StatusBlocked}, StatusCancelled},
+
+ // failure with other status, fail fast
+ // Should "running" win? Maybe no: old code does make "running" win, but GitHub does fail fast.
+ {[]Status{StatusFailure}, StatusFailure},
+ {[]Status{StatusFailure, StatusSuccess}, StatusFailure},
+ {[]Status{StatusFailure, StatusSkipped}, StatusFailure},
+ {[]Status{StatusFailure, StatusCancelled}, StatusCancelled},
+ {[]Status{StatusFailure, StatusWaiting}, StatusFailure},
+ {[]Status{StatusFailure, StatusRunning}, StatusFailure},
+ {[]Status{StatusFailure, StatusBlocked}, StatusFailure},
+
+ // skipped with other status
+ // TODO: need to clarify whether a PR with "skipped" job status is considered as "mergeable" or not.
+ {[]Status{StatusSkipped}, StatusSkipped},
+ {[]Status{StatusSkipped, StatusSuccess}, StatusSuccess},
+ {[]Status{StatusSkipped, StatusFailure}, StatusFailure},
+ {[]Status{StatusSkipped, StatusCancelled}, StatusCancelled},
+ {[]Status{StatusSkipped, StatusWaiting}, StatusWaiting},
+ {[]Status{StatusSkipped, StatusRunning}, StatusRunning},
+ {[]Status{StatusSkipped, StatusBlocked}, StatusBlocked},
+ }
+
+ for _, c := range cases {
+ testStatuses(c.expected, c.statuses)
+ }
+}
diff --git a/models/fixtures/action_run.yml b/models/fixtures/action_run.yml
index 2fe9094d13..7a7bf34197 100644
--- a/models/fixtures/action_run.yml
+++ b/models/fixtures/action_run.yml
@@ -433,6 +433,25 @@
need_approval: 0
approved_by: 0
-
+ id: 794
+ title: "job output"
+ repo_id: 4
+ owner_id: 1
+ workflow_id: "test.yaml"
+ index: 190
+ trigger_user_id: 1
+ ref: "refs/heads/test"
+ commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
+ event: "push"
+ is_fork_pull_request: 0
+ status: 1
+ started: 1683636528
+ stopped: 1683636626
+ created: 1683636108
+ updated: 1683636626
+ need_approval: 0
+ approved_by: 0
+-
id: 891
title: "update actions"
repo_id: 1
diff --git a/models/fixtures/branch.yml b/models/fixtures/branch.yml
index 93003049c6..b75d3706cc 100644
--- a/models/fixtures/branch.yml
+++ b/models/fixtures/branch.yml
@@ -45,3 +45,15 @@
is_deleted: false
deleted_by_id: 0
deleted_unix: 0
+
+-
+ id: 15
+ repo_id: 4
+ name: 'master'
+ commit_id: 'c7cd3cd144e6d23c9d6f3d07e52b2c1a956e0338'
+ commit_message: 'add Readme'
+ commit_time: 1588147171
+ pusher_id: 13
+ is_deleted: false
+ deleted_by_id: 0
+ deleted_unix: 0
diff --git a/models/forgejo_migrations/v25.go b/models/forgejo_migrations/v25.go
index 2e9641929c..e2316007cf 100644
--- a/models/forgejo_migrations/v25.go
+++ b/models/forgejo_migrations/v25.go
@@ -20,9 +20,34 @@ import (
func MigrateTwoFactorToKeying(x *xorm.Engine) error {
var err error
+ // When upgrading from Forgejo v9 to v10, this migration will already be
+ // called from models/migrations/migrations.go migration 304 and must not
+ // be run twice.
+ var version int
+ _, err = x.Table("version").Where("`id` = 1").Select("version").Get(&version)
+ if err != nil {
+ // the version table does not exist when a test environment only applies Forgejo migrations
+ } else if version > 304 {
+ return nil
+ }
+
switch x.Dialect().URI().DBType {
case schemas.MYSQL:
_, err = x.Exec("ALTER TABLE `two_factor` MODIFY `secret` BLOB")
+ case schemas.SQLITE:
+ _, err = x.Exec("ALTER TABLE `two_factor` RENAME COLUMN `secret` TO `secret_backup`")
+ if err != nil {
+ return err
+ }
+ _, err = x.Exec("ALTER TABLE `two_factor` ADD COLUMN `secret` BLOB")
+ if err != nil {
+ return err
+ }
+ _, err = x.Exec("UPDATE `two_factor` SET `secret` = `secret_backup`")
+ if err != nil {
+ return err
+ }
+ _, err = x.Exec("ALTER TABLE `two_factor` DROP COLUMN `secret_backup`")
case schemas.POSTGRES:
_, err = x.Exec("ALTER TABLE `two_factor` ALTER COLUMN `secret` SET DATA TYPE bytea USING secret::text::bytea")
}
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 77a97fb3f6..1674af08cd 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -362,6 +362,10 @@ func prepareMigrationTasks() []*migration {
newMigration(300, "Add force-push branch protection support", v1_23.AddForcePushBranchProtection),
newMigration(301, "Add skip_secondary_authorization option to oauth2 application table", v1_23.AddSkipSecondaryAuthColumnToOAuth2ApplicationTable),
newMigration(302, "Add index to action_task stopped log_expired", v1_23.AddIndexToActionTaskStoppedLogExpired),
+
+ // Migration to Forgejo v10
+ newMigration(303, "Gitea last drop", v1_23.GiteaLastDrop),
+ newMigration(304, "Migrate `secret` column to store keying material", forgejo_migrations.MigrateTwoFactorToKeying),
}
return preparedMigrations
}
diff --git a/models/migrations/v1_23/v303.go b/models/migrations/v1_23/v303.go
new file mode 100644
index 0000000000..c1e74c596a
--- /dev/null
+++ b/models/migrations/v1_23/v303.go
@@ -0,0 +1,33 @@
+// Copyright 2024 The Forgejo Authors.
+// SPDX-License-Identifier: MIT
+
+package v1_23 //nolint
+
+import (
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "xorm.io/xorm"
+)
+
+func GiteaLastDrop(x *xorm.Engine) error {
+ sess := x.NewSession()
+ defer sess.Close()
+
+ if err := base.DropTableColumns(sess, "badge", "slug"); err != nil {
+ return err
+ }
+ if err := base.DropTableColumns(sess, "oauth2_application", "skip_secondary_authorization"); err != nil {
+ return err
+ }
+ if err := base.DropTableColumns(sess, "repository", "default_wiki_branch"); err != nil {
+ return err
+ }
+ // the migration v297.go that adds everyone_access_mode exists in Gitea >= v1.22 and the column must be dropped
+ // but it does not exist in Forgejo and a failure to drop the column can be ignored
+ base.DropTableColumns(sess, "repo_unit", "everyone_access_mode")
+ if err := base.DropTableColumns(sess, "protected_branch", "can_force_push", "enable_force_push_allowlist", "force_push_allowlist_user_i_ds", "force_push_allowlist_team_i_ds", "force_push_allowlist_deploy_keys"); err != nil {
+ return err
+ }
+
+ return sess.Commit()
+}
diff --git a/models/repo/git.go b/models/repo/git.go
index 388bf86522..d39915e869 100644
--- a/models/repo/git.go
+++ b/models/repo/git.go
@@ -29,6 +29,15 @@ const (
MergeStyleRebaseUpdate MergeStyle = "rebase-update-only"
)
+type UpdateStyle string
+
+const (
+ // UpdateStyleMerge create merge commit to update
+ UpdateStyleMerge UpdateStyle = "merge"
+ // UpdateStyleRebase rebase to update
+ UpdateStyleRebase UpdateStyle = "rebase"
+)
+
// UpdateDefaultBranch updates the default branch
func UpdateDefaultBranch(ctx context.Context, repo *Repository) error {
_, err := db.GetEngine(ctx).ID(repo.ID).Cols("default_branch").Update(repo)
diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go
index ed553844fc..5b4066d994 100644
--- a/models/repo/repo_unit.go
+++ b/models/repo/repo_unit.go
@@ -159,6 +159,7 @@ type PullRequestsConfig struct {
AllowRebaseUpdate bool
DefaultDeleteBranchAfterMerge bool
DefaultMergeStyle MergeStyle
+ DefaultUpdateStyle UpdateStyle
DefaultAllowMaintainerEdit bool
}
@@ -197,6 +198,25 @@ func (cfg *PullRequestsConfig) GetDefaultMergeStyle() MergeStyle {
return MergeStyleMerge
}
+// IsUpdateStyleAllowed returns if update style is allowed
+func (cfg *PullRequestsConfig) IsUpdateStyleAllowed(updateStyle UpdateStyle) bool {
+ return updateStyle == UpdateStyleMerge ||
+ updateStyle == UpdateStyleRebase && cfg.AllowRebaseUpdate
+}
+
+// GetDefaultUpdateStyle returns the default update style for this pull request
+func (cfg *PullRequestsConfig) GetDefaultUpdateStyle() UpdateStyle {
+ if len(cfg.DefaultUpdateStyle) != 0 {
+ return cfg.DefaultUpdateStyle
+ }
+
+ if setting.Repository.PullRequest.DefaultUpdateStyle != "" {
+ return UpdateStyle(setting.Repository.PullRequest.DefaultUpdateStyle)
+ }
+
+ return UpdateStyleMerge
+}
+
type ActionsConfig struct {
DisabledWorkflows []string
}
diff --git a/models/repo/repo_unit_test.go b/models/repo/repo_unit_test.go
index deee1a7472..129d913cfb 100644
--- a/models/repo/repo_unit_test.go
+++ b/models/repo/repo_unit_test.go
@@ -7,6 +7,8 @@ import (
"testing"
"code.gitea.io/gitea/models/perm"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
)
@@ -37,3 +39,50 @@ func TestRepoUnitAccessMode(t *testing.T) {
assert.Equal(t, perm.AccessModeWrite, UnitAccessModeWrite.ToAccessMode(perm.AccessModeAdmin))
assert.Equal(t, perm.AccessModeRead, UnitAccessModeUnset.ToAccessMode(perm.AccessModeRead))
}
+
+func TestRepoPRIsUpdateStyleAllowed(t *testing.T) {
+ var cfg PullRequestsConfig
+ cfg = PullRequestsConfig{
+ AllowRebaseUpdate: true,
+ }
+ assert.True(t, cfg.IsUpdateStyleAllowed(UpdateStyleMerge))
+ assert.True(t, cfg.IsUpdateStyleAllowed(UpdateStyleRebase))
+
+ cfg = PullRequestsConfig{
+ AllowRebaseUpdate: false,
+ }
+ assert.True(t, cfg.IsUpdateStyleAllowed(UpdateStyleMerge))
+ assert.False(t, cfg.IsUpdateStyleAllowed(UpdateStyleRebase))
+}
+
+func TestRepoPRGetDefaultUpdateStyle(t *testing.T) {
+ defer test.MockVariableValue(&setting.Repository.PullRequest.DefaultUpdateStyle, "merge")()
+
+ var cfg PullRequestsConfig
+ cfg = PullRequestsConfig{
+ DefaultUpdateStyle: "",
+ }
+ assert.Equal(t, UpdateStyleMerge, cfg.GetDefaultUpdateStyle())
+ cfg = PullRequestsConfig{
+ DefaultUpdateStyle: "rebase",
+ }
+ assert.Equal(t, UpdateStyleRebase, cfg.GetDefaultUpdateStyle())
+ cfg = PullRequestsConfig{
+ DefaultUpdateStyle: "merge",
+ }
+ assert.Equal(t, UpdateStyleMerge, cfg.GetDefaultUpdateStyle())
+
+ setting.Repository.PullRequest.DefaultUpdateStyle = "rebase"
+ cfg = PullRequestsConfig{
+ DefaultUpdateStyle: "",
+ }
+ assert.Equal(t, UpdateStyleRebase, cfg.GetDefaultUpdateStyle())
+ cfg = PullRequestsConfig{
+ DefaultUpdateStyle: "rebase",
+ }
+ assert.Equal(t, UpdateStyleRebase, cfg.GetDefaultUpdateStyle())
+ cfg = PullRequestsConfig{
+ DefaultUpdateStyle: "merge",
+ }
+ assert.Equal(t, UpdateStyleMerge, cfg.GetDefaultUpdateStyle())
+}
diff --git a/models/system/setting.go b/models/system/setting.go
index 4472b4c228..cda60d1758 100644
--- a/models/system/setting.go
+++ b/models/system/setting.go
@@ -18,10 +18,10 @@ import (
)
type Setting struct {
- ID int64 `xorm:"pk autoincr"`
- SettingKey string `xorm:"varchar(255) unique"` // key should be lowercase
- SettingValue string `xorm:"text"`
- Version int `xorm:"version"`
+ ID int64 `xorm:"pk autoincr"`
+ SettingKey string `xorm:"varchar(255) unique"` // key should be lowercase
+ SettingValue string `xorm:"text"`
+ Version int
Created timeutil.TimeStamp `xorm:"created"`
Updated timeutil.TimeStamp `xorm:"updated"`
}
diff --git a/modules/repository/create.go b/modules/repository/create.go
index ca2150b972..32c6235544 100644
--- a/modules/repository/create.go
+++ b/modules/repository/create.go
@@ -89,8 +89,9 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re
Type: tp,
Config: &repo_model.PullRequestsConfig{
AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, AllowFastForwardOnly: true,
- DefaultMergeStyle: repo_model.MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle),
- AllowRebaseUpdate: true,
+ DefaultMergeStyle: repo_model.MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle),
+ DefaultUpdateStyle: repo_model.UpdateStyle(setting.Repository.PullRequest.DefaultUpdateStyle),
+ AllowRebaseUpdate: true,
},
})
} else {
diff --git a/modules/setting/repository.go b/modules/setting/repository.go
index 6086dd1d57..d13f25f554 100644
--- a/modules/setting/repository.go
+++ b/modules/setting/repository.go
@@ -87,6 +87,7 @@ var (
DefaultMergeMessageAllAuthors bool
DefaultMergeMessageMaxApprovers int
DefaultMergeMessageOfficialApproversOnly bool
+ DefaultUpdateStyle string
PopulateSquashCommentWithCommitMessages bool
AddCoCommitterTrailers bool
TestConflictingPatchesWithGitApply bool
@@ -216,6 +217,7 @@ var (
DefaultMergeMessageAllAuthors bool
DefaultMergeMessageMaxApprovers int
DefaultMergeMessageOfficialApproversOnly bool
+ DefaultUpdateStyle string
PopulateSquashCommentWithCommitMessages bool
AddCoCommitterTrailers bool
TestConflictingPatchesWithGitApply bool
@@ -232,6 +234,7 @@ var (
DefaultMergeMessageAllAuthors: false,
DefaultMergeMessageMaxApprovers: 10,
DefaultMergeMessageOfficialApproversOnly: true,
+ DefaultUpdateStyle: "merge",
PopulateSquashCommentWithCommitMessages: false,
AddCoCommitterTrailers: true,
RetargetChildrenOnMerge: true,
diff --git a/modules/structs/repo.go b/modules/structs/repo.go
index 36190fe36b..b5f54a2a7a 100644
--- a/modules/structs/repo.go
+++ b/modules/structs/repo.go
@@ -105,6 +105,7 @@ type Repository struct {
DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge"`
DefaultMergeStyle string `json:"default_merge_style"`
DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit"`
+ DefaultUpdateStyle string `json:"default_update_style"`
AvatarURL string `json:"avatar_url"`
Internal bool `json:"internal"`
MirrorInterval string `json:"mirror_interval"`
@@ -225,6 +226,8 @@ type EditRepoOption struct {
DefaultDeleteBranchAfterMerge *bool `json:"default_delete_branch_after_merge,omitempty"`
// set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", "squash", or "fast-forward-only".
DefaultMergeStyle *string `json:"default_merge_style,omitempty"`
+ // set to a update style to be used by this repository: "rebase" or "merge"
+ DefaultUpdateStyle *string `json:"default_update_style,omitempty"`
// set to `true` to allow edits from maintainers by default
DefaultAllowMaintainerEdit *bool `json:"default_allow_maintainer_edit,omitempty"`
// set to `true` to archive this repository.
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 6e1524f583..3b413b8f92 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -2246,6 +2246,7 @@ settings.pulls_desc = Enable repository pull requests
settings.pulls.ignore_whitespace = Ignore whitespace for conflicts
settings.pulls.enable_autodetect_manual_merge = Enable autodetect manual merge (Note: In some special cases, misjudgments can occur)
settings.pulls.allow_rebase_update = Enable updating pull request branch by rebase
+settings.default_update_style_desc=Default update style used for updating pull requests that are behind the base branch.
settings.pulls.default_delete_branch_after_merge = Delete pull request branch after merge by default
settings.pulls.default_allow_edits_from_maintainers = Allow edits from maintainers by default
settings.releases_desc = Enable repository releases
diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go
index 93d40d2dbf..04c6fde453 100644
--- a/routers/api/v1/repo/repo.go
+++ b/routers/api/v1/repo/repo.go
@@ -937,6 +937,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
AllowRebaseUpdate: true,
DefaultDeleteBranchAfterMerge: false,
DefaultMergeStyle: repo_model.MergeStyleMerge,
+ DefaultUpdateStyle: repo_model.UpdateStyleMerge,
DefaultAllowMaintainerEdit: false,
}
} else {
@@ -976,6 +977,9 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
if opts.DefaultMergeStyle != nil {
config.DefaultMergeStyle = repo_model.MergeStyle(*opts.DefaultMergeStyle)
}
+ if opts.DefaultUpdateStyle != nil {
+ config.DefaultUpdateStyle = repo_model.UpdateStyle(*opts.DefaultUpdateStyle)
+ }
if opts.DefaultAllowMaintainerEdit != nil {
config.DefaultAllowMaintainerEdit = *opts.DefaultAllowMaintainerEdit
}
diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go
index 283d476df1..e5134c1f62 100644
--- a/routers/web/repo/actions/actions.go
+++ b/routers/web/repo/actions/actions.go
@@ -5,6 +5,7 @@ package actions
import (
"bytes"
+ stdCtx "context"
"fmt"
"net/http"
"slices"
@@ -224,7 +225,7 @@ func List(ctx *context.Context) {
return
}
- if err := loadIsRefDeleted(ctx, runs); err != nil {
+ if err := loadIsRefDeleted(ctx, ctx.Repo.Repository.ID, runs); err != nil {
log.Error("LoadIsRefDeleted", err)
}
@@ -254,7 +255,7 @@ func List(ctx *context.Context) {
// loadIsRefDeleted loads the IsRefDeleted field for each run in the list.
// TODO: move this function to models/actions/run_list.go but now it will result in a circular import.
-func loadIsRefDeleted(ctx *context.Context, runs actions_model.RunList) error {
+func loadIsRefDeleted(ctx stdCtx.Context, repoID int64, runs actions_model.RunList) error {
branches := make(container.Set[string], len(runs))
for _, run := range runs {
refName := git.RefName(run.Ref)
@@ -266,14 +267,14 @@ func loadIsRefDeleted(ctx *context.Context, runs actions_model.RunList) error {
return nil
}
- branchInfos, err := git_model.GetBranches(ctx, ctx.Repo.Repository.ID, branches.Values(), false)
+ branchInfos, err := git_model.GetBranches(ctx, repoID, branches.Values(), false)
if err != nil {
return err
}
branchSet := git_model.BranchesToNamesSet(branchInfos)
for _, run := range runs {
refName := git.RefName(run.Ref)
- if refName.IsBranch() && !branchSet.Contains(run.Ref) {
+ if refName.IsBranch() && !branchSet.Contains(refName.ShortName()) {
run.IsRefDeleted = true
}
}
diff --git a/routers/web/repo/actions/actions_test.go b/routers/web/repo/actions/actions_test.go
new file mode 100644
index 0000000000..939c4aaf57
--- /dev/null
+++ b/routers/web/repo/actions/actions_test.go
@@ -0,0 +1,33 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package actions
+
+import (
+ "testing"
+
+ actions_model "code.gitea.io/gitea/models/actions"
+ "code.gitea.io/gitea/models/db"
+ unittest "code.gitea.io/gitea/models/unittest"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_loadIsRefDeleted(t *testing.T) {
+ unittest.PrepareTestEnv(t)
+
+ runs, total, err := db.FindAndCount[actions_model.ActionRun](db.DefaultContext,
+ actions_model.FindRunOptions{RepoID: 4, Ref: "refs/heads/test"})
+ require.NoError(t, err)
+ assert.Len(t, runs, 1)
+ assert.EqualValues(t, 1, total)
+ for _, run := range runs {
+ assert.False(t, run.IsRefDeleted)
+ }
+
+ require.NoError(t, loadIsRefDeleted(db.DefaultContext, 4, runs))
+ for _, run := range runs {
+ assert.True(t, run.IsRefDeleted)
+ }
+}
diff --git a/routers/web/repo/actions/main_test.go b/routers/web/repo/actions/main_test.go
new file mode 100644
index 0000000000..a82f9c6672
--- /dev/null
+++ b/routers/web/repo/actions/main_test.go
@@ -0,0 +1,14 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package actions
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/unittest"
+)
+
+func TestMain(m *testing.M) {
+ unittest.MainTest(m)
+}
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index 78fb5e6c01..c154a9b665 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -1918,6 +1918,21 @@ func ViewIssue(ctx *context.Context) {
ctx.Data["MergeStyle"] = mergeStyle
+ var updateStyle repo_model.UpdateStyle
+ // Check correct values and select default
+ if ms, ok := ctx.Data["UpdateStyle"].(repo_model.UpdateStyle); !ok ||
+ !prConfig.IsUpdateStyleAllowed(ms) {
+ defaultUpdateStyle := prConfig.GetDefaultUpdateStyle()
+ if prConfig.IsUpdateStyleAllowed(defaultUpdateStyle) && !ok {
+ updateStyle = defaultUpdateStyle
+ } else if prConfig.AllowMerge {
+ updateStyle = repo_model.UpdateStyleMerge
+ } else if prConfig.AllowRebase {
+ updateStyle = repo_model.UpdateStyleRebase
+ }
+ }
+ ctx.Data["UpdateStyle"] = updateStyle
+
defaultMergeMessage, defaultMergeBody, err := pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pull, mergeStyle)
if err != nil {
ctx.ServerError("GetDefaultMergeMessage", err)
diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go
index ce506eafb1..2c6ca77a6a 100644
--- a/routers/web/repo/setting/setting.go
+++ b/routers/web/repo/setting/setting.go
@@ -262,6 +262,7 @@ func UnitsPost(ctx *context.Context) {
AllowRebaseUpdate: form.PullsAllowRebaseUpdate,
DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge,
DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle),
+ DefaultUpdateStyle: repo_model.UpdateStyle(form.PullsDefaultUpdateStyle),
DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit,
},
})
diff --git a/services/convert/pull.go b/services/convert/pull.go
index 4ec24a8276..70dc22445a 100644
--- a/services/convert/pull.go
+++ b/services/convert/pull.go
@@ -29,6 +29,11 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
err error
)
+ if err = pr.LoadIssue(ctx); err != nil {
+ log.Error("pr.LoadIssue[%d]: %v", pr.ID, err)
+ return nil
+ }
+
if err = pr.Issue.LoadRepo(ctx); err != nil {
log.Error("pr.Issue.LoadRepo[%d]: %v", pr.ID, err)
return nil
diff --git a/services/convert/repository.go b/services/convert/repository.go
index 2fb6f6d7c0..e4b2c7b8bc 100644
--- a/services/convert/repository.go
+++ b/services/convert/repository.go
@@ -101,6 +101,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
allowRebaseUpdate := false
defaultDeleteBranchAfterMerge := false
defaultMergeStyle := repo_model.MergeStyleMerge
+ defaultUpdateStyle := repo_model.UpdateStyleMerge
defaultAllowMaintainerEdit := false
if unit, err := repo.GetUnit(ctx, unit_model.TypePullRequests); err == nil {
config := unit.PullRequestsConfig()
@@ -114,6 +115,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
allowRebaseUpdate = config.AllowRebaseUpdate
defaultDeleteBranchAfterMerge = config.DefaultDeleteBranchAfterMerge
defaultMergeStyle = config.GetDefaultMergeStyle()
+ defaultUpdateStyle = config.GetDefaultUpdateStyle()
defaultAllowMaintainerEdit = config.DefaultAllowMaintainerEdit
}
hasProjects := false
@@ -231,6 +233,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
AllowRebaseUpdate: allowRebaseUpdate,
DefaultDeleteBranchAfterMerge: defaultDeleteBranchAfterMerge,
DefaultMergeStyle: string(defaultMergeStyle),
+ DefaultUpdateStyle: string(defaultUpdateStyle),
DefaultAllowMaintainerEdit: defaultAllowMaintainerEdit,
AvatarURL: repo.AvatarLink(ctx),
Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate,
diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
index f6e184fcb6..1ce9b298ad 100644
--- a/services/forms/repo_form.go
+++ b/services/forms/repo_form.go
@@ -189,6 +189,7 @@ type RepoUnitSettingForm struct {
PullsAllowFastForwardOnly bool
PullsAllowManualMerge bool
PullsDefaultMergeStyle string
+ PullsDefaultUpdateStyle string
EnableAutodetectManualMerge bool
PullsAllowRebaseUpdate bool
DefaultDeleteBranchAfterMerge bool
diff --git a/services/webhook/notifier.go b/services/webhook/notifier.go
index 8bfd03024f..fed33d8008 100644
--- a/services/webhook/notifier.go
+++ b/services/webhook/notifier.go
@@ -409,6 +409,10 @@ func (m *webhookNotifier) CreateIssueComment(ctx context.Context, doer *user_mod
var pullRequest *api.PullRequest
if issue.IsPull {
eventType = webhook_module.HookEventPullRequestComment
+ if err := issue.LoadPullRequest(ctx); err != nil {
+ log.Error("LoadPullRequest: %v", err)
+ return
+ }
pullRequest = convert.ToAPIPullRequest(ctx, issue.PullRequest, doer)
} else {
eventType = webhook_module.HookEventIssueComment
diff --git a/templates/package/content/container.tmpl b/templates/package/content/container.tmpl
index 78fdac3bad..dd1c24269b 100644
--- a/templates/package/content/container.tmpl
+++ b/templates/package/content/container.tmpl
@@ -36,11 +36,13 @@
</thead>
<tbody>
{{range .PackageDescriptor.Metadata.Manifests}}
- <tr>
+ {{if ne .Platform "unknown/unknown"}}
+ <tr>
<td><a href="{{$.PackageDescriptor.PackageWebLink}}/{{PathEscape .Digest}}">{{.Digest}}</a></td>
<td>{{.Platform}}</td>
<td>{{ctx.Locale.TrSize .Size}}</td>
- </tr>
+ </tr>
+ {{end}}
{{end}}
</tbody>
</table>
diff --git a/templates/repo/actions/status.tmpl b/templates/repo/actions/status.tmpl
index a0e02cf8d7..99fa74ac17 100644
--- a/templates/repo/actions/status.tmpl
+++ b/templates/repo/actions/status.tmpl
@@ -17,13 +17,15 @@
{{svg "octicon-check-circle-fill" $size (printf "text green %s" $className)}}
{{else if eq .status "skipped"}}
{{svg "octicon-skip" $size (printf "text grey %s" $className)}}
+{{else if eq .status "cancelled"}}
+ {{svg "octicon-stop" $size (printf "text grey %s" $className)}}
{{else if eq .status "waiting"}}
{{svg "octicon-clock" $size (printf "text yellow %s" $className)}}
{{else if eq .status "blocked"}}
{{svg "octicon-blocked" $size (printf "text yellow %s" $className)}}
{{else if eq .status "running"}}
{{svg "octicon-meter" $size (printf "text yellow job-status-rotate %s" $className)}}
-{{else if or (eq .status "failure") or (eq .status "cancelled") or (eq .status "unknown")}}
+{{else}}{{/*failure, unknown*/}}
{{svg "octicon-x-circle-fill" $size (printf "text red %s" $className)}}
{{end}}
</span>
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 adce052dee..9fbc8b018b 100644
--- a/templates/repo/issue/view_content/update_branch_by_merge.tmpl
+++ b/templates/repo/issue/view_content/update_branch_by_merge.tmpl
@@ -9,23 +9,31 @@
{{if and $.UpdateAllowed $.UpdateByRebaseAllowed}}
<div class="tw-inline-block">
<div class="ui buttons update-button">
- <button class="ui button" data-do="{{$.Link}}/update" data-redirect="{{$.Link}}">
+ <button class="ui button" data-do="{{$.Link}}/update?style={{$.UpdateStyle}}" data-redirect="{{$.Link}}">
<span class="button-text">
- {{ctx.Locale.Tr "repo.pulls.update_branch"}}
+ {{if eq $.UpdateStyle "rebase"}}
+ {{ctx.Locale.Tr "repo.pulls.update_branch_rebase"}}
+ {{else}}
+ {{ctx.Locale.Tr "repo.pulls.update_branch"}}
+ {{end}}
</span>
</button>
<div class="ui dropdown icon button">
{{svg "octicon-triangle-down"}}
<div class="menu">
- <a class="item active selected" data-do="{{$.Link}}/update">{{ctx.Locale.Tr "repo.pulls.update_branch"}}</a>
- <a class="item" data-do="{{$.Link}}/update?style=rebase">{{ctx.Locale.Tr "repo.pulls.update_branch_rebase"}}</a>
+ <a class="item {{if ne $.UpdateStyle "rebase"}}active selected{{end}}" data-do="{{$.Link}}/update?style=merge">
+ {{ctx.Locale.Tr "repo.pulls.update_branch"}}
+ </a>
+ <a class="item {{if eq $.UpdateStyle "rebase"}}active selected{{end}}" data-do="{{$.Link}}/update?style=rebase">
+ {{ctx.Locale.Tr "repo.pulls.update_branch_rebase"}}
+ </a>
</div>
</div>
</div>
</div>
{{end}}
{{if and $.UpdateAllowed (not $.UpdateByRebaseAllowed)}}
- <form action="{{$.Link}}/update" method="post" class="ui update-branch-form">
+ <form action="{{$.Link}}/update?style=merge" method="post" class="ui update-branch-form">
{{$.CsrfTokenHtml}}
<button class="ui compact button">
<span class="ui text">{{ctx.Locale.Tr "repo.pulls.update_branch"}}</span>
diff --git a/templates/repo/settings/units/pulls.tmpl b/templates/repo/settings/units/pulls.tmpl
index 4e9c53e0f4..2e637d4e0b 100644
--- a/templates/repo/settings/units/pulls.tmpl
+++ b/templates/repo/settings/units/pulls.tmpl
@@ -106,6 +106,29 @@
</div>
</div>
<div class="field">
+ <p>
+ {{ctx.Locale.Tr "repo.settings.default_update_style_desc"}}
+ </p>
+ <div class="ui dropdown selection">
+ <select name="pulls_default_update_style">
+ <option value="merge" {{if or (not $pullRequestEnabled) (eq $prUnit.PullRequestsConfig.DefaultUpdateStyle "merge")}}selected{{end}}>{{ctx.Locale.Tr "repo.pulls.update_branch"}}</option>
+ <option value="rebase" {{if or (not $pullRequestEnabled) (eq $prUnit.PullRequestsConfig.DefaultUpdateStyle "rebase")}}selected{{end}}>{{ctx.Locale.Tr "repo.pulls.update_branch_rebase"}}</option>
+ </select>{{svg "octicon-triangle-down" 14 "dropdown icon"}}
+ <div class="default text">
+ {{if (eq $prUnit.PullRequestsConfig.DefaultUpdateStyle "merge")}}
+ {{ctx.Locale.Tr "repo.pulls.update_branch"}}
+ {{end}}
+ {{if (eq $prUnit.PullRequestsConfig.DefaultUpdateStyle "rebase")}}
+ {{ctx.Locale.Tr "repo.pulls.update_branch_rebase"}}
+ {{end}}
+ </div>
+ <div class="menu">
+ <div class="item" data-value="merge">{{ctx.Locale.Tr "repo.pulls.update_branch"}}</div>
+ <div class="item" data-value="rebase">{{ctx.Locale.Tr "repo.pulls.update_branch_rebase"}}</div>
+ </div>
+ </div>
+ </div>
+ <div class="field">
<div class="ui checkbox">
<input name="default_delete_branch_after_merge" type="checkbox" {{if or (not $pullRequestEnabled) ($prUnit.PullRequestsConfig.DefaultDeleteBranchAfterMerge)}}checked{{end}}>
<label>{{ctx.Locale.Tr "repo.settings.pulls.default_delete_branch_after_merge"}}</label>
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index f0a0b86622..1feeebba98 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -23321,6 +23321,11 @@
"type": "string",
"x-go-name": "DefaultMergeStyle"
},
+ "default_update_style": {
+ "description": "set to a update style to be used by this repository: \"rebase\" or \"merge\"",
+ "type": "string",
+ "x-go-name": "DefaultUpdateStyle"
+ },
"description": {
"description": "a short description of the repository.",
"type": "string",
@@ -26605,6 +26610,10 @@
"type": "string",
"x-go-name": "DefaultMergeStyle"
},
+ "default_update_style": {
+ "type": "string",
+ "x-go-name": "DefaultUpdateStyle"
+ },
"description": {
"type": "string",
"x-go-name": "Description"
diff --git a/tests/integration/pull_update_test.go b/tests/integration/pull_update_test.go
index 08041f0717..f36ea88c2b 100644
--- a/tests/integration/pull_update_test.go
+++ b/tests/integration/pull_update_test.go
@@ -4,6 +4,7 @@
package integration
import (
+ "fmt"
"net/http"
"net/url"
"strings"
@@ -16,6 +17,9 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/setting"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/test"
pull_service "code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository"
files_service "code.gitea.io/gitea/services/repository/files"
@@ -83,6 +87,102 @@ func TestAPIPullUpdateByRebase(t *testing.T) {
})
}
+func TestAPIViewUpdateSettings(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ defer tests.PrepareTestEnv(t)()
+ // Create PR to test
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26})
+ pr := createOutdatedPR(t, user, org26)
+
+ // Test GetDiverging
+ diffCount, err := pull_service.GetDiverging(git.DefaultContext, pr)
+ require.NoError(t, err)
+ assert.EqualValues(t, 1, diffCount.Behind)
+ assert.EqualValues(t, 1, diffCount.Ahead)
+ require.NoError(t, pr.LoadBaseRepo(db.DefaultContext))
+ require.NoError(t, pr.LoadIssue(db.DefaultContext))
+
+ session := loginUser(t, "user2")
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
+
+ defaultUpdateStyle := "rebase"
+ editOption := api.EditRepoOption{
+ DefaultUpdateStyle: &defaultUpdateStyle,
+ }
+
+ req := NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s", pr.BaseRepo.OwnerName, pr.BaseRepo.Name), editOption).AddTokenAuth(token)
+ session.MakeRequest(t, req, http.StatusOK)
+ assertViewPullUpdate(t, pr, session, "rebase", true)
+
+ defaultUpdateStyle = "merge"
+ req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s", pr.BaseRepo.OwnerName, pr.BaseRepo.Name), editOption).AddTokenAuth(token)
+ session.MakeRequest(t, req, http.StatusOK)
+ assertViewPullUpdate(t, pr, session, "merge", true)
+ })
+}
+
+func TestViewPullUpdateByMerge(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ testViewPullUpdate(t, "merge")
+ })
+}
+
+func TestViewPullUpdateByRebase(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ testViewPullUpdate(t, "rebase")
+ })
+}
+
+func testViewPullUpdate(t *testing.T, updateStyle string) {
+ defer test.MockVariableValue(&setting.Repository.PullRequest.DefaultUpdateStyle, updateStyle)()
+ defer tests.PrepareTestEnv(t)()
+ // Create PR to test
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26})
+ pr := createOutdatedPR(t, user, org26)
+
+ // Test GetDiverging
+ diffCount, err := pull_service.GetDiverging(git.DefaultContext, pr)
+ require.NoError(t, err)
+ assert.EqualValues(t, 1, diffCount.Behind)
+ assert.EqualValues(t, 1, diffCount.Ahead)
+ require.NoError(t, pr.LoadBaseRepo(db.DefaultContext))
+ require.NoError(t, pr.LoadIssue(db.DefaultContext))
+
+ session := loginUser(t, "user2")
+ assertViewPullUpdate(t, pr, session, updateStyle, true)
+}
+
+func assertViewPullUpdate(t *testing.T, pr *issues_model.PullRequest, session *TestSession, expectedStyle string, dropdownExpected bool) {
+ req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/pulls/%d", pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index))
+ resp := session.MakeRequest(t, req, http.StatusOK)
+
+ htmlDoc := NewHTMLParser(t, resp.Body)
+ // Verify that URL of the update button is shown correctly.
+ var mainExpectedURL string
+ mergeExpectedURL := fmt.Sprintf("/%s/%s/pulls/%d/update?style=merge", pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index)
+ rebaseExpectedURL := fmt.Sprintf("/%s/%s/pulls/%d/update?style=rebase", pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index)
+ if expectedStyle == "rebase" {
+ mainExpectedURL = rebaseExpectedURL
+ if dropdownExpected {
+ htmlDoc.AssertElement(t, fmt.Sprintf(".update-button .dropdown .menu .item[data-do=\"%s\"]:not(.active.selected)", mergeExpectedURL), true)
+ htmlDoc.AssertElement(t, fmt.Sprintf(".update-button .dropdown .menu .active.selected.item[data-do=\"%s\"]", rebaseExpectedURL), true)
+ }
+ } else {
+ mainExpectedURL = mergeExpectedURL
+ if dropdownExpected {
+ htmlDoc.AssertElement(t, fmt.Sprintf(".update-button .dropdown .menu .active.selected.item[data-do=\"%s\"]", mergeExpectedURL), true)
+ htmlDoc.AssertElement(t, fmt.Sprintf(".update-button .dropdown .menu .item[data-do=\"%s\"]:not(.active.selected)", rebaseExpectedURL), true)
+ }
+ }
+ if dropdownExpected {
+ htmlDoc.AssertElement(t, fmt.Sprintf(".update-button .button[data-do=\"%s\"]", mainExpectedURL), true)
+ } else {
+ htmlDoc.AssertElement(t, fmt.Sprintf("form[action=\"%s\"]", mainExpectedURL), true)
+ }
+}
+
func createOutdatedPR(t *testing.T, actor, forkOrg *user_model.User) *issues_model.PullRequest {
baseRepo, _, _ := tests.CreateDeclarativeRepo(t, actor, "repo-pr-update", nil, nil, nil)
diff --git a/web_src/js/components/ActionRunStatus.vue b/web_src/js/components/ActionRunStatus.vue
index 7ada543fea..eb7f780fbe 100644
--- a/web_src/js/components/ActionRunStatus.vue
+++ b/web_src/js/components/ActionRunStatus.vue
@@ -28,12 +28,13 @@ export default {
};
</script>
<template>
- <span class="tw-flex tw-items-center" :data-tooltip-content="localeStatus" v-if="status">
+ <span class="tw-flex tw-items-center" :data-tooltip-content="localeStatus ?? status" v-if="status">
<SvgIcon name="octicon-check-circle-fill" class="text green" :size="size" :class-name="className" v-if="status === 'success'"/>
<SvgIcon name="octicon-skip" class="text grey" :size="size" :class-name="className" v-else-if="status === 'skipped'"/>
+ <SvgIcon name="octicon-stop" class="text yellow" :size="size" :class-name="className" v-else-if="status === 'cancelled'"/>
<SvgIcon name="octicon-clock" class="text yellow" :size="size" :class-name="className" v-else-if="status === 'waiting'"/>
<SvgIcon name="octicon-blocked" class="text yellow" :size="size" :class-name="className" v-else-if="status === 'blocked'"/>
<SvgIcon name="octicon-meter" class="text yellow" :size="size" :class-name="'job-status-rotate ' + className" v-else-if="status === 'running'"/>
- <SvgIcon name="octicon-x-circle-fill" class="text red" :size="size" v-else-if="['failure', 'cancelled', 'unknown'].includes(status)"/>
+ <SvgIcon name="octicon-x-circle-fill" class="text red" :size="size" v-else/><!-- failure, unknown -->
</span>
</template>
diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue
index 13ac9427f3..136ad3693d 100644
--- a/web_src/js/components/RepoActionView.vue
+++ b/web_src/js/components/RepoActionView.vue
@@ -570,11 +570,13 @@ export function initRepositoryActionView() {
.action-info-summary-title {
display: flex;
+ align-items: center;
+ gap: 0.5em;
}
.action-info-summary-title-text {
font-size: 20px;
- margin: 0 0 0 8px;
+ margin: 0;
flex: 1;
overflow-wrap: anywhere;
}
diff --git a/web_src/js/svg.js b/web_src/js/svg.js
index 9ef5f28e2f..76df7ff211 100644
--- a/web_src/js/svg.js
+++ b/web_src/js/svg.js
@@ -64,6 +64,7 @@ import octiconSidebarCollapse from '../../public/assets/img/svg/octicon-sidebar-
import octiconSidebarExpand from '../../public/assets/img/svg/octicon-sidebar-expand.svg';
import octiconSkip from '../../public/assets/img/svg/octicon-skip.svg';
import octiconStar from '../../public/assets/img/svg/octicon-star.svg';
+import octiconStop from '../../public/assets/img/svg/octicon-stop.svg';
import octiconStrikethrough from '../../public/assets/img/svg/octicon-strikethrough.svg';
import octiconSync from '../../public/assets/img/svg/octicon-sync.svg';
import octiconTable from '../../public/assets/img/svg/octicon-table.svg';
@@ -138,6 +139,7 @@ const svgs = {
'octicon-sidebar-expand': octiconSidebarExpand,
'octicon-skip': octiconSkip,
'octicon-star': octiconStar,
+ 'octicon-stop': octiconStop,
'octicon-strikethrough': octiconStrikethrough,
'octicon-sync': octiconSync,
'octicon-table': octiconTable,