mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	Support "merge upstream branch" (Sync fork) (#32741)
Add basic "sync fork" support (GitHub-like) <details>  </details>
This commit is contained in:
		| @@ -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> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user