mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	Add merge style fast-forward-only (#28954)
				
					
				
			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 #24906
This commit is contained in:
		| @@ -1044,7 +1044,7 @@ LEVEL = Info | |||||||
| ;; List of keywords used in Pull Request comments to automatically reopen a related issue | ;; List of keywords used in Pull Request comments to automatically reopen a related issue | ||||||
| ;REOPEN_KEYWORDS = reopen,reopens,reopened | ;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 | ;DEFAULT_MERGE_STYLE = merge | ||||||
| ;; | ;; | ||||||
| ;; In the default merge message for squash commits include at most this many commits | ;; 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 |  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 | - `REOPEN_KEYWORDS`: **reopen**, **reopens**, **reopened**: List of keywords used in Pull Request comments to automatically reopen | ||||||
|  a related issue |  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_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_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 | - `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**: 在拉取请求评论中用于自动关闭相关问题的关键词列表。 | - `CLOSE_KEYWORDS`: **close**, **closes**, **closed**, **fix**, **fixes**, **fixed**, **resolve**, **resolves**, **resolved**: 在拉取请求评论中用于自动关闭相关问题的关键词列表。 | ||||||
| - `REOPEN_KEYWORDS`: **reopen**, **reopens**, **reopened**: 在拉取请求评论中用于自动重新打开相关问题的 | - `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_COMMITS_LIMIT`: **50**: 在默认合并消息中,对于`squash`提交,最多包括此数量的提交。设置为 -1 以包括所有提交。 | ||||||
| - `DEFAULT_MERGE_MESSAGE_SIZE`: **5120**: 在默认的合并消息中,对于`squash`提交,限制提交消息的大小。设置为 `-1`以取消限制。仅在`POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES`为`true`时使用。 | - `DEFAULT_MERGE_MESSAGE_SIZE`: **5120**: 在默认的合并消息中,对于`squash`提交,限制提交消息的大小。设置为 `-1`以取消限制。仅在`POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES`为`true`时使用。 | ||||||
| - `DEFAULT_MERGE_MESSAGE_ALL_AUTHORS`: **false**: 在默认合并消息中,对于`squash`提交,遍历所有提交以包括所有作者的`Co-authored-by`,否则仅使用限定列表中的作者。 | - `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) | 	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 | // ErrRebaseConflicts represents an error if rebase fails with a conflict | ||||||
| type ErrRebaseConflicts struct { | type ErrRebaseConflicts struct { | ||||||
| 	Style     repo_model.MergeStyle | 	Style     repo_model.MergeStyle | ||||||
|   | |||||||
| @@ -21,6 +21,8 @@ const ( | |||||||
| 	MergeStyleRebaseMerge MergeStyle = "rebase-merge" | 	MergeStyleRebaseMerge MergeStyle = "rebase-merge" | ||||||
| 	// MergeStyleSquash squash commits into single commit before merging | 	// MergeStyleSquash squash commits into single commit before merging | ||||||
| 	MergeStyleSquash MergeStyle = "squash" | 	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 pr has been merged manually, just mark it as merged directly | ||||||
| 	MergeStyleManuallyMerged MergeStyle = "manually-merged" | 	MergeStyleManuallyMerged MergeStyle = "manually-merged" | ||||||
| 	// MergeStyleRebaseUpdate not a merge style, used to update pull head by rebase | 	// MergeStyleRebaseUpdate not a merge style, used to update pull head by rebase | ||||||
|   | |||||||
| @@ -122,6 +122,7 @@ type PullRequestsConfig struct { | |||||||
| 	AllowRebase                   bool | 	AllowRebase                   bool | ||||||
| 	AllowRebaseMerge              bool | 	AllowRebaseMerge              bool | ||||||
| 	AllowSquash                   bool | 	AllowSquash                   bool | ||||||
|  | 	AllowFastForwardOnly          bool | ||||||
| 	AllowManualMerge              bool | 	AllowManualMerge              bool | ||||||
| 	AutodetectManualMerge         bool | 	AutodetectManualMerge         bool | ||||||
| 	AllowRebaseUpdate             bool | 	AllowRebaseUpdate             bool | ||||||
| @@ -148,6 +149,7 @@ func (cfg *PullRequestsConfig) IsMergeStyleAllowed(mergeStyle MergeStyle) bool { | |||||||
| 		mergeStyle == MergeStyleRebase && cfg.AllowRebase || | 		mergeStyle == MergeStyleRebase && cfg.AllowRebase || | ||||||
| 		mergeStyle == MergeStyleRebaseMerge && cfg.AllowRebaseMerge || | 		mergeStyle == MergeStyleRebaseMerge && cfg.AllowRebaseMerge || | ||||||
| 		mergeStyle == MergeStyleSquash && cfg.AllowSquash || | 		mergeStyle == MergeStyleSquash && cfg.AllowSquash || | ||||||
|  | 		mergeStyle == MergeStyleFastForwardOnly && cfg.AllowFastForwardOnly || | ||||||
| 		mergeStyle == MergeStyleManuallyMerged && cfg.AllowManualMerge | 		mergeStyle == MergeStyleManuallyMerged && cfg.AllowManualMerge | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -96,7 +96,7 @@ func (err ErrBranchNotExist) Unwrap() error { | |||||||
| 	return util.ErrNotExist | 	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 { | type ErrPushOutOfDate struct { | ||||||
| 	StdOut string | 	StdOut string | ||||||
| 	StdErr string | 	StdErr string | ||||||
|   | |||||||
| @@ -87,7 +87,11 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re | |||||||
| 			units = append(units, repo_model.RepoUnit{ | 			units = append(units, repo_model.RepoUnit{ | ||||||
| 				RepoID: repo.ID, | 				RepoID: repo.ID, | ||||||
| 				Type:   tp, | 				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 { | 		} else { | ||||||
| 			units = append(units, repo_model.RepoUnit{ | 			units = append(units, repo_model.RepoUnit{ | ||||||
|   | |||||||
| @@ -98,6 +98,7 @@ type Repository struct { | |||||||
| 	AllowRebase                   bool             `json:"allow_rebase"` | 	AllowRebase                   bool             `json:"allow_rebase"` | ||||||
| 	AllowRebaseMerge              bool             `json:"allow_rebase_explicit"` | 	AllowRebaseMerge              bool             `json:"allow_rebase_explicit"` | ||||||
| 	AllowSquash                   bool             `json:"allow_squash_merge"` | 	AllowSquash                   bool             `json:"allow_squash_merge"` | ||||||
|  | 	AllowFastForwardOnly          bool             `json:"allow_fast_forward_only_merge"` | ||||||
| 	AllowRebaseUpdate             bool             `json:"allow_rebase_update"` | 	AllowRebaseUpdate             bool             `json:"allow_rebase_update"` | ||||||
| 	DefaultDeleteBranchAfterMerge bool             `json:"default_delete_branch_after_merge"` | 	DefaultDeleteBranchAfterMerge bool             `json:"default_delete_branch_after_merge"` | ||||||
| 	DefaultMergeStyle             string           `json:"default_merge_style"` | 	DefaultMergeStyle             string           `json:"default_merge_style"` | ||||||
| @@ -195,6 +196,8 @@ type EditRepoOption struct { | |||||||
| 	AllowRebaseMerge *bool `json:"allow_rebase_explicit,omitempty"` | 	AllowRebaseMerge *bool `json:"allow_rebase_explicit,omitempty"` | ||||||
| 	// either `true` to allow squash-merging pull requests, or `false` to prevent squash-merging. | 	// either `true` to allow squash-merging pull requests, or `false` to prevent squash-merging. | ||||||
| 	AllowSquash *bool `json:"allow_squash_merge,omitempty"` | 	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. | 	// either `true` to allow mark pr as merged manually, or `false` to prevent it. | ||||||
| 	AllowManualMerge *bool `json:"allow_manual_merge,omitempty"` | 	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. | 	// 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"` | 	AllowRebaseUpdate *bool `json:"allow_rebase_update,omitempty"` | ||||||
| 	// set to `true` to delete pr branch after merge by default | 	// set to `true` to delete pr branch after merge by default | ||||||
| 	DefaultDeleteBranchAfterMerge *bool `json:"default_delete_branch_after_merge,omitempty"` | 	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"` | 	DefaultMergeStyle *string `json:"default_merge_style,omitempty"` | ||||||
| 	// set to `true` to allow edits from maintainers by default | 	// set to `true` to allow edits from maintainers by default | ||||||
| 	DefaultAllowMaintainerEdit *bool `json:"default_allow_maintainer_edit,omitempty"` | 	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_pull_request = Rebase then fast-forward | ||||||
| pulls.rebase_merge_commit_pull_request = Rebase then create merge commit | pulls.rebase_merge_commit_pull_request = Rebase then create merge commit | ||||||
| pulls.squash_merge_pull_request = Create squash 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_manually = Manually merged | ||||||
| pulls.merge_commit_id = The merge commit ID | 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 | 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, | 					AllowRebase:                   true, | ||||||
| 					AllowRebaseMerge:              true, | 					AllowRebaseMerge:              true, | ||||||
| 					AllowSquash:                   true, | 					AllowSquash:                   true, | ||||||
|  | 					AllowFastForwardOnly:          true, | ||||||
| 					AllowManualMerge:              true, | 					AllowManualMerge:              true, | ||||||
| 					AutodetectManualMerge:         false, | 					AutodetectManualMerge:         false, | ||||||
| 					AllowRebaseUpdate:             true, | 					AllowRebaseUpdate:             true, | ||||||
| @@ -911,6 +912,9 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { | |||||||
| 			if opts.AllowSquash != nil { | 			if opts.AllowSquash != nil { | ||||||
| 				config.AllowSquash = *opts.AllowSquash | 				config.AllowSquash = *opts.AllowSquash | ||||||
| 			} | 			} | ||||||
|  | 			if opts.AllowFastForwardOnly != nil { | ||||||
|  | 				config.AllowFastForwardOnly = *opts.AllowFastForwardOnly | ||||||
|  | 			} | ||||||
| 			if opts.AllowManualMerge != nil { | 			if opts.AllowManualMerge != nil { | ||||||
| 				config.AllowManualMerge = *opts.AllowManualMerge | 				config.AllowManualMerge = *opts.AllowManualMerge | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -35,6 +35,7 @@ func TestRepoEdit(t *testing.T) { | |||||||
| 	allowRebase := false | 	allowRebase := false | ||||||
| 	allowRebaseMerge := false | 	allowRebaseMerge := false | ||||||
| 	allowSquashMerge := false | 	allowSquashMerge := false | ||||||
|  | 	allowFastForwardOnlyMerge := false | ||||||
| 	archived := true | 	archived := true | ||||||
| 	opts := api.EditRepoOption{ | 	opts := api.EditRepoOption{ | ||||||
| 		Name:                      &ctx.Repo.Repository.Name, | 		Name:                      &ctx.Repo.Repository.Name, | ||||||
| @@ -50,6 +51,7 @@ func TestRepoEdit(t *testing.T) { | |||||||
| 		AllowRebase:               &allowRebase, | 		AllowRebase:               &allowRebase, | ||||||
| 		AllowRebaseMerge:          &allowRebaseMerge, | 		AllowRebaseMerge:          &allowRebaseMerge, | ||||||
| 		AllowSquash:               &allowSquashMerge, | 		AllowSquash:               &allowSquashMerge, | ||||||
|  | 		AllowFastForwardOnly:      &allowFastForwardOnlyMerge, | ||||||
| 		Archived:                  &archived, | 		Archived:                  &archived, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1862,6 +1862,8 @@ func ViewIssue(ctx *context.Context) { | |||||||
| 				mergeStyle = repo_model.MergeStyleRebaseMerge | 				mergeStyle = repo_model.MergeStyleRebaseMerge | ||||||
| 			} else if prConfig.AllowSquash { | 			} else if prConfig.AllowSquash { | ||||||
| 				mergeStyle = repo_model.MergeStyleSquash | 				mergeStyle = repo_model.MergeStyleSquash | ||||||
|  | 			} else if prConfig.AllowFastForwardOnly { | ||||||
|  | 				mergeStyle = repo_model.MergeStyleFastForwardOnly | ||||||
| 			} else if prConfig.AllowManualMerge { | 			} else if prConfig.AllowManualMerge { | ||||||
| 				mergeStyle = repo_model.MergeStyleManuallyMerged | 				mergeStyle = repo_model.MergeStyleManuallyMerged | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -576,6 +576,7 @@ func SettingsPost(ctx *context.Context) { | |||||||
| 					AllowRebase:                   form.PullsAllowRebase, | 					AllowRebase:                   form.PullsAllowRebase, | ||||||
| 					AllowRebaseMerge:              form.PullsAllowRebaseMerge, | 					AllowRebaseMerge:              form.PullsAllowRebaseMerge, | ||||||
| 					AllowSquash:                   form.PullsAllowSquash, | 					AllowSquash:                   form.PullsAllowSquash, | ||||||
|  | 					AllowFastForwardOnly:          form.PullsAllowFastForwardOnly, | ||||||
| 					AllowManualMerge:              form.PullsAllowManualMerge, | 					AllowManualMerge:              form.PullsAllowManualMerge, | ||||||
| 					AutodetectManualMerge:         form.EnableAutodetectManualMerge, | 					AutodetectManualMerge:         form.EnableAutodetectManualMerge, | ||||||
| 					AllowRebaseUpdate:             form.PullsAllowRebaseUpdate, | 					AllowRebaseUpdate:             form.PullsAllowRebaseUpdate, | ||||||
|   | |||||||
| @@ -93,6 +93,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR | |||||||
| 	allowRebase := false | 	allowRebase := false | ||||||
| 	allowRebaseMerge := false | 	allowRebaseMerge := false | ||||||
| 	allowSquash := false | 	allowSquash := false | ||||||
|  | 	allowFastForwardOnly := false | ||||||
| 	allowRebaseUpdate := false | 	allowRebaseUpdate := false | ||||||
| 	defaultDeleteBranchAfterMerge := false | 	defaultDeleteBranchAfterMerge := false | ||||||
| 	defaultMergeStyle := repo_model.MergeStyleMerge | 	defaultMergeStyle := repo_model.MergeStyleMerge | ||||||
| @@ -105,6 +106,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR | |||||||
| 		allowRebase = config.AllowRebase | 		allowRebase = config.AllowRebase | ||||||
| 		allowRebaseMerge = config.AllowRebaseMerge | 		allowRebaseMerge = config.AllowRebaseMerge | ||||||
| 		allowSquash = config.AllowSquash | 		allowSquash = config.AllowSquash | ||||||
|  | 		allowFastForwardOnly = config.AllowFastForwardOnly | ||||||
| 		allowRebaseUpdate = config.AllowRebaseUpdate | 		allowRebaseUpdate = config.AllowRebaseUpdate | ||||||
| 		defaultDeleteBranchAfterMerge = config.DefaultDeleteBranchAfterMerge | 		defaultDeleteBranchAfterMerge = config.DefaultDeleteBranchAfterMerge | ||||||
| 		defaultMergeStyle = config.GetDefaultMergeStyle() | 		defaultMergeStyle = config.GetDefaultMergeStyle() | ||||||
| @@ -219,6 +221,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR | |||||||
| 		AllowRebase:                   allowRebase, | 		AllowRebase:                   allowRebase, | ||||||
| 		AllowRebaseMerge:              allowRebaseMerge, | 		AllowRebaseMerge:              allowRebaseMerge, | ||||||
| 		AllowSquash:                   allowSquash, | 		AllowSquash:                   allowSquash, | ||||||
|  | 		AllowFastForwardOnly:          allowFastForwardOnly, | ||||||
| 		AllowRebaseUpdate:             allowRebaseUpdate, | 		AllowRebaseUpdate:             allowRebaseUpdate, | ||||||
| 		DefaultDeleteBranchAfterMerge: defaultDeleteBranchAfterMerge, | 		DefaultDeleteBranchAfterMerge: defaultDeleteBranchAfterMerge, | ||||||
| 		DefaultMergeStyle:             string(defaultMergeStyle), | 		DefaultMergeStyle:             string(defaultMergeStyle), | ||||||
|   | |||||||
| @@ -151,6 +151,7 @@ type RepoSettingForm struct { | |||||||
| 	PullsAllowRebase                      bool | 	PullsAllowRebase                      bool | ||||||
| 	PullsAllowRebaseMerge                 bool | 	PullsAllowRebaseMerge                 bool | ||||||
| 	PullsAllowSquash                      bool | 	PullsAllowSquash                      bool | ||||||
|  | 	PullsAllowFastForwardOnly             bool | ||||||
| 	PullsAllowManualMerge                 bool | 	PullsAllowManualMerge                 bool | ||||||
| 	PullsDefaultMergeStyle                string | 	PullsDefaultMergeStyle                string | ||||||
| 	EnableAutodetectManualMerge           bool | 	EnableAutodetectManualMerge           bool | ||||||
| @@ -598,8 +599,8 @@ func (f *InitializeLabelsForm) Validate(req *http.Request, errs binding.Errors) | |||||||
| // swagger:model MergePullRequestOption | // swagger:model MergePullRequestOption | ||||||
| type MergePullRequestForm struct { | type MergePullRequestForm struct { | ||||||
| 	// required: true | 	// required: true | ||||||
| 	// enum: 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,manually-merged)"` | 	Do                     string `binding:"Required;In(merge,rebase,rebase-merge,squash,fast-forward-only,manually-merged)"` | ||||||
| 	MergeTitleField        string | 	MergeTitleField        string | ||||||
| 	MergeMessageField      string | 	MergeMessageField      string | ||||||
| 	MergeCommitID          string // only used for manually-merged | 	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 { | 		if err := doMergeStyleSquash(mergeCtx, message); err != nil { | ||||||
| 			return "", err | 			return "", err | ||||||
| 		} | 		} | ||||||
|  | 	case repo_model.MergeStyleFastForwardOnly: | ||||||
|  | 		if err := doMergeStyleFastForwardOnly(mergeCtx); err != nil { | ||||||
|  | 			return "", err | ||||||
|  | 		} | ||||||
| 	default: | 	default: | ||||||
| 		return "", models.ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: mergeStyle} | 		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(), | 				StdErr: ctx.errbuf.String(), | ||||||
| 				Err:    err, | 				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()) | 		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()) | 		return fmt.Errorf("git merge %v: %w\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) | ||||||
|   | |||||||
							
								
								
									
										21
									
								
								services/pull/merge_ff_only.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								services/pull/merge_ff_only.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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" | 	"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 { | func doMergeStyleMerge(ctx *mergeContext, message string) error { | ||||||
| 	cmd := git.NewCommand(ctx, "merge", "--no-ff", "--no-commit").AddDynamicArguments(trackingBranch) | 	cmd := git.NewCommand(ctx, "merge", "--no-ff", "--no-commit").AddDynamicArguments(trackingBranch) | ||||||
| 	if err := runMergeCommand(ctx, repo_model.MergeStyleMerge, cmd); err != nil { | 	if err := runMergeCommand(ctx, repo_model.MergeStyleMerge, cmd); err != nil { | ||||||
|   | |||||||
| @@ -197,7 +197,7 @@ | |||||||
| 				{{if .AllowMerge}} {{/* user is allowed to merge */}} | 				{{if .AllowMerge}} {{/* user is allowed to merge */}} | ||||||
| 					{{$prUnit := .Repository.MustGetUnit $.Context $.UnitTypePullRequests}} | 					{{$prUnit := .Repository.MustGetUnit $.Context $.UnitTypePullRequests}} | ||||||
| 					{{$approvers := (.Issue.PullRequest.GetApprovers ctx)}} | 					{{$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 := ""}} | 						{{$hasPendingPullRequestMergeTip := ""}} | ||||||
| 						{{if .HasPendingPullRequestMerge}} | 						{{if .HasPendingPullRequestMerge}} | ||||||
| 							{{$createdPRMergeStr := TimeSinceUnix .PendingPullRequestMerge.CreatedUnix ctx.Locale}} | 							{{$createdPRMergeStr := TimeSinceUnix .PendingPullRequestMerge.CreatedUnix ctx.Locale}} | ||||||
| @@ -268,6 +268,13 @@ | |||||||
| 									'mergeMessageFieldText': {{.GetCommitMessages}} + defaultSquashMergeMessage, | 									'mergeMessageFieldText': {{.GetCommitMessages}} + defaultSquashMergeMessage, | ||||||
| 									'hideAutoMerge': generalHideAutoMerge, | 									'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', | 									'name': 'manually-merged', | ||||||
| 									'allowed': {{$prUnit.PullRequestsConfig.AllowManualMerge}}, | 									'allowed': {{$prUnit.PullRequestsConfig.AllowManualMerge}}, | ||||||
|   | |||||||
| @@ -35,6 +35,10 @@ | |||||||
| 			<div>git checkout {{.PullRequest.BaseBranch}}</div> | 			<div>git checkout {{.PullRequest.BaseBranch}}</div> | ||||||
| 			<div>git merge --squash {{$localBranch}}</div> | 			<div>git merge --squash {{$localBranch}}</div> | ||||||
| 		</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 class="gt-hidden" data-pull-merge-style="manually-merged"> | ||||||
| 			<div>git checkout {{.PullRequest.BaseBranch}}</div> | 			<div>git checkout {{.PullRequest.BaseBranch}}</div> | ||||||
| 			<div>git merge {{$localBranch}}</div> | 			<div>git merge {{$localBranch}}</div> | ||||||
|   | |||||||
| @@ -528,6 +528,12 @@ | |||||||
| 								<label>{{ctx.Locale.Tr "repo.pulls.squash_merge_pull_request"}}</label> | 								<label>{{ctx.Locale.Tr "repo.pulls.squash_merge_pull_request"}}</label> | ||||||
| 							</div> | 							</div> | ||||||
| 						</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="field"> | ||||||
| 							<div class="ui checkbox"> | 							<div class="ui checkbox"> | ||||||
| 								<input name="pulls_allow_manual_merge" type="checkbox" {{if or (not $pullRequestEnabled) ($prUnit.PullRequestsConfig.AllowManualMerge)}}checked{{end}}> | 								<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" {{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="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="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"}} | 								</select>{{svg "octicon-triangle-down" 14 "dropdown icon"}} | ||||||
| 								<div class="default text"> | 								<div class="default text"> | ||||||
| 									{{if (eq $prUnit.PullRequestsConfig.DefaultMergeStyle "merge")}} | 									{{if (eq $prUnit.PullRequestsConfig.DefaultMergeStyle "merge")}} | ||||||
| @@ -559,12 +566,16 @@ | |||||||
| 									{{if (eq $prUnit.PullRequestsConfig.DefaultMergeStyle "squash")}} | 									{{if (eq $prUnit.PullRequestsConfig.DefaultMergeStyle "squash")}} | ||||||
| 										{{ctx.Locale.Tr "repo.pulls.squash_merge_pull_request"}} | 										{{ctx.Locale.Tr "repo.pulls.squash_merge_pull_request"}} | ||||||
| 									{{end}} | 									{{end}} | ||||||
|  | 									{{if (eq $prUnit.PullRequestsConfig.DefaultMergeStyle "fast-forward-only")}} | ||||||
|  | 										{{ctx.Locale.Tr "repo.pulls.fast_forward_only_merge_pull_request"}} | ||||||
|  | 									{{end}} | ||||||
| 								</div> | 								</div> | ||||||
| 								<div class="menu"> | 								<div class="menu"> | ||||||
| 									<div class="item" data-value="merge">{{ctx.Locale.Tr "repo.pulls.merge_pull_request"}}</div> | 									<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">{{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="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="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> | 							</div> | ||||||
| 						</div> | 						</div> | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							| @@ -19195,6 +19195,11 @@ | |||||||
|       "description": "EditRepoOption options when editing a repository's properties", |       "description": "EditRepoOption options when editing a repository's properties", | ||||||
|       "type": "object", |       "type": "object", | ||||||
|       "properties": { |       "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": { |         "allow_manual_merge": { | ||||||
|           "description": "either `true` to allow mark pr as merged manually, or `false` to prevent it.", |           "description": "either `true` to allow mark pr as merged manually, or `false` to prevent it.", | ||||||
|           "type": "boolean", |           "type": "boolean", | ||||||
| @@ -19251,7 +19256,7 @@ | |||||||
|           "x-go-name": "DefaultDeleteBranchAfterMerge" |           "x-go-name": "DefaultDeleteBranchAfterMerge" | ||||||
|         }, |         }, | ||||||
|         "default_merge_style": { |         "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", |           "type": "string", | ||||||
|           "x-go-name": "DefaultMergeStyle" |           "x-go-name": "DefaultMergeStyle" | ||||||
|         }, |         }, | ||||||
| @@ -20650,6 +20655,7 @@ | |||||||
|             "rebase", |             "rebase", | ||||||
|             "rebase-merge", |             "rebase-merge", | ||||||
|             "squash", |             "squash", | ||||||
|  |             "fast-forward-only", | ||||||
|             "manually-merged" |             "manually-merged" | ||||||
|           ] |           ] | ||||||
|         }, |         }, | ||||||
| @@ -22036,6 +22042,10 @@ | |||||||
|       "description": "Repository represents a repository", |       "description": "Repository represents a repository", | ||||||
|       "type": "object", |       "type": "object", | ||||||
|       "properties": { |       "properties": { | ||||||
|  |         "allow_fast_forward_only_merge": { | ||||||
|  |           "type": "boolean", | ||||||
|  |           "x-go-name": "AllowFastForwardOnly" | ||||||
|  |         }, | ||||||
|         "allow_merge_commits": { |         "allow_merge_commits": { | ||||||
|           "type": "boolean", |           "type": "boolean", | ||||||
|           "x-go-name": "AllowMerge" |           "x-go-name": "AllowMerge" | ||||||
|   | |||||||
| @@ -65,6 +65,7 @@ func getRepoEditOptionFromRepo(repo *repo_model.Repository) *api.EditRepoOption | |||||||
| 	allowRebase := false | 	allowRebase := false | ||||||
| 	allowRebaseMerge := false | 	allowRebaseMerge := false | ||||||
| 	allowSquash := false | 	allowSquash := false | ||||||
|  | 	allowFastForwardOnly := false | ||||||
| 	if unit, err := repo.GetUnit(db.DefaultContext, unit_model.TypePullRequests); err == nil { | 	if unit, err := repo.GetUnit(db.DefaultContext, unit_model.TypePullRequests); err == nil { | ||||||
| 		config := unit.PullRequestsConfig() | 		config := unit.PullRequestsConfig() | ||||||
| 		hasPullRequests = true | 		hasPullRequests = true | ||||||
| @@ -73,6 +74,7 @@ func getRepoEditOptionFromRepo(repo *repo_model.Repository) *api.EditRepoOption | |||||||
| 		allowRebase = config.AllowRebase | 		allowRebase = config.AllowRebase | ||||||
| 		allowRebaseMerge = config.AllowRebaseMerge | 		allowRebaseMerge = config.AllowRebaseMerge | ||||||
| 		allowSquash = config.AllowSquash | 		allowSquash = config.AllowSquash | ||||||
|  | 		allowFastForwardOnly = config.AllowFastForwardOnly | ||||||
| 	} | 	} | ||||||
| 	archived := repo.IsArchived | 	archived := repo.IsArchived | ||||||
| 	return &api.EditRepoOption{ | 	return &api.EditRepoOption{ | ||||||
| @@ -92,6 +94,7 @@ func getRepoEditOptionFromRepo(repo *repo_model.Repository) *api.EditRepoOption | |||||||
| 		AllowRebase:               &allowRebase, | 		AllowRebase:               &allowRebase, | ||||||
| 		AllowRebaseMerge:          &allowRebaseMerge, | 		AllowRebaseMerge:          &allowRebaseMerge, | ||||||
| 		AllowSquash:               &allowSquash, | 		AllowSquash:               &allowSquash, | ||||||
|  | 		AllowFastForwardOnly:      &allowFastForwardOnly, | ||||||
| 		Archived:                  &archived, | 		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) { | func TestConflictChecking(t *testing.T) { | ||||||
| 	onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { | 	onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { | ||||||
| 		user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) | 		user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Chris Copeland
					Chris Copeland