Skip to content

Commit e5f3c16

Browse files
Fix sync fork for consistency (#33147)
Fixes #33145 An integration test could be added. --------- Co-authored-by: wxiaoguang <[email protected]>
1 parent 189e740 commit e5f3c16

File tree

8 files changed

+308
-13
lines changed

8 files changed

+308
-13
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+
}

services/repository/merge_upstream.go

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,20 @@ import (
1212
repo_model "code.gitea.io/gitea/models/repo"
1313
user_model "code.gitea.io/gitea/models/user"
1414
"code.gitea.io/gitea/modules/git"
15+
"code.gitea.io/gitea/modules/gitrepo"
1516
repo_module "code.gitea.io/gitea/modules/repository"
17+
"code.gitea.io/gitea/modules/reqctx"
1618
"code.gitea.io/gitea/modules/util"
1719
"code.gitea.io/gitea/services/pull"
1820
)
1921

2022
type UpstreamDivergingInfo struct {
21-
BaseIsNewer bool
22-
CommitsBehind int
23-
CommitsAhead int
23+
BaseHasNewCommits bool
24+
CommitsBehind int
25+
CommitsAhead int
2426
}
2527

28+
// MergeUpstream merges the base repository's default branch into the fork repository's current branch.
2629
func MergeUpstream(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, branch string) (mergeStyle string, err error) {
2730
if err = repo.MustNotBeArchived(); err != nil {
2831
return "", err
@@ -32,7 +35,7 @@ func MergeUpstream(ctx context.Context, doer *user_model.User, repo *repo_model.
3235
}
3336
err = git.Push(ctx, repo.BaseRepo.RepoPath(), git.PushOptions{
3437
Remote: repo.RepoPath(),
35-
Branch: fmt.Sprintf("%s:%s", branch, branch),
38+
Branch: fmt.Sprintf("%s:%s", repo.BaseRepo.DefaultBranch, branch),
3639
Env: repo_module.PushingEnvironment(doer, repo),
3740
})
3841
if err == nil {
@@ -64,7 +67,7 @@ func MergeUpstream(ctx context.Context, doer *user_model.User, repo *repo_model.
6467
BaseRepoID: repo.BaseRepo.ID,
6568
BaseRepo: repo.BaseRepo,
6669
HeadBranch: branch, // maybe HeadCommitID is not needed
67-
BaseBranch: branch,
70+
BaseBranch: repo.BaseRepo.DefaultBranch,
6871
}
6972
fakeIssue.PullRequest = fakePR
7073
err = pull.Update(ctx, fakePR, doer, "merge upstream", false)
@@ -74,7 +77,8 @@ func MergeUpstream(ctx context.Context, doer *user_model.User, repo *repo_model.
7477
return "merge", nil
7578
}
7679

77-
func GetUpstreamDivergingInfo(ctx context.Context, repo *repo_model.Repository, branch string) (*UpstreamDivergingInfo, error) {
80+
// GetUpstreamDivergingInfo returns the information about the divergence between the fork repository's branch and the base repository's default branch.
81+
func GetUpstreamDivergingInfo(ctx reqctx.RequestContext, repo *repo_model.Repository, branch string) (*UpstreamDivergingInfo, error) {
7882
if !repo.IsFork {
7983
return nil, util.NewInvalidArgumentErrorf("repo is not a fork")
8084
}
@@ -92,7 +96,7 @@ func GetUpstreamDivergingInfo(ctx context.Context, repo *repo_model.Repository,
9296
return nil, err
9397
}
9498

95-
baseBranch, err := git_model.GetBranch(ctx, repo.BaseRepo.ID, branch)
99+
baseBranch, err := git_model.GetBranch(ctx, repo.BaseRepo.ID, repo.BaseRepo.DefaultBranch)
96100
if err != nil {
97101
return nil, err
98102
}
@@ -102,14 +106,39 @@ func GetUpstreamDivergingInfo(ctx context.Context, repo *repo_model.Repository,
102106
return info, nil
103107
}
104108

105-
// TODO: if the fork repo has new commits, this call will fail:
109+
// if the fork repo has new commits, this call will fail because they are not in the base repo
106110
// exit status 128 - fatal: Invalid symmetric difference expression aaaaaaaaaaaa...bbbbbbbbbbbb
107-
// so at the moment, we are not able to handle this case, should be improved in the future
111+
// so at the moment, we first check the update time, then check whether the fork branch has base's head
108112
diff, err := git.GetDivergingCommits(ctx, repo.BaseRepo.RepoPath(), baseBranch.CommitID, forkBranch.CommitID)
109113
if err != nil {
110-
info.BaseIsNewer = baseBranch.UpdatedUnix > forkBranch.UpdatedUnix
114+
info.BaseHasNewCommits = baseBranch.UpdatedUnix > forkBranch.UpdatedUnix
115+
if info.BaseHasNewCommits {
116+
return info, nil
117+
}
118+
119+
// if the base's update time is before the fork, check whether the base's head is in the fork
120+
baseGitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, repo.BaseRepo)
121+
if err != nil {
122+
return nil, err
123+
}
124+
headGitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, repo)
125+
if err != nil {
126+
return nil, err
127+
}
128+
129+
baseCommitID, err := baseGitRepo.ConvertToGitID(baseBranch.CommitID)
130+
if err != nil {
131+
return nil, err
132+
}
133+
headCommit, err := headGitRepo.GetCommit(forkBranch.CommitID)
134+
if err != nil {
135+
return nil, err
136+
}
137+
hasPreviousCommit, _ := headCommit.HasPreviousCommit(baseCommitID)
138+
info.BaseHasNewCommits = !hasPreviousCommit
111139
return info, nil
112140
}
141+
113142
info.CommitsBehind, info.CommitsAhead = diff.Behind, diff.Ahead
114143
return info, nil
115144
}

templates/repo/code/upstream_diverging_info.tmpl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
{{if and .UpstreamDivergingInfo (or .UpstreamDivergingInfo.BaseIsNewer .UpstreamDivergingInfo.CommitsBehind)}}
1+
{{if and .UpstreamDivergingInfo (or .UpstreamDivergingInfo.BaseHasNewCommits .UpstreamDivergingInfo.CommitsBehind)}}
22
<div class="ui message flex-text-block">
33
<div class="tw-flex-1">
4-
{{$upstreamLink := printf "%s/src/branch/%s" .Repository.BaseRepo.Link (.BranchName|PathEscapeSegments)}}
5-
{{$upstreamHtml := HTMLFormat `<a href="%s">%s:%s</a>` $upstreamLink .Repository.BaseRepo.FullName .BranchName}}
4+
{{$upstreamLink := printf "%s/src/branch/%s" .Repository.BaseRepo.Link (.Repository.BaseRepo.DefaultBranch|PathEscapeSegments)}}
5+
{{$upstreamHtml := HTMLFormat `<a href="%s">%s:%s</a>` $upstreamLink .Repository.BaseRepo.FullName .Repository.BaseRepo.DefaultBranch}}
66
{{if .UpstreamDivergingInfo.CommitsBehind}}
77
{{ctx.Locale.TrN .UpstreamDivergingInfo.CommitsBehind "repo.pulls.upstream_diverging_prompt_behind_1" "repo.pulls.upstream_diverging_prompt_behind_n" .UpstreamDivergingInfo.CommitsBehind $upstreamHtml}}
88
{{else}}

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.

0 commit comments

Comments
 (0)