Support "merge upstream branch" (Sync fork) (#32741)
Add basic "sync fork" support (GitHub-like) <details> ![image](https://github.com/user-attachments/assets/e71473f4-4518-48c7-b9e2-fedfcd564fc3) </details>
This commit is contained in:
parent
5a75160c92
commit
513da407f4
@ -223,7 +223,7 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error {
|
||||
if err != nil {
|
||||
if strings.Contains(stderr, "non-fast-forward") {
|
||||
return &ErrPushOutOfDate{StdOut: stdout, StdErr: stderr, Err: err}
|
||||
} else if strings.Contains(stderr, "! [remote rejected]") {
|
||||
} else if strings.Contains(stderr, "! [remote rejected]") || strings.Contains(stderr, "! [rejected]") {
|
||||
err := &ErrPushRejected{StdOut: stdout, StdErr: stderr, Err: err}
|
||||
err.GenerateMessage()
|
||||
return err
|
||||
|
@ -1946,6 +1946,10 @@ pulls.delete.title = Delete this pull request?
|
||||
pulls.delete.text = Do you really want to delete this pull request? (This will permanently remove all content. Consider closing it instead, if you intend to keep it archived)
|
||||
|
||||
pulls.recently_pushed_new_branches = You pushed on branch <strong>%[1]s</strong> %[2]s
|
||||
pulls.upstream_diverging_prompt_behind_1 = This branch is %d commit behind %s
|
||||
pulls.upstream_diverging_prompt_behind_n = This branch is %d commits behind %s
|
||||
pulls.upstream_diverging_prompt_base_newer = The base branch %s has new changes
|
||||
pulls.upstream_diverging_merge = Sync fork
|
||||
|
||||
pull.deleted_branch = (deleted):%s
|
||||
pull.agit_documentation = Review documentation about AGit
|
||||
|
@ -259,3 +259,20 @@ func CreateBranch(ctx *context.Context) {
|
||||
ctx.Flash.Success(ctx.Tr("repo.branch.create_success", form.NewBranchName))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(form.NewBranchName) + "/" + util.PathEscapeSegments(form.CurrentPath))
|
||||
}
|
||||
|
||||
func MergeUpstream(ctx *context.Context) {
|
||||
branchName := ctx.FormString("branch")
|
||||
_, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, branchName)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.JSONError(ctx.Tr("error.not_found"))
|
||||
return
|
||||
} else if models.IsErrMergeConflicts(err) {
|
||||
ctx.JSONError(ctx.Tr("repo.pulls.merge_conflict"))
|
||||
return
|
||||
}
|
||||
ctx.ServerError("MergeUpstream", err)
|
||||
return
|
||||
}
|
||||
ctx.JSONRedirect("")
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import (
|
||||
"github.com/nektos/act/pkg/model"
|
||||
)
|
||||
|
||||
func renderFile(ctx *context.Context, entry *git.TreeEntry) {
|
||||
func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) {
|
||||
ctx.Data["IsViewFile"] = true
|
||||
ctx.Data["HideRepoInfo"] = true
|
||||
blob := entry.Blob()
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1320,6 +1320,7 @@ func registerRoutes(m *web.Router) {
|
||||
m.Post("/delete", repo.DeleteBranchPost)
|
||||
m.Post("/restore", repo.RestoreBranchPost)
|
||||
m.Post("/rename", web.Bind(forms.RenameBranchForm{}), repo_setting.RenameBranchPost)
|
||||
m.Post("/merge-upstream", repo.MergeUpstream)
|
||||
}, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty)
|
||||
|
||||
m.Combo("/fork").Get(repo.Fork).Post(web.Bind(forms.CreateRepoForm{}), repo.ForkPost)
|
||||
|
@ -65,7 +65,9 @@ func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.
|
||||
return fmt.Errorf("unable to load HeadRepo for PR[%d] during update-by-merge: %w", pr.ID, err)
|
||||
}
|
||||
|
||||
// use merge functions but switch repos and branches
|
||||
// TODO: FakePR: it is somewhat hacky, but it is the only way to "merge" at the moment
|
||||
// ideally in the future the "merge" functions should be refactored to decouple from the PullRequest
|
||||
// now use a fake reverse PR to switch head&base repos/branches
|
||||
reversePR := &issues_model.PullRequest{
|
||||
ID: pr.ID,
|
||||
|
||||
|
115
services/repository/merge_upstream.go
Normal file
115
services/repository/merge_upstream.go
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issue_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/pull"
|
||||
)
|
||||
|
||||
type UpstreamDivergingInfo struct {
|
||||
BaseIsNewer bool
|
||||
CommitsBehind int
|
||||
CommitsAhead int
|
||||
}
|
||||
|
||||
func MergeUpstream(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, branch string) (mergeStyle string, err error) {
|
||||
if err = repo.MustNotBeArchived(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err = repo.GetBaseRepo(ctx); err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = git.Push(ctx, repo.BaseRepo.RepoPath(), git.PushOptions{
|
||||
Remote: repo.RepoPath(),
|
||||
Branch: fmt.Sprintf("%s:%s", branch, branch),
|
||||
Env: repo_module.PushingEnvironment(doer, repo),
|
||||
})
|
||||
if err == nil {
|
||||
return "fast-forward", nil
|
||||
}
|
||||
if !git.IsErrPushOutOfDate(err) && !git.IsErrPushRejected(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// TODO: FakePR: it is somewhat hacky, but it is the only way to "merge" at the moment
|
||||
// ideally in the future the "merge" functions should be refactored to decouple from the PullRequest
|
||||
fakeIssue := &issue_model.Issue{
|
||||
ID: -1,
|
||||
RepoID: repo.ID,
|
||||
Repo: repo,
|
||||
Index: -1,
|
||||
PosterID: doer.ID,
|
||||
Poster: doer,
|
||||
IsPull: true,
|
||||
}
|
||||
fakePR := &issue_model.PullRequest{
|
||||
ID: -1,
|
||||
Status: issue_model.PullRequestStatusMergeable,
|
||||
IssueID: -1,
|
||||
Issue: fakeIssue,
|
||||
Index: -1,
|
||||
HeadRepoID: repo.ID,
|
||||
HeadRepo: repo,
|
||||
BaseRepoID: repo.BaseRepo.ID,
|
||||
BaseRepo: repo.BaseRepo,
|
||||
HeadBranch: branch, // maybe HeadCommitID is not needed
|
||||
BaseBranch: branch,
|
||||
}
|
||||
fakeIssue.PullRequest = fakePR
|
||||
err = pull.Update(ctx, fakePR, doer, "merge upstream", false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "merge", nil
|
||||
}
|
||||
|
||||
func GetUpstreamDivergingInfo(ctx context.Context, repo *repo_model.Repository, branch string) (*UpstreamDivergingInfo, error) {
|
||||
if !repo.IsFork {
|
||||
return nil, util.NewInvalidArgumentErrorf("repo is not a fork")
|
||||
}
|
||||
|
||||
if repo.IsArchived {
|
||||
return nil, util.NewInvalidArgumentErrorf("repo is archived")
|
||||
}
|
||||
|
||||
if err := repo.GetBaseRepo(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
forkBranch, err := git_model.GetBranch(ctx, repo.ID, branch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
baseBranch, err := git_model.GetBranch(ctx, repo.BaseRepo.ID, branch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := &UpstreamDivergingInfo{}
|
||||
if forkBranch.CommitID == baseBranch.CommitID {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// TODO: if the fork repo has new commits, this call will fail:
|
||||
// exit status 128 - fatal: Invalid symmetric difference expression aaaaaaaaaaaa...bbbbbbbbbbbb
|
||||
// so at the moment, we are not able to handle this case, should be improved in the future
|
||||
diff, err := git.GetDivergingCommits(ctx, repo.BaseRepo.RepoPath(), baseBranch.CommitID, forkBranch.CommitID)
|
||||
if err != nil {
|
||||
info.BaseIsNewer = baseBranch.UpdatedUnix > forkBranch.UpdatedUnix
|
||||
return info, nil
|
||||
}
|
||||
info.CommitsBehind, info.CommitsAhead = diff.Behind, diff.Ahead
|
||||
return info, nil
|
||||
}
|
18
templates/repo/code/upstream_diverging_info.tmpl
Normal file
18
templates/repo/code/upstream_diverging_info.tmpl
Normal file
@ -0,0 +1,18 @@
|
||||
{{if and .UpstreamDivergingInfo (or .UpstreamDivergingInfo.BaseIsNewer .UpstreamDivergingInfo.CommitsBehind)}}
|
||||
<div class="ui message flex-text-block">
|
||||
<div class="tw-flex-1">
|
||||
{{$upstreamLink := printf "%s/src/branch/%s" .Repository.BaseRepo.Link (.BranchName|PathEscapeSegments)}}
|
||||
{{$upstreamHtml := HTMLFormat `<a href="%s">%s:%s</a>` $upstreamLink .Repository.BaseRepo.FullName .BranchName}}
|
||||
{{if .UpstreamDivergingInfo.CommitsBehind}}
|
||||
{{ctx.Locale.TrN .UpstreamDivergingInfo.CommitsBehind "repo.pulls.upstream_diverging_prompt_behind_1" "repo.pulls.upstream_diverging_prompt_behind_n" .UpstreamDivergingInfo.CommitsBehind $upstreamHtml}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.pulls.upstream_diverging_prompt_base_newer" $upstreamHtml}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{if .CanWriteCode}}
|
||||
<button class="ui compact green button tw-m-0 link-action" data-url="{{.Repository.Link}}/branches/merge-upstream?branch={{.BranchName}}">
|
||||
{{ctx.Locale.Tr "repo.pulls.upstream_diverging_merge"}}
|
||||
</button>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
@ -136,6 +136,9 @@
|
||||
{{else if .IsBlame}}
|
||||
{{template "repo/blame" .}}
|
||||
{{else}}{{/* IsViewDirectory */}}
|
||||
{{if $isTreePathRoot}}
|
||||
{{template "repo/code/upstream_diverging_info" .}}
|
||||
{{end}}
|
||||
{{template "repo/view_list" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user