mirror of
https://github.com/go-gitea/gitea
synced 2024-12-22 16:44:26 +00:00
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 err != nil {
|
||||||
if strings.Contains(stderr, "non-fast-forward") {
|
if strings.Contains(stderr, "non-fast-forward") {
|
||||||
return &ErrPushOutOfDate{StdOut: stdout, StdErr: stderr, Err: err}
|
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 := &ErrPushRejected{StdOut: stdout, StdErr: stderr, Err: err}
|
||||||
err.GenerateMessage()
|
err.GenerateMessage()
|
||||||
return err
|
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.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.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.deleted_branch = (deleted):%s
|
||||||
pull.agit_documentation = Review documentation about AGit
|
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.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))
|
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"
|
"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["IsViewFile"] = true
|
||||||
ctx.Data["HideRepoInfo"] = true
|
ctx.Data["HideRepoInfo"] = true
|
||||||
blob := entry.Blob()
|
blob := entry.Blob()
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -86,29 +87,31 @@ func prepareOpenWithEditorApps(ctx *context.Context) {
|
|||||||
ctx.Data["OpenWithEditorApps"] = tmplApps
|
ctx.Data["OpenWithEditorApps"] = tmplApps
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareHomeSidebarCitationFile(ctx *context.Context, entry *git.TreeEntry) {
|
func prepareHomeSidebarCitationFile(entry *git.TreeEntry) func(ctx *context.Context) {
|
||||||
if entry.Name() != "" {
|
return func(ctx *context.Context) {
|
||||||
return
|
if entry.Name() != "" {
|
||||||
}
|
return
|
||||||
tree, err := ctx.Repo.Commit.SubTree(ctx.Repo.TreePath)
|
}
|
||||||
if err != nil {
|
tree, err := ctx.Repo.Commit.SubTree(ctx.Repo.TreePath)
|
||||||
HandleGitError(ctx, "Repo.Commit.SubTree", err)
|
if err != nil {
|
||||||
return
|
HandleGitError(ctx, "Repo.Commit.SubTree", err)
|
||||||
}
|
return
|
||||||
allEntries, err := tree.ListEntries()
|
}
|
||||||
if err != nil {
|
allEntries, err := tree.ListEntries()
|
||||||
ctx.ServerError("ListEntries", err)
|
if err != nil {
|
||||||
return
|
ctx.ServerError("ListEntries", err)
|
||||||
}
|
return
|
||||||
for _, entry := range allEntries {
|
}
|
||||||
if entry.Name() == "CITATION.cff" || entry.Name() == "CITATION.bib" {
|
for _, entry := range allEntries {
|
||||||
// Read Citation file contents
|
if entry.Name() == "CITATION.cff" || entry.Name() == "CITATION.bib" {
|
||||||
if content, err := entry.Blob().GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
|
// Read Citation file contents
|
||||||
log.Error("checkCitationFile: GetBlobContent: %v", err)
|
if content, err := entry.Blob().GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
|
||||||
} else {
|
log.Error("checkCitationFile: GetBlobContent: %v", err)
|
||||||
ctx.Data["CitiationExist"] = true
|
} else {
|
||||||
ctx.PageData["citationFileContent"] = content
|
ctx.Data["CitiationExist"] = true
|
||||||
break
|
ctx.PageData["citationFileContent"] = content
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,83 +177,21 @@ func prepareHomeSidebarLatestRelease(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderHomeCode(ctx *context.Context) {
|
func prepareUpstreamDivergingInfo(ctx *context.Context) {
|
||||||
ctx.Data["PageIsViewCode"] = true
|
if !ctx.Repo.Repository.IsFork || !ctx.Repo.IsViewBranch || ctx.Repo.TreePath != "" {
|
||||||
ctx.Data["RepositoryUploadEnabled"] = setting.Repository.Upload.Enabled
|
|
||||||
prepareOpenWithEditorApps(ctx)
|
|
||||||
|
|
||||||
if ctx.Repo.Commit == nil || ctx.Repo.Repository.IsEmpty || ctx.Repo.Repository.IsBroken() {
|
|
||||||
showEmpty := true
|
|
||||||
var err error
|
|
||||||
if ctx.Repo.GitRepo != nil {
|
|
||||||
showEmpty, err = ctx.Repo.GitRepo.IsEmpty()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("GitRepo.IsEmpty: %v", err)
|
|
||||||
ctx.Repo.Repository.Status = repo_model.RepositoryBroken
|
|
||||||
showEmpty = true
|
|
||||||
ctx.Flash.Error(ctx.Tr("error.occurred"), true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if showEmpty {
|
|
||||||
ctx.HTML(http.StatusOK, tplRepoEMPTY)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// the repo is not really empty, so we should update the modal in database
|
|
||||||
// such problem may be caused by:
|
|
||||||
// 1) an error occurs during pushing/receiving. 2) the user replaces an empty git repo manually
|
|
||||||
// and even more: the IsEmpty flag is deeply broken and should be removed with the UI changed to manage to cope with empty repos.
|
|
||||||
// it's possible for a repository to be non-empty by that flag but still 500
|
|
||||||
// because there are no branches - only tags -or the default branch is non-extant as it has been 0-pushed.
|
|
||||||
ctx.Repo.Repository.IsEmpty = false
|
|
||||||
if err = repo_model.UpdateRepositoryCols(ctx, ctx.Repo.Repository, "is_empty"); err != nil {
|
|
||||||
ctx.ServerError("UpdateRepositoryCols", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = repo_module.UpdateRepoSize(ctx, ctx.Repo.Repository); err != nil {
|
|
||||||
ctx.ServerError("UpdateRepoSize", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// the repo's IsEmpty has been updated, redirect to this page to make sure middlewares can get the correct values
|
|
||||||
link := ctx.Link
|
|
||||||
if ctx.Req.URL.RawQuery != "" {
|
|
||||||
link += "?" + ctx.Req.URL.RawQuery
|
|
||||||
}
|
|
||||||
ctx.Redirect(link)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
upstreamDivergingInfo, err := repo_service.GetUpstreamDivergingInfo(ctx, ctx.Repo.Repository, ctx.Repo.BranchName)
|
||||||
title := ctx.Repo.Repository.Owner.Name + "/" + ctx.Repo.Repository.Name
|
|
||||||
if len(ctx.Repo.Repository.Description) > 0 {
|
|
||||||
title += ": " + ctx.Repo.Repository.Description
|
|
||||||
}
|
|
||||||
ctx.Data["Title"] = title
|
|
||||||
|
|
||||||
// Get Topics of this repo
|
|
||||||
prepareHomeSidebarRepoTopics(ctx)
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current entry user currently looking at.
|
|
||||||
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
HandleGitError(ctx, "Repo.Commit.GetTreeEntryByPath", err)
|
if !errors.Is(err, util.ErrNotExist) && !errors.Is(err, util.ErrInvalidArgument) {
|
||||||
return
|
log.Error("GetUpstreamDivergingInfo: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
checkOutdatedBranch(ctx)
|
|
||||||
|
|
||||||
if entry.IsDir() {
|
|
||||||
prepareToRenderDirectory(ctx)
|
|
||||||
} else {
|
|
||||||
renderFile(ctx, entry)
|
|
||||||
}
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ctx.Data["UpstreamDivergingInfo"] = upstreamDivergingInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareRecentlyPushedNewBranches(ctx *context.Context) {
|
||||||
if ctx.Doer != nil {
|
if ctx.Doer != nil {
|
||||||
if err := ctx.Repo.Repository.GetBaseRepo(ctx); err != nil {
|
if err := ctx.Repo.Repository.GetBaseRepo(ctx); err != nil {
|
||||||
ctx.ServerError("GetBaseRepo", err)
|
ctx.ServerError("GetBaseRepo", err)
|
||||||
@ -280,7 +221,112 @@ func renderHomeCode(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleRepoEmptyOrBroken(ctx *context.Context) {
|
||||||
|
showEmpty := true
|
||||||
|
var err error
|
||||||
|
if ctx.Repo.GitRepo != nil {
|
||||||
|
showEmpty, err = ctx.Repo.GitRepo.IsEmpty()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GitRepo.IsEmpty: %v", err)
|
||||||
|
ctx.Repo.Repository.Status = repo_model.RepositoryBroken
|
||||||
|
showEmpty = true
|
||||||
|
ctx.Flash.Error(ctx.Tr("error.occurred"), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if showEmpty {
|
||||||
|
ctx.HTML(http.StatusOK, tplRepoEMPTY)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// the repo is not really empty, so we should update the modal in database
|
||||||
|
// such problem may be caused by:
|
||||||
|
// 1) an error occurs during pushing/receiving. 2) the user replaces an empty git repo manually
|
||||||
|
// and even more: the IsEmpty flag is deeply broken and should be removed with the UI changed to manage to cope with empty repos.
|
||||||
|
// it's possible for a repository to be non-empty by that flag but still 500
|
||||||
|
// because there are no branches - only tags -or the default branch is non-extant as it has been 0-pushed.
|
||||||
|
ctx.Repo.Repository.IsEmpty = false
|
||||||
|
if err = repo_model.UpdateRepositoryCols(ctx, ctx.Repo.Repository, "is_empty"); err != nil {
|
||||||
|
ctx.ServerError("UpdateRepositoryCols", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = repo_module.UpdateRepoSize(ctx, ctx.Repo.Repository); err != nil {
|
||||||
|
ctx.ServerError("UpdateRepoSize", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// the repo's IsEmpty has been updated, redirect to this page to make sure middlewares can get the correct values
|
||||||
|
link := ctx.Link
|
||||||
|
if ctx.Req.URL.RawQuery != "" {
|
||||||
|
link += "?" + ctx.Req.URL.RawQuery
|
||||||
|
}
|
||||||
|
ctx.Redirect(link)
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareToRenderDirOrFile(entry *git.TreeEntry) func(ctx *context.Context) {
|
||||||
|
return func(ctx *context.Context) {
|
||||||
|
if entry.IsDir() {
|
||||||
|
prepareToRenderDirectory(ctx)
|
||||||
|
} else {
|
||||||
|
prepareToRenderFile(ctx, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleRepoHomeFeed(ctx *context.Context) bool {
|
||||||
|
if setting.Other.EnableFeed {
|
||||||
|
isFeed, _, showFeedType := feed.GetFeedType(ctx.PathParam(":reponame"), ctx.Req)
|
||||||
|
if isFeed {
|
||||||
|
switch {
|
||||||
|
case ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType):
|
||||||
|
feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType)
|
||||||
|
case ctx.Repo.TreePath == "":
|
||||||
|
feed.ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType)
|
||||||
|
case ctx.Repo.TreePath != "":
|
||||||
|
feed.ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Home render repository home page
|
||||||
|
func Home(ctx *context.Context) {
|
||||||
|
if handleRepoHomeFeed(ctx) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the repo is viewable: not in migration, and the code unit should be enabled
|
||||||
|
// Ideally the "feed" logic should be after this, but old code did so, so keep it as-is.
|
||||||
|
checkHomeCodeViewable(ctx)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
title := ctx.Repo.Repository.Owner.Name + "/" + ctx.Repo.Repository.Name
|
||||||
|
if len(ctx.Repo.Repository.Description) > 0 {
|
||||||
|
title += ": " + ctx.Repo.Repository.Description
|
||||||
|
}
|
||||||
|
ctx.Data["Title"] = title
|
||||||
|
ctx.Data["PageIsViewCode"] = true
|
||||||
|
ctx.Data["RepositoryUploadEnabled"] = setting.Repository.Upload.Enabled // show New File / Upload File buttons
|
||||||
|
|
||||||
|
if ctx.Repo.Commit == nil || ctx.Repo.Repository.IsEmpty || ctx.Repo.Repository.IsBroken() {
|
||||||
|
// empty or broken repositories need to be handled differently
|
||||||
|
handleRepoEmptyOrBroken(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the current git entry which doer user is currently looking at.
|
||||||
|
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
|
||||||
|
if err != nil {
|
||||||
|
HandleGitError(ctx, "Repo.Commit.GetTreeEntryByPath", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare the tree path
|
||||||
var treeNames, paths []string
|
var treeNames, paths []string
|
||||||
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
|
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
|
||||||
treeLink := branchLink
|
treeLink := branchLink
|
||||||
@ -295,57 +341,38 @@ func renderHomeCode(ctx *context.Context) {
|
|||||||
ctx.Data["ParentPath"] = "/" + paths[len(paths)-2]
|
ctx.Data["ParentPath"] = "/" + paths[len(paths)-2]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isTreePathRoot := ctx.Repo.TreePath == ""
|
|
||||||
if isTreePathRoot {
|
|
||||||
prepareHomeSidebarLicenses(ctx)
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
prepareHomeSidebarCitationFile(ctx, entry)
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareHomeSidebarLanguageStats(ctx)
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareHomeSidebarLatestRelease(ctx)
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Data["Paths"] = paths
|
ctx.Data["Paths"] = paths
|
||||||
ctx.Data["TreeLink"] = treeLink
|
ctx.Data["TreeLink"] = treeLink
|
||||||
ctx.Data["TreeNames"] = treeNames
|
ctx.Data["TreeNames"] = treeNames
|
||||||
ctx.Data["BranchLink"] = branchLink
|
ctx.Data["BranchLink"] = branchLink
|
||||||
ctx.HTML(http.StatusOK, tplRepoHome)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Home render repository home page
|
// some UI components are only shown when the tree path is root
|
||||||
func Home(ctx *context.Context) {
|
isTreePathRoot := ctx.Repo.TreePath == ""
|
||||||
if setting.Other.EnableFeed {
|
|
||||||
isFeed, _, showFeedType := feed.GetFeedType(ctx.PathParam(":reponame"), ctx.Req)
|
prepareFuncs := []func(*context.Context){
|
||||||
if isFeed {
|
prepareOpenWithEditorApps,
|
||||||
switch {
|
prepareHomeSidebarRepoTopics,
|
||||||
case ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType):
|
checkOutdatedBranch,
|
||||||
feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType)
|
prepareToRenderDirOrFile(entry),
|
||||||
case ctx.Repo.TreePath == "":
|
prepareRecentlyPushedNewBranches,
|
||||||
feed.ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType)
|
}
|
||||||
case ctx.Repo.TreePath != "":
|
|
||||||
feed.ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType)
|
if isTreePathRoot {
|
||||||
}
|
prepareFuncs = append(prepareFuncs,
|
||||||
|
prepareUpstreamDivergingInfo,
|
||||||
|
prepareHomeSidebarLicenses,
|
||||||
|
prepareHomeSidebarCitationFile(entry),
|
||||||
|
prepareHomeSidebarLanguageStats,
|
||||||
|
prepareHomeSidebarLatestRelease,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, prepare := range prepareFuncs {
|
||||||
|
prepare(ctx)
|
||||||
|
if ctx.Written() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkHomeCodeViewable(ctx)
|
ctx.HTML(http.StatusOK, tplRepoHome)
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
renderHomeCode(ctx)
|
|
||||||
}
|
}
|
||||||
|
@ -1320,6 +1320,7 @@ func registerRoutes(m *web.Router) {
|
|||||||
m.Post("/delete", repo.DeleteBranchPost)
|
m.Post("/delete", repo.DeleteBranchPost)
|
||||||
m.Post("/restore", repo.RestoreBranchPost)
|
m.Post("/restore", repo.RestoreBranchPost)
|
||||||
m.Post("/rename", web.Bind(forms.RenameBranchForm{}), repo_setting.RenameBranchPost)
|
m.Post("/rename", web.Bind(forms.RenameBranchForm{}), repo_setting.RenameBranchPost)
|
||||||
|
m.Post("/merge-upstream", repo.MergeUpstream)
|
||||||
}, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty)
|
}, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty)
|
||||||
|
|
||||||
m.Combo("/fork").Get(repo.Fork).Post(web.Bind(forms.CreateRepoForm{}), repo.ForkPost)
|
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)
|
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{
|
reversePR := &issues_model.PullRequest{
|
||||||
ID: pr.ID,
|
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}}
|
{{else if .IsBlame}}
|
||||||
{{template "repo/blame" .}}
|
{{template "repo/blame" .}}
|
||||||
{{else}}{{/* IsViewDirectory */}}
|
{{else}}{{/* IsViewDirectory */}}
|
||||||
|
{{if $isTreePathRoot}}
|
||||||
|
{{template "repo/code/upstream_diverging_info" .}}
|
||||||
|
{{end}}
|
||||||
{{template "repo/view_list" .}}
|
{{template "repo/view_list" .}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user