Skip to content

Commit ed7b4ed

Browse files
committed
fix
1 parent 2a02734 commit ed7b4ed

File tree

6 files changed

+235
-0
lines changed

6 files changed

+235
-0
lines changed

modules/structs/repo_branch.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,11 @@ type EditBranchProtectionOption struct {
133133
type UpdateBranchProtectionPriories struct {
134134
IDs []int64 `json:"ids"`
135135
}
136+
137+
type MergeUpstreamRequest struct {
138+
Branch string `json:"branch"`
139+
}
140+
141+
type MergeUpstreamResponse struct {
142+
MergeStyle string `json:"merge_type"`
143+
}

routers/api/v1/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,6 +1190,7 @@ func Routes() *web.Router {
11901190
m.Get("/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive)
11911191
m.Combo("/forks").Get(repo.ListForks).
11921192
Post(reqToken(), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
1193+
m.Post("/merge-upstream", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeCode), bind(api.MergeUpstreamRequest{}), repo.MergeUpstream)
11931194
m.Group("/branches", func() {
11941195
m.Get("", repo.ListBranches)
11951196
m.Get("/*", repo.GetBranch)

routers/api/v1/repo/branch.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"code.gitea.io/gitea/modules/optional"
1919
repo_module "code.gitea.io/gitea/modules/repository"
2020
api "code.gitea.io/gitea/modules/structs"
21+
"code.gitea.io/gitea/modules/util"
2122
"code.gitea.io/gitea/modules/web"
2223
"code.gitea.io/gitea/routers/api/v1/utils"
2324
"code.gitea.io/gitea/services/context"
@@ -1186,3 +1187,47 @@ func UpdateBranchProtectionPriories(ctx *context.APIContext) {
11861187

11871188
ctx.Status(http.StatusNoContent)
11881189
}
1190+
1191+
func MergeUpstream(ctx *context.APIContext) {
1192+
// swagger:operation POST /repos/{owner}/{repo}/merge-upstream repository repoMergeUpstream
1193+
// ---
1194+
// summary: Merge a branch from upstream
1195+
// produces:
1196+
// - application/json
1197+
// parameters:
1198+
// - name: owner
1199+
// in: path
1200+
// description: owner of the repo
1201+
// type: string
1202+
// required: true
1203+
// - name: repo
1204+
// in: path
1205+
// description: name of the repo
1206+
// type: string
1207+
// required: true
1208+
// - name: body
1209+
// in: body
1210+
// schema:
1211+
// "$ref": "#/definitions/MergeUpstreamRequest"
1212+
// responses:
1213+
// "200":
1214+
// "$ref": "#/responses/MergeUpstreamResponse"
1215+
// "400":
1216+
// "$ref": "#/responses/error"
1217+
// "404":
1218+
// "$ref": "#/responses/notFound"
1219+
form := web.GetForm(ctx).(*api.MergeUpstreamRequest)
1220+
mergeStyle, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, form.Branch)
1221+
if err != nil {
1222+
if errors.Is(err, util.ErrInvalidArgument) {
1223+
ctx.Error(http.StatusBadRequest, "MergeUpstream", err)
1224+
return
1225+
} else if errors.Is(err, util.ErrNotExist) {
1226+
ctx.Error(http.StatusNotFound, "MergeUpstream", err)
1227+
return
1228+
}
1229+
ctx.Error(http.StatusInternalServerError, "MergeUpstream", err)
1230+
return
1231+
}
1232+
ctx.JSON(http.StatusOK, &api.MergeUpstreamResponse{MergeStyle: mergeStyle})
1233+
}

routers/api/v1/swagger/repo.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,3 +448,15 @@ type swaggerCompare struct {
448448
// in:body
449449
Body api.Compare `json:"body"`
450450
}
451+
452+
// swagger:response MergeUpstreamRequest
453+
type swaggerMergeUpstreamRequest struct {
454+
// in:body
455+
Body api.MergeUpstreamRequest `json:"body"`
456+
}
457+
458+
// swagger:response MergeUpstreamResponse
459+
type swaggerMergeUpstreamResponse struct {
460+
// in:body
461+
Body api.MergeUpstreamResponse `json:"body"`
462+
}

templates/swagger/v1_json.tmpl

Lines changed: 78 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package integration
5+
6+
import (
7+
"fmt"
8+
"net/http"
9+
"net/url"
10+
"strings"
11+
"testing"
12+
"time"
13+
14+
auth_model "code.gitea.io/gitea/models/auth"
15+
repo_model "code.gitea.io/gitea/models/repo"
16+
"code.gitea.io/gitea/models/unittest"
17+
user_model "code.gitea.io/gitea/models/user"
18+
api "code.gitea.io/gitea/modules/structs"
19+
"code.gitea.io/gitea/modules/util"
20+
21+
"github.com/stretchr/testify/assert"
22+
"github.com/stretchr/testify/require"
23+
)
24+
25+
func TestRepoMergeUpstream(t *testing.T) {
26+
onGiteaRun(t, func(*testing.T, *url.URL) {
27+
forkUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
28+
29+
baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
30+
baseUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: baseRepo.OwnerID})
31+
32+
checkFileContent := func(exp string) {
33+
req := NewRequest(t, "GET", fmt.Sprintf("/%s/test-repo-fork/raw/branch/master/new-file.txt", forkUser.Name))
34+
resp := MakeRequest(t, req, http.StatusOK)
35+
require.Equal(t, exp, resp.Body.String())
36+
}
37+
38+
session := loginUser(t, forkUser.Name)
39+
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
40+
41+
// create a fork
42+
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/forks", baseUser.Name, baseRepo.Name), &api.CreateForkOption{
43+
Name: util.ToPointer("test-repo-fork"),
44+
}).AddTokenAuth(token)
45+
MakeRequest(t, req, http.StatusAccepted)
46+
forkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: forkUser.ID, Name: "test-repo-fork"})
47+
48+
// add a file in base repo
49+
require.NoError(t, createOrReplaceFileInBranch(baseUser, baseRepo, "new-file.txt", "master", "test-content-1"))
50+
51+
// the repo shows a prompt to "sync fork"
52+
var mergeUpstreamLink string
53+
require.Eventually(t, func() bool {
54+
resp := session.MakeRequest(t, NewRequestf(t, "GET", "/%s/test-repo-fork", forkUser.Name), http.StatusOK)
55+
htmlDoc := NewHTMLParser(t, resp.Body)
56+
respMsg, _ := htmlDoc.Find(".ui.message").Html()
57+
if !strings.Contains(respMsg, `This branch is 1 commit behind <a href="/user2/repo1/src/branch/master">user2/repo1:master</a>`) {
58+
return false
59+
}
60+
mergeUpstreamLink = htmlDoc.Find("button[data-url*='merge-upstream']").AttrOr("data-url", "")
61+
require.NotEmpty(t, mergeUpstreamLink)
62+
return true
63+
}, 5*time.Second, 100*time.Millisecond)
64+
65+
// click the "sync fork" button
66+
req = NewRequestWithValues(t, "POST", mergeUpstreamLink, map[string]string{"_csrf": GetUserCSRFToken(t, session)})
67+
session.MakeRequest(t, req, http.StatusOK)
68+
checkFileContent("test-content-1")
69+
70+
// update the files
71+
require.NoError(t, createOrReplaceFileInBranch(forkUser, forkRepo, "new-file-other.txt", "master", "test-content-other"))
72+
require.NoError(t, createOrReplaceFileInBranch(baseUser, baseRepo, "new-file.txt", "master", "test-content-2"))
73+
require.Eventually(t, func() bool {
74+
resp := session.MakeRequest(t, NewRequestf(t, "GET", "/%s/test-repo-fork", forkUser.Name), http.StatusOK)
75+
htmlDoc := NewHTMLParser(t, resp.Body)
76+
respMsg, _ := htmlDoc.Find(".ui.message:not(.positive)").Html()
77+
return strings.Contains(respMsg, `The base branch <a href="/user2/repo1/src/branch/master">user2/repo1:master</a> has new changes`)
78+
}, 5*time.Second, 100*time.Millisecond)
79+
80+
// and do the merge-upstream by API
81+
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/test-repo-fork/merge-upstream", forkUser.Name), &api.MergeUpstreamRequest{
82+
Branch: "master",
83+
}).AddTokenAuth(token)
84+
resp := MakeRequest(t, req, http.StatusOK)
85+
checkFileContent("test-content-2")
86+
87+
var mergeResp api.MergeUpstreamResponse
88+
DecodeJSON(t, resp, &mergeResp)
89+
assert.Equal(t, "merge", mergeResp.MergeStyle)
90+
})
91+
}

0 commit comments

Comments
 (0)