With this option, it is possible to require a linear commit history with the following benefits over the next best option `Rebase+fast-forward`: The original commits continue existing, with the original signatures continuing to stay valid instead of being rewritten, there is no merge commit, and reverting commits becomes easier. Closes #24906tags/v1.22.0-rc0
@@ -1044,7 +1044,7 @@ LEVEL = Info | |||
;; List of keywords used in Pull Request comments to automatically reopen a related issue | |||
;REOPEN_KEYWORDS = reopen,reopens,reopened | |||
;; | |||
;; Set default merge style for repository creating, valid options: merge, rebase, rebase-merge, squash | |||
;; Set default merge style for repository creating, valid options: merge, rebase, rebase-merge, squash, fast-forward-only | |||
;DEFAULT_MERGE_STYLE = merge | |||
;; | |||
;; In the default merge message for squash commits include at most this many commits |
@@ -126,7 +126,7 @@ In addition, there is _`StaticRootPath`_ which can be set as a built-in at build | |||
keywords used in Pull Request comments to automatically close a related issue | |||
- `REOPEN_KEYWORDS`: **reopen**, **reopens**, **reopened**: List of keywords used in Pull Request comments to automatically reopen | |||
a related issue | |||
- `DEFAULT_MERGE_STYLE`: **merge**: Set default merge style for repository creating, valid options: `merge`, `rebase`, `rebase-merge`, `squash` | |||
- `DEFAULT_MERGE_STYLE`: **merge**: Set default merge style for repository creating, valid options: `merge`, `rebase`, `rebase-merge`, `squash`, `fast-forward-only` | |||
- `DEFAULT_MERGE_MESSAGE_COMMITS_LIMIT`: **50**: In the default merge message for squash commits include at most this many commits. Set to `-1` to include all commits | |||
- `DEFAULT_MERGE_MESSAGE_SIZE`: **5120**: In the default merge message for squash commits limit the size of the commit messages. Set to `-1` to have no limit. Only used if `POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES` is `true`. | |||
- `DEFAULT_MERGE_MESSAGE_ALL_AUTHORS`: **false**: In the default merge message for squash commits walk all commits to include all authors in the Co-authored-by otherwise just use those in the limited list |
@@ -125,7 +125,7 @@ menu: | |||
- `CLOSE_KEYWORDS`: **close**, **closes**, **closed**, **fix**, **fixes**, **fixed**, **resolve**, **resolves**, **resolved**: 在拉取请求评论中用于自动关闭相关问题的关键词列表。 | |||
- `REOPEN_KEYWORDS`: **reopen**, **reopens**, **reopened**: 在拉取请求评论中用于自动重新打开相关问题的 | |||
关键词列表。 | |||
- `DEFAULT_MERGE_STYLE`: **merge**: 设置创建仓库的默认合并方式,可选: `merge`, `rebase`, `rebase-merge`, `squash` | |||
- `DEFAULT_MERGE_STYLE`: **merge**: 设置创建仓库的默认合并方式,可选: `merge`, `rebase`, `rebase-merge`, `squash`, `fast-forward-only` | |||
- `DEFAULT_MERGE_MESSAGE_COMMITS_LIMIT`: **50**: 在默认合并消息中,对于`squash`提交,最多包括此数量的提交。设置为 -1 以包括所有提交。 | |||
- `DEFAULT_MERGE_MESSAGE_SIZE`: **5120**: 在默认的合并消息中,对于`squash`提交,限制提交消息的大小。设置为 `-1`以取消限制。仅在`POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES`为`true`时使用。 | |||
- `DEFAULT_MERGE_MESSAGE_ALL_AUTHORS`: **false**: 在默认合并消息中,对于`squash`提交,遍历所有提交以包括所有作者的`Co-authored-by`,否则仅使用限定列表中的作者。 |
@@ -493,6 +493,23 @@ func (err ErrMergeUnrelatedHistories) Error() string { | |||
return fmt.Sprintf("Merge UnrelatedHistories Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut) | |||
} | |||
// ErrMergeDivergingFastForwardOnly represents an error if a fast-forward-only merge fails because the branches diverge | |||
type ErrMergeDivergingFastForwardOnly struct { | |||
StdOut string | |||
StdErr string | |||
Err error | |||
} | |||
// IsErrMergeDivergingFastForwardOnly checks if an error is a ErrMergeDivergingFastForwardOnly. | |||
func IsErrMergeDivergingFastForwardOnly(err error) bool { | |||
_, ok := err.(ErrMergeDivergingFastForwardOnly) | |||
return ok | |||
} | |||
func (err ErrMergeDivergingFastForwardOnly) Error() string { | |||
return fmt.Sprintf("Merge DivergingFastForwardOnly Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut) | |||
} | |||
// ErrRebaseConflicts represents an error if rebase fails with a conflict | |||
type ErrRebaseConflicts struct { | |||
Style repo_model.MergeStyle |
@@ -21,6 +21,8 @@ const ( | |||
MergeStyleRebaseMerge MergeStyle = "rebase-merge" | |||
// MergeStyleSquash squash commits into single commit before merging | |||
MergeStyleSquash MergeStyle = "squash" | |||
// MergeStyleFastForwardOnly fast-forward merge if possible, otherwise fail | |||
MergeStyleFastForwardOnly MergeStyle = "fast-forward-only" | |||
// MergeStyleManuallyMerged pr has been merged manually, just mark it as merged directly | |||
MergeStyleManuallyMerged MergeStyle = "manually-merged" | |||
// MergeStyleRebaseUpdate not a merge style, used to update pull head by rebase |
@@ -122,6 +122,7 @@ type PullRequestsConfig struct { | |||
AllowRebase bool | |||
AllowRebaseMerge bool | |||
AllowSquash bool | |||
AllowFastForwardOnly bool | |||
AllowManualMerge bool | |||
AutodetectManualMerge bool | |||
AllowRebaseUpdate bool | |||
@@ -148,6 +149,7 @@ func (cfg *PullRequestsConfig) IsMergeStyleAllowed(mergeStyle MergeStyle) bool { | |||
mergeStyle == MergeStyleRebase && cfg.AllowRebase || | |||
mergeStyle == MergeStyleRebaseMerge && cfg.AllowRebaseMerge || | |||
mergeStyle == MergeStyleSquash && cfg.AllowSquash || | |||
mergeStyle == MergeStyleFastForwardOnly && cfg.AllowFastForwardOnly || | |||
mergeStyle == MergeStyleManuallyMerged && cfg.AllowManualMerge | |||
} | |||
@@ -96,7 +96,7 @@ func (err ErrBranchNotExist) Unwrap() error { | |||
return util.ErrNotExist | |||
} | |||
// ErrPushOutOfDate represents an error if merging fails due to unrelated histories | |||
// ErrPushOutOfDate represents an error if merging fails due to the base branch being updated | |||
type ErrPushOutOfDate struct { | |||
StdOut string | |||
StdErr string |
@@ -87,7 +87,11 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re | |||
units = append(units, repo_model.RepoUnit{ | |||
RepoID: repo.ID, | |||
Type: tp, | |||
Config: &repo_model.PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, DefaultMergeStyle: repo_model.MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle), AllowRebaseUpdate: true}, | |||
Config: &repo_model.PullRequestsConfig{ | |||
AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, AllowFastForwardOnly: true, | |||
DefaultMergeStyle: repo_model.MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle), | |||
AllowRebaseUpdate: true, | |||
}, | |||
}) | |||
} else { | |||
units = append(units, repo_model.RepoUnit{ |
@@ -98,6 +98,7 @@ type Repository struct { | |||
AllowRebase bool `json:"allow_rebase"` | |||
AllowRebaseMerge bool `json:"allow_rebase_explicit"` | |||
AllowSquash bool `json:"allow_squash_merge"` | |||
AllowFastForwardOnly bool `json:"allow_fast_forward_only_merge"` | |||
AllowRebaseUpdate bool `json:"allow_rebase_update"` | |||
DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge"` | |||
DefaultMergeStyle string `json:"default_merge_style"` | |||
@@ -195,6 +196,8 @@ type EditRepoOption struct { | |||
AllowRebaseMerge *bool `json:"allow_rebase_explicit,omitempty"` | |||
// either `true` to allow squash-merging pull requests, or `false` to prevent squash-merging. | |||
AllowSquash *bool `json:"allow_squash_merge,omitempty"` | |||
// either `true` to allow fast-forward-only merging pull requests, or `false` to prevent fast-forward-only merging. | |||
AllowFastForwardOnly *bool `json:"allow_fast_forward_only_merge,omitempty"` | |||
// either `true` to allow mark pr as merged manually, or `false` to prevent it. | |||
AllowManualMerge *bool `json:"allow_manual_merge,omitempty"` | |||
// either `true` to enable AutodetectManualMerge, or `false` to prevent it. Note: In some special cases, misjudgments can occur. | |||
@@ -203,7 +206,7 @@ type EditRepoOption struct { | |||
AllowRebaseUpdate *bool `json:"allow_rebase_update,omitempty"` | |||
// set to `true` to delete pr branch after merge by default | |||
DefaultDeleteBranchAfterMerge *bool `json:"default_delete_branch_after_merge,omitempty"` | |||
// set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", or "squash". | |||
// 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 `true` to allow edits from maintainers by default | |||
DefaultAllowMaintainerEdit *bool `json:"default_allow_maintainer_edit,omitempty"` |
@@ -1775,6 +1775,7 @@ pulls.merge_pull_request = Create merge commit | |||
pulls.rebase_merge_pull_request = Rebase then fast-forward | |||
pulls.rebase_merge_commit_pull_request = Rebase then create merge commit | |||
pulls.squash_merge_pull_request = Create squash commit | |||
pulls.fast_forward_only_merge_pull_request = Fast-forward only | |||
pulls.merge_manually = Manually merged | |||
pulls.merge_commit_id = The merge commit ID | |||
pulls.require_signed_wont_sign = The branch requires signed commits but this merge will not be signed |
@@ -885,6 +885,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { | |||
AllowRebase: true, | |||
AllowRebaseMerge: true, | |||
AllowSquash: true, | |||
AllowFastForwardOnly: true, | |||
AllowManualMerge: true, | |||
AutodetectManualMerge: false, | |||
AllowRebaseUpdate: true, | |||
@@ -911,6 +912,9 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { | |||
if opts.AllowSquash != nil { | |||
config.AllowSquash = *opts.AllowSquash | |||
} | |||
if opts.AllowFastForwardOnly != nil { | |||
config.AllowFastForwardOnly = *opts.AllowFastForwardOnly | |||
} | |||
if opts.AllowManualMerge != nil { | |||
config.AllowManualMerge = *opts.AllowManualMerge | |||
} |
@@ -35,6 +35,7 @@ func TestRepoEdit(t *testing.T) { | |||
allowRebase := false | |||
allowRebaseMerge := false | |||
allowSquashMerge := false | |||
allowFastForwardOnlyMerge := false | |||
archived := true | |||
opts := api.EditRepoOption{ | |||
Name: &ctx.Repo.Repository.Name, | |||
@@ -50,6 +51,7 @@ func TestRepoEdit(t *testing.T) { | |||
AllowRebase: &allowRebase, | |||
AllowRebaseMerge: &allowRebaseMerge, | |||
AllowSquash: &allowSquashMerge, | |||
AllowFastForwardOnly: &allowFastForwardOnlyMerge, | |||
Archived: &archived, | |||
} | |||
@@ -1862,6 +1862,8 @@ func ViewIssue(ctx *context.Context) { | |||
mergeStyle = repo_model.MergeStyleRebaseMerge | |||
} else if prConfig.AllowSquash { | |||
mergeStyle = repo_model.MergeStyleSquash | |||
} else if prConfig.AllowFastForwardOnly { | |||
mergeStyle = repo_model.MergeStyleFastForwardOnly | |||
} else if prConfig.AllowManualMerge { | |||
mergeStyle = repo_model.MergeStyleManuallyMerged | |||
} |
@@ -576,6 +576,7 @@ func SettingsPost(ctx *context.Context) { | |||
AllowRebase: form.PullsAllowRebase, | |||
AllowRebaseMerge: form.PullsAllowRebaseMerge, | |||
AllowSquash: form.PullsAllowSquash, | |||
AllowFastForwardOnly: form.PullsAllowFastForwardOnly, | |||
AllowManualMerge: form.PullsAllowManualMerge, | |||
AutodetectManualMerge: form.EnableAutodetectManualMerge, | |||
AllowRebaseUpdate: form.PullsAllowRebaseUpdate, |
@@ -93,6 +93,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR | |||
allowRebase := false | |||
allowRebaseMerge := false | |||
allowSquash := false | |||
allowFastForwardOnly := false | |||
allowRebaseUpdate := false | |||
defaultDeleteBranchAfterMerge := false | |||
defaultMergeStyle := repo_model.MergeStyleMerge | |||
@@ -105,6 +106,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR | |||
allowRebase = config.AllowRebase | |||
allowRebaseMerge = config.AllowRebaseMerge | |||
allowSquash = config.AllowSquash | |||
allowFastForwardOnly = config.AllowFastForwardOnly | |||
allowRebaseUpdate = config.AllowRebaseUpdate | |||
defaultDeleteBranchAfterMerge = config.DefaultDeleteBranchAfterMerge | |||
defaultMergeStyle = config.GetDefaultMergeStyle() | |||
@@ -219,6 +221,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR | |||
AllowRebase: allowRebase, | |||
AllowRebaseMerge: allowRebaseMerge, | |||
AllowSquash: allowSquash, | |||
AllowFastForwardOnly: allowFastForwardOnly, | |||
AllowRebaseUpdate: allowRebaseUpdate, | |||
DefaultDeleteBranchAfterMerge: defaultDeleteBranchAfterMerge, | |||
DefaultMergeStyle: string(defaultMergeStyle), |
@@ -151,6 +151,7 @@ type RepoSettingForm struct { | |||
PullsAllowRebase bool | |||
PullsAllowRebaseMerge bool | |||
PullsAllowSquash bool | |||
PullsAllowFastForwardOnly bool | |||
PullsAllowManualMerge bool | |||
PullsDefaultMergeStyle string | |||
EnableAutodetectManualMerge bool | |||
@@ -598,8 +599,8 @@ func (f *InitializeLabelsForm) Validate(req *http.Request, errs binding.Errors) | |||
// swagger:model MergePullRequestOption | |||
type MergePullRequestForm struct { | |||
// required: true | |||
// enum: merge,rebase,rebase-merge,squash,manually-merged | |||
Do string `binding:"Required;In(merge,rebase,rebase-merge,squash,manually-merged)"` | |||
// enum: merge,rebase,rebase-merge,squash,fast-forward-only,manually-merged | |||
Do string `binding:"Required;In(merge,rebase,rebase-merge,squash,fast-forward-only,manually-merged)"` | |||
MergeTitleField string | |||
MergeMessageField string | |||
MergeCommitID string // only used for manually-merged |
@@ -267,6 +267,10 @@ func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *use | |||
if err := doMergeStyleSquash(mergeCtx, message); err != nil { | |||
return "", err | |||
} | |||
case repo_model.MergeStyleFastForwardOnly: | |||
if err := doMergeStyleFastForwardOnly(mergeCtx); err != nil { | |||
return "", err | |||
} | |||
default: | |||
return "", models.ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: mergeStyle} | |||
} | |||
@@ -377,6 +381,13 @@ func runMergeCommand(ctx *mergeContext, mergeStyle repo_model.MergeStyle, cmd *g | |||
StdErr: ctx.errbuf.String(), | |||
Err: err, | |||
} | |||
} else if mergeStyle == repo_model.MergeStyleFastForwardOnly && strings.Contains(ctx.errbuf.String(), "Not possible to fast-forward, aborting") { | |||
log.Debug("MergeDivergingFastForwardOnly %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) | |||
return models.ErrMergeDivergingFastForwardOnly{ | |||
StdOut: ctx.outbuf.String(), | |||
StdErr: ctx.errbuf.String(), | |||
Err: err, | |||
} | |||
} | |||
log.Error("git merge %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) | |||
return fmt.Errorf("git merge %v: %w\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) |
@@ -0,0 +1,21 @@ | |||
// Copyright 2023 The Gitea Authors. All rights reserved. | |||
// SPDX-License-Identifier: MIT | |||
package pull | |||
import ( | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
"code.gitea.io/gitea/modules/git" | |||
"code.gitea.io/gitea/modules/log" | |||
) | |||
// doMergeStyleFastForwardOnly merges the tracking into the current HEAD - which is assumed to be staging branch (equal to the pr.BaseBranch) | |||
func doMergeStyleFastForwardOnly(ctx *mergeContext) error { | |||
cmd := git.NewCommand(ctx, "merge", "--ff-only").AddDynamicArguments(trackingBranch) | |||
if err := runMergeCommand(ctx, repo_model.MergeStyleFastForwardOnly, cmd); err != nil { | |||
log.Error("%-v Unable to merge tracking into base: %v", ctx.pr, err) | |||
return err | |||
} | |||
return nil | |||
} |
@@ -9,7 +9,7 @@ import ( | |||
"code.gitea.io/gitea/modules/log" | |||
) | |||
// doMergeStyleMerge merges the tracking into the current HEAD - which is assumed to tbe staging branch (equal to the pr.BaseBranch) | |||
// doMergeStyleMerge merges the tracking branch into the current HEAD - which is assumed to be the staging branch (equal to the pr.BaseBranch) | |||
func doMergeStyleMerge(ctx *mergeContext, message string) error { | |||
cmd := git.NewCommand(ctx, "merge", "--no-ff", "--no-commit").AddDynamicArguments(trackingBranch) | |||
if err := runMergeCommand(ctx, repo_model.MergeStyleMerge, cmd); err != nil { |
@@ -197,7 +197,7 @@ | |||
{{if .AllowMerge}} {{/* user is allowed to merge */}} | |||
{{$prUnit := .Repository.MustGetUnit $.Context $.UnitTypePullRequests}} | |||
{{$approvers := (.Issue.PullRequest.GetApprovers ctx)}} | |||
{{if or $prUnit.PullRequestsConfig.AllowMerge $prUnit.PullRequestsConfig.AllowRebase $prUnit.PullRequestsConfig.AllowRebaseMerge $prUnit.PullRequestsConfig.AllowSquash}} | |||
{{if or $prUnit.PullRequestsConfig.AllowMerge $prUnit.PullRequestsConfig.AllowRebase $prUnit.PullRequestsConfig.AllowRebaseMerge $prUnit.PullRequestsConfig.AllowSquash $prUnit.PullRequestsConfig.AllowFastForwardOnly}} | |||
{{$hasPendingPullRequestMergeTip := ""}} | |||
{{if .HasPendingPullRequestMerge}} | |||
{{$createdPRMergeStr := TimeSinceUnix .PendingPullRequestMerge.CreatedUnix ctx.Locale}} | |||
@@ -268,6 +268,13 @@ | |||
'mergeMessageFieldText': {{.GetCommitMessages}} + defaultSquashMergeMessage, | |||
'hideAutoMerge': generalHideAutoMerge, | |||
}, | |||
{ | |||
'name': 'fast-forward-only', | |||
'allowed': {{and $prUnit.PullRequestsConfig.AllowFastForwardOnly (eq .Issue.PullRequest.CommitsBehind 0)}}, | |||
'textDoMerge': {{ctx.Locale.Tr "repo.pulls.fast_forward_only_merge_pull_request"}}, | |||
'hideMergeMessageTexts': true, | |||
'hideAutoMerge': generalHideAutoMerge, | |||
}, | |||
{ | |||
'name': 'manually-merged', | |||
'allowed': {{$prUnit.PullRequestsConfig.AllowManualMerge}}, |
@@ -35,6 +35,10 @@ | |||
<div>git checkout {{.PullRequest.BaseBranch}}</div> | |||
<div>git merge --squash {{$localBranch}}</div> | |||
</div> | |||
<div class="gt-hidden" data-pull-merge-style="fast-forward-only"> | |||
<div>git checkout {{.PullRequest.BaseBranch}}</div> | |||
<div>git merge --ff-only {{$localBranch}}</div> | |||
</div> | |||
<div class="gt-hidden" data-pull-merge-style="manually-merged"> | |||
<div>git checkout {{.PullRequest.BaseBranch}}</div> | |||
<div>git merge {{$localBranch}}</div> |
@@ -528,6 +528,12 @@ | |||
<label>{{ctx.Locale.Tr "repo.pulls.squash_merge_pull_request"}}</label> | |||
</div> | |||
</div> | |||
<div class="field"> | |||
<div class="ui checkbox"> | |||
<input name="pulls_allow_fast_forward_only" type="checkbox" {{if or (not $pullRequestEnabled) ($prUnit.PullRequestsConfig.AllowFastForwardOnly)}}checked{{end}}> | |||
<label>{{ctx.Locale.Tr "repo.pulls.fast_forward_only_merge_pull_request"}}</label> | |||
</div> | |||
</div> | |||
<div class="field"> | |||
<div class="ui checkbox"> | |||
<input name="pulls_allow_manual_merge" type="checkbox" {{if or (not $pullRequestEnabled) ($prUnit.PullRequestsConfig.AllowManualMerge)}}checked{{end}}> | |||
@@ -545,6 +551,7 @@ | |||
<option value="rebase" {{if or (not $pullRequestEnabled) (eq $prUnit.PullRequestsConfig.DefaultMergeStyle "rebase")}}selected{{end}}>{{ctx.Locale.Tr "repo.pulls.rebase_merge_pull_request"}}</option> | |||
<option value="rebase-merge" {{if or (not $pullRequestEnabled) (eq $prUnit.PullRequestsConfig.DefaultMergeStyle "rebase-merge")}}selected{{end}}>{{ctx.Locale.Tr "repo.pulls.rebase_merge_commit_pull_request"}}</option> | |||
<option value="squash" {{if or (not $pullRequestEnabled) (eq $prUnit.PullRequestsConfig.DefaultMergeStyle "squash")}}selected{{end}}>{{ctx.Locale.Tr "repo.pulls.squash_merge_pull_request"}}</option> | |||
<option value="fast-forward-only" {{if or (not $pullRequestEnabled) (eq $prUnit.PullRequestsConfig.DefaultMergeStyle "fast-forward-only")}}selected{{end}}>{{ctx.Locale.Tr "repo.pulls.fast_forward_only_merge_pull_request"}}</option> | |||
</select>{{svg "octicon-triangle-down" 14 "dropdown icon"}} | |||
<div class="default text"> | |||
{{if (eq $prUnit.PullRequestsConfig.DefaultMergeStyle "merge")}} | |||
@@ -559,12 +566,16 @@ | |||
{{if (eq $prUnit.PullRequestsConfig.DefaultMergeStyle "squash")}} | |||
{{ctx.Locale.Tr "repo.pulls.squash_merge_pull_request"}} | |||
{{end}} | |||
{{if (eq $prUnit.PullRequestsConfig.DefaultMergeStyle "fast-forward-only")}} | |||
{{ctx.Locale.Tr "repo.pulls.fast_forward_only_merge_pull_request"}} | |||
{{end}} | |||
</div> | |||
<div class="menu"> | |||
<div class="item" data-value="merge">{{ctx.Locale.Tr "repo.pulls.merge_pull_request"}}</div> | |||
<div class="item" data-value="rebase">{{ctx.Locale.Tr "repo.pulls.rebase_merge_pull_request"}}</div> | |||
<div class="item" data-value="rebase-merge">{{ctx.Locale.Tr "repo.pulls.rebase_merge_commit_pull_request"}}</div> | |||
<div class="item" data-value="squash">{{ctx.Locale.Tr "repo.pulls.squash_merge_pull_request"}}</div> | |||
<div class="item" data-value="fast-forward-only">{{ctx.Locale.Tr "repo.pulls.fast_forward_only_merge_pull_request"}}</div> | |||
</div> | |||
</div> | |||
</div> |
@@ -19195,6 +19195,11 @@ | |||
"description": "EditRepoOption options when editing a repository's properties", | |||
"type": "object", | |||
"properties": { | |||
"allow_fast_forward_only_merge": { | |||
"description": "either `true` to allow fast-forward-only merging pull requests, or `false` to prevent fast-forward-only merging.", | |||
"type": "boolean", | |||
"x-go-name": "AllowFastForwardOnly" | |||
}, | |||
"allow_manual_merge": { | |||
"description": "either `true` to allow mark pr as merged manually, or `false` to prevent it.", | |||
"type": "boolean", | |||
@@ -19251,7 +19256,7 @@ | |||
"x-go-name": "DefaultDeleteBranchAfterMerge" | |||
}, | |||
"default_merge_style": { | |||
"description": "set to a merge style to be used by this repository: \"merge\", \"rebase\", \"rebase-merge\", or \"squash\".", | |||
"description": "set to a merge style to be used by this repository: \"merge\", \"rebase\", \"rebase-merge\", \"squash\", or \"fast-forward-only\".", | |||
"type": "string", | |||
"x-go-name": "DefaultMergeStyle" | |||
}, | |||
@@ -20650,6 +20655,7 @@ | |||
"rebase", | |||
"rebase-merge", | |||
"squash", | |||
"fast-forward-only", | |||
"manually-merged" | |||
] | |||
}, | |||
@@ -22036,6 +22042,10 @@ | |||
"description": "Repository represents a repository", | |||
"type": "object", | |||
"properties": { | |||
"allow_fast_forward_only_merge": { | |||
"type": "boolean", | |||
"x-go-name": "AllowFastForwardOnly" | |||
}, | |||
"allow_merge_commits": { | |||
"type": "boolean", | |||
"x-go-name": "AllowMerge" |
@@ -65,6 +65,7 @@ func getRepoEditOptionFromRepo(repo *repo_model.Repository) *api.EditRepoOption | |||
allowRebase := false | |||
allowRebaseMerge := false | |||
allowSquash := false | |||
allowFastForwardOnly := false | |||
if unit, err := repo.GetUnit(db.DefaultContext, unit_model.TypePullRequests); err == nil { | |||
config := unit.PullRequestsConfig() | |||
hasPullRequests = true | |||
@@ -73,6 +74,7 @@ func getRepoEditOptionFromRepo(repo *repo_model.Repository) *api.EditRepoOption | |||
allowRebase = config.AllowRebase | |||
allowRebaseMerge = config.AllowRebaseMerge | |||
allowSquash = config.AllowSquash | |||
allowFastForwardOnly = config.AllowFastForwardOnly | |||
} | |||
archived := repo.IsArchived | |||
return &api.EditRepoOption{ | |||
@@ -92,6 +94,7 @@ func getRepoEditOptionFromRepo(repo *repo_model.Repository) *api.EditRepoOption | |||
AllowRebase: &allowRebase, | |||
AllowRebaseMerge: &allowRebaseMerge, | |||
AllowSquash: &allowSquash, | |||
AllowFastForwardOnly: &allowFastForwardOnly, | |||
Archived: &archived, | |||
} | |||
} |
@@ -365,6 +365,90 @@ func TestCantMergeUnrelated(t *testing.T) { | |||
}) | |||
} | |||
func TestFastForwardOnlyMerge(t *testing.T) { | |||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { | |||
session := loginUser(t, "user1") | |||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1") | |||
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "update", "README.md", "Hello, World 2\n") | |||
// Use API to create a pr from update to master | |||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) | |||
req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", "user1", "repo1"), &api.CreatePullRequestOption{ | |||
Head: "update", | |||
Base: "master", | |||
Title: "create a pr that can be fast-forward-only merged", | |||
}).AddTokenAuth(token) | |||
session.MakeRequest(t, req, http.StatusCreated) | |||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ | |||
Name: "user1", | |||
}) | |||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ | |||
OwnerID: user1.ID, | |||
Name: "repo1", | |||
}) | |||
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ | |||
HeadRepoID: repo1.ID, | |||
BaseRepoID: repo1.ID, | |||
HeadBranch: "update", | |||
BaseBranch: "master", | |||
}) | |||
gitRepo, err := git.OpenRepository(git.DefaultContext, repo_model.RepoPath(user1.Name, repo1.Name)) | |||
assert.NoError(t, err) | |||
err = pull.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleFastForwardOnly, "", "FAST-FORWARD-ONLY", false) | |||
assert.NoError(t, err) | |||
gitRepo.Close() | |||
}) | |||
} | |||
func TestCantFastForwardOnlyMergeDiverging(t *testing.T) { | |||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { | |||
session := loginUser(t, "user1") | |||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1") | |||
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "diverging", "README.md", "Hello, World diverged\n") | |||
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World 2\n") | |||
// Use API to create a pr from diverging to update | |||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) | |||
req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", "user1", "repo1"), &api.CreatePullRequestOption{ | |||
Head: "diverging", | |||
Base: "master", | |||
Title: "create a pr from a diverging branch", | |||
}).AddTokenAuth(token) | |||
session.MakeRequest(t, req, http.StatusCreated) | |||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ | |||
Name: "user1", | |||
}) | |||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ | |||
OwnerID: user1.ID, | |||
Name: "repo1", | |||
}) | |||
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ | |||
HeadRepoID: repo1.ID, | |||
BaseRepoID: repo1.ID, | |||
HeadBranch: "diverging", | |||
BaseBranch: "master", | |||
}) | |||
gitRepo, err := git.OpenRepository(git.DefaultContext, repo_model.RepoPath(user1.Name, repo1.Name)) | |||
assert.NoError(t, err) | |||
err = pull.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleFastForwardOnly, "", "DIVERGING", false) | |||
assert.Error(t, err, "Merge should return an error due to being for a diverging branch") | |||
assert.True(t, models.IsErrMergeDivergingFastForwardOnly(err), "Merge error is not a diverging fast-forward-only error") | |||
gitRepo.Close() | |||
}) | |||
} | |||
func TestConflictChecking(t *testing.T) { | |||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { | |||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) |