Skip to content

Commit f5bc44e

Browse files
committed
Add ff_only parameter to POST /repos/{owner}/{repo}/merge-upstream
The merge-upstream route was so far performing any kind of merge, even those that would create merge commits and thus make your branch diverge from upstream, requiring manual intervention via the git cli to undo the damage. With the new optional parameter ff_only, we can instruct gitea to error out, if a non-fast-forward merge would be performed.
1 parent 416ff1f commit f5bc44e

File tree

5 files changed

+56
-3
lines changed

5 files changed

+56
-3
lines changed

modules/structs/repo_branch.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ type UpdateBranchProtectionPriories struct {
136136

137137
type MergeUpstreamRequest struct {
138138
Branch string `json:"branch"`
139+
FfOnly *bool `json:"ff_only,omitempty"`
139140
}
140141

141142
type MergeUpstreamResponse struct {

routers/api/v1/repo/branch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1181,7 +1181,7 @@ func MergeUpstream(ctx *context.APIContext) {
11811181
// "404":
11821182
// "$ref": "#/responses/notFound"
11831183
form := web.GetForm(ctx).(*api.MergeUpstreamRequest)
1184-
mergeStyle, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, form.Branch)
1184+
mergeStyle, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, form.Branch, form.FfOnly)
11851185
if err != nil {
11861186
if errors.Is(err, util.ErrInvalidArgument) {
11871187
ctx.APIError(http.StatusBadRequest, err)

routers/web/repo/branch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ func CreateBranch(ctx *context.Context) {
258258

259259
func MergeUpstream(ctx *context.Context) {
260260
branchName := ctx.FormString("branch")
261-
_, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, branchName)
261+
_, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, branchName, nil)
262262
if err != nil {
263263
if errors.Is(err, util.ErrNotExist) {
264264
ctx.JSONErrorNotFound()

services/repository/merge_upstream.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
)
1919

2020
// MergeUpstream merges the base repository's default branch into the fork repository's current branch.
21-
func MergeUpstream(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_model.Repository, branch string) (mergeStyle string, err error) {
21+
func MergeUpstream(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_model.Repository, branch string, ffOnly *bool) (mergeStyle string, err error) {
2222
if err = repo.MustNotBeArchived(); err != nil {
2323
return "", err
2424
}
@@ -45,6 +45,11 @@ func MergeUpstream(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_
4545
return "", err
4646
}
4747

48+
// If ff_only is requested and fast-forward failed, return error
49+
if ffOnly != nil && *ffOnly {
50+
return "", util.NewInvalidArgumentErrorf("fast-forward merge not possible: branch has diverged")
51+
}
52+
4853
// TODO: FakePR: it is somewhat hacky, but it is the only way to "merge" at the moment
4954
// ideally in the future the "merge" functions should be refactored to decouple from the PullRequest
5055
fakeIssue := &issue_model.Issue{

tests/integration/repo_merge_upstream_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,5 +147,52 @@ func TestRepoMergeUpstream(t *testing.T) {
147147
return queryMergeUpstreamButtonLink(htmlDoc) == ""
148148
}, 5*time.Second, 100*time.Millisecond)
149149
})
150+
151+
t.Run("FastForwardOnly", func(t *testing.T) {
152+
// ff_only=true with fast-forward possible (should succeed)
153+
require.NoError(t, createOrReplaceFileInBranch(baseUser, baseRepo, "ff-test.txt", "master", "ff-content-1"))
154+
155+
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/test-repo-fork/merge-upstream", forkUser.Name), &api.MergeUpstreamRequest{
156+
Branch: "fork-branch",
157+
FfOnly: util.ToPointer(true),
158+
}).AddTokenAuth(token)
159+
resp := MakeRequest(t, req, http.StatusOK)
160+
161+
var mergeResp api.MergeUpstreamResponse
162+
DecodeJSON(t, resp, &mergeResp)
163+
assert.Equal(t, "fast-forward", mergeResp.MergeStyle)
164+
165+
// make a conflicting change in fork, then try ff_only=true (should fail)
166+
require.NoError(t, createOrReplaceFileInBranch(forkUser, forkRepo, "conflict-file.txt", "fork-branch", "fork-content"))
167+
require.NoError(t, createOrReplaceFileInBranch(baseUser, baseRepo, "ff-test.txt", "master", "ff-content-2"))
168+
169+
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/test-repo-fork/merge-upstream", forkUser.Name), &api.MergeUpstreamRequest{
170+
Branch: "fork-branch",
171+
FfOnly: util.ToPointer(true),
172+
}).AddTokenAuth(token)
173+
MakeRequest(t, req, http.StatusBadRequest)
174+
175+
// same scenario but with ff_only=false (should succeed with merge)
176+
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/test-repo-fork/merge-upstream", forkUser.Name), &api.MergeUpstreamRequest{
177+
Branch: "fork-branch",
178+
FfOnly: util.ToPointer(false),
179+
}).AddTokenAuth(token)
180+
resp = MakeRequest(t, req, http.StatusOK)
181+
182+
DecodeJSON(t, resp, &mergeResp)
183+
assert.Equal(t, "merge", mergeResp.MergeStyle)
184+
185+
// ff_only not specified (should use default behavior - merge)
186+
require.NoError(t, createOrReplaceFileInBranch(forkUser, forkRepo, "another-conflict.txt", "fork-branch", "another-fork-content"))
187+
require.NoError(t, createOrReplaceFileInBranch(baseUser, baseRepo, "ff-test.txt", "master", "ff-content-3"))
188+
189+
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/test-repo-fork/merge-upstream", forkUser.Name), &api.MergeUpstreamRequest{
190+
Branch: "fork-branch",
191+
}).AddTokenAuth(token)
192+
resp = MakeRequest(t, req, http.StatusOK)
193+
194+
DecodeJSON(t, resp, &mergeResp)
195+
assert.Equal(t, "merge", mergeResp.MergeStyle)
196+
})
150197
})
151198
}

0 commit comments

Comments
 (0)