mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 19:45:25 +01:00 
			
		
		
		
	Fix bug when review pull request commits (#35192)
The commit range in the UI follows a half-open, half-closed convention: (,]. When reviewing a range of commits, the beforeCommitID should be set to the commit immediately preceding the first selected commit. For single-commit reviews, we must identify and use the previous commit of that specific commit. The endpoint ViewPullFilesStartingFromCommit is currently unused and can be safely removed. Fix #35157 Replace #35184 Partially extract from #35077
This commit is contained in:
		
							parent
							
								
									1f676b36b1
								
							
						
					
					
						commit
						be2a6b4414
					
				| @ -643,8 +643,17 @@ func ViewPullCommits(ctx *context.Context) { | ||||
| 	ctx.HTML(http.StatusOK, tplPullCommits) | ||||
| } | ||||
| 
 | ||||
| func indexCommit(commits []*git.Commit, commitID string) *git.Commit { | ||||
| 	for i := range commits { | ||||
| 		if commits[i].ID.String() == commitID { | ||||
| 			return commits[i] | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // ViewPullFiles render pull request changed files list page | ||||
| func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommit string, willShowSpecifiedCommitRange, willShowSpecifiedCommit bool) { | ||||
| func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) { | ||||
| 	ctx.Data["PageIsPullList"] = true | ||||
| 	ctx.Data["PageIsPullFiles"] = true | ||||
| 
 | ||||
| @ -654,11 +663,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi | ||||
| 	} | ||||
| 	pull := issue.PullRequest | ||||
| 
 | ||||
| 	var ( | ||||
| 		startCommitID string | ||||
| 		endCommitID   string | ||||
| 		gitRepo       = ctx.Repo.GitRepo | ||||
| 	) | ||||
| 	gitRepo := ctx.Repo.GitRepo | ||||
| 
 | ||||
| 	prInfo := preparePullViewPullInfo(ctx, issue) | ||||
| 	if ctx.Written() { | ||||
| @ -668,77 +673,68 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// Validate the given commit sha to show (if any passed) | ||||
| 	if willShowSpecifiedCommit || willShowSpecifiedCommitRange { | ||||
| 		foundStartCommit := len(specifiedStartCommit) == 0 | ||||
| 		foundEndCommit := len(specifiedEndCommit) == 0 | ||||
| 
 | ||||
| 		if !(foundStartCommit && foundEndCommit) { | ||||
| 			for _, commit := range prInfo.Commits { | ||||
| 				if commit.ID.String() == specifiedStartCommit { | ||||
| 					foundStartCommit = true | ||||
| 				} | ||||
| 				if commit.ID.String() == specifiedEndCommit { | ||||
| 					foundEndCommit = true | ||||
| 				} | ||||
| 
 | ||||
| 				if foundStartCommit && foundEndCommit { | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if !(foundStartCommit && foundEndCommit) { | ||||
| 			ctx.NotFound(nil) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	headCommitID, err := gitRepo.GetRefCommitID(pull.GetGitHeadRefName()) | ||||
| 	if err != nil { | ||||
| 		ctx.ServerError("GetRefCommitID", err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.Data["IsShowingOnlySingleCommit"] = willShowSpecifiedCommit | ||||
| 	isSingleCommit := beforeCommitID == "" && afterCommitID != "" | ||||
| 	ctx.Data["IsShowingOnlySingleCommit"] = isSingleCommit | ||||
| 	isShowAllCommits := (beforeCommitID == "" || beforeCommitID == prInfo.MergeBase) && (afterCommitID == "" || afterCommitID == headCommitID) | ||||
| 	ctx.Data["IsShowingAllCommits"] = isShowAllCommits | ||||
| 
 | ||||
| 	if willShowSpecifiedCommit || willShowSpecifiedCommitRange { | ||||
| 		if len(specifiedEndCommit) > 0 { | ||||
| 			endCommitID = specifiedEndCommit | ||||
| 	if afterCommitID == "" || afterCommitID == headCommitID { | ||||
| 		afterCommitID = headCommitID | ||||
| 	} | ||||
| 	afterCommit := indexCommit(prInfo.Commits, afterCommitID) | ||||
| 	if afterCommit == nil { | ||||
| 		ctx.HTTPError(http.StatusBadRequest, "after commit not found in PR commits") | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	var beforeCommit *git.Commit | ||||
| 	if !isSingleCommit { | ||||
| 		if beforeCommitID == "" || beforeCommitID == prInfo.MergeBase { | ||||
| 			beforeCommitID = prInfo.MergeBase | ||||
| 			// mergebase commit is not in the list of the pull request commits | ||||
| 			beforeCommit, err = gitRepo.GetCommit(beforeCommitID) | ||||
| 			if err != nil { | ||||
| 				ctx.ServerError("GetCommit", err) | ||||
| 				return | ||||
| 			} | ||||
| 		} else { | ||||
| 			endCommitID = headCommitID | ||||
| 			beforeCommit = indexCommit(prInfo.Commits, beforeCommitID) | ||||
| 			if beforeCommit == nil { | ||||
| 				ctx.HTTPError(http.StatusBadRequest, "before commit not found in PR commits") | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 		if len(specifiedStartCommit) > 0 { | ||||
| 			startCommitID = specifiedStartCommit | ||||
| 		} else { | ||||
| 			startCommitID = prInfo.MergeBase | ||||
| 		} | ||||
| 		ctx.Data["IsShowingAllCommits"] = false | ||||
| 	} else { | ||||
| 		endCommitID = headCommitID | ||||
| 		startCommitID = prInfo.MergeBase | ||||
| 		ctx.Data["IsShowingAllCommits"] = true | ||||
| 		beforeCommit, err = afterCommit.Parent(0) | ||||
| 		if err != nil { | ||||
| 			ctx.ServerError("Parent", err) | ||||
| 			return | ||||
| 		} | ||||
| 		beforeCommitID = beforeCommit.ID.String() | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.Data["Username"] = ctx.Repo.Owner.Name | ||||
| 	ctx.Data["Reponame"] = ctx.Repo.Repository.Name | ||||
| 	ctx.Data["AfterCommitID"] = endCommitID | ||||
| 	ctx.Data["BeforeCommitID"] = startCommitID | ||||
| 
 | ||||
| 	fileOnly := ctx.FormBool("file-only") | ||||
| 	ctx.Data["MergeBase"] = prInfo.MergeBase | ||||
| 	ctx.Data["AfterCommitID"] = afterCommitID | ||||
| 	ctx.Data["BeforeCommitID"] = beforeCommitID | ||||
| 
 | ||||
| 	maxLines, maxFiles := setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffFiles | ||||
| 	files := ctx.FormStrings("files") | ||||
| 	fileOnly := ctx.FormBool("file-only") | ||||
| 	if fileOnly && (len(files) == 2 || len(files) == 1) { | ||||
| 		maxLines, maxFiles = -1, -1 | ||||
| 	} | ||||
| 
 | ||||
| 	diffOptions := &gitdiff.DiffOptions{ | ||||
| 		AfterCommitID:      endCommitID, | ||||
| 		BeforeCommitID:     beforeCommitID, | ||||
| 		AfterCommitID:      afterCommitID, | ||||
| 		SkipTo:             ctx.FormString("skip-to"), | ||||
| 		MaxLines:           maxLines, | ||||
| 		MaxLineCharacters:  setting.Git.MaxGitDiffLineCharacters, | ||||
| @ -746,10 +742,6 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi | ||||
| 		WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)), | ||||
| 	} | ||||
| 
 | ||||
| 	if !willShowSpecifiedCommit { | ||||
| 		diffOptions.BeforeCommitID = startCommitID | ||||
| 	} | ||||
| 
 | ||||
| 	diff, err := gitdiff.GetDiffForRender(ctx, ctx.Repo.RepoLink, gitRepo, diffOptions, files...) | ||||
| 	if err != nil { | ||||
| 		ctx.ServerError("GetDiff", err) | ||||
| @ -761,7 +753,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi | ||||
| 	// as the viewed information is designed to be loaded only on latest PR | ||||
| 	// diff and if you're signed in. | ||||
| 	var reviewState *pull_model.ReviewState | ||||
| 	if ctx.IsSigned && !willShowSpecifiedCommit && !willShowSpecifiedCommitRange { | ||||
| 	if ctx.IsSigned && isShowAllCommits { | ||||
| 		reviewState, err = gitdiff.SyncUserSpecificDiff(ctx, ctx.Doer.ID, pull, gitRepo, diff, diffOptions) | ||||
| 		if err != nil { | ||||
| 			ctx.ServerError("SyncUserSpecificDiff", err) | ||||
| @ -769,7 +761,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	diffShortStat, err := gitdiff.GetDiffShortStat(ctx.Repo.GitRepo, startCommitID, endCommitID) | ||||
| 	diffShortStat, err := gitdiff.GetDiffShortStat(ctx.Repo.GitRepo, beforeCommitID, afterCommitID) | ||||
| 	if err != nil { | ||||
| 		ctx.ServerError("GetDiffShortStat", err) | ||||
| 		return | ||||
| @ -816,7 +808,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi | ||||
| 
 | ||||
| 	if !fileOnly { | ||||
| 		// note: use mergeBase is set to false because we already have the merge base from the pull request info | ||||
| 		diffTree, err := gitdiff.GetDiffTree(ctx, gitRepo, false, startCommitID, endCommitID) | ||||
| 		diffTree, err := gitdiff.GetDiffTree(ctx, gitRepo, false, beforeCommitID, afterCommitID) | ||||
| 		if err != nil { | ||||
| 			ctx.ServerError("GetDiffTree", err) | ||||
| 			return | ||||
| @ -836,17 +828,6 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi | ||||
| 	ctx.Data["Diff"] = diff | ||||
| 	ctx.Data["DiffNotAvailable"] = diffShortStat.NumFiles == 0 | ||||
| 
 | ||||
| 	baseCommit, err := ctx.Repo.GitRepo.GetCommit(startCommitID) | ||||
| 	if err != nil { | ||||
| 		ctx.ServerError("GetCommit", err) | ||||
| 		return | ||||
| 	} | ||||
| 	commit, err := gitRepo.GetCommit(endCommitID) | ||||
| 	if err != nil { | ||||
| 		ctx.ServerError("GetCommit", err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if ctx.IsSigned && ctx.Doer != nil { | ||||
| 		if ctx.Data["CanMarkConversation"], err = issues_model.CanMarkConversation(ctx, issue, ctx.Doer); err != nil { | ||||
| 			ctx.ServerError("CanMarkConversation", err) | ||||
| @ -854,7 +835,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	setCompareContext(ctx, baseCommit, commit, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) | ||||
| 	setCompareContext(ctx, beforeCommit, afterCommit, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) | ||||
| 
 | ||||
| 	assigneeUsers, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository) | ||||
| 	if err != nil { | ||||
| @ -901,7 +882,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi | ||||
| 	ctx.Data["CanBlockUser"] = func(blocker, blockee *user_model.User) bool { | ||||
| 		return user_service.CanBlockUser(ctx, ctx.Doer, blocker, blockee) | ||||
| 	} | ||||
| 	if !willShowSpecifiedCommit && !willShowSpecifiedCommitRange && pull.Flow == issues_model.PullRequestFlowGithub { | ||||
| 	if isShowAllCommits && pull.Flow == issues_model.PullRequestFlowGithub { | ||||
| 		if err := pull.LoadHeadRepo(ctx); err != nil { | ||||
| 			ctx.ServerError("LoadHeadRepo", err) | ||||
| 			return | ||||
| @ -930,19 +911,17 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi | ||||
| } | ||||
| 
 | ||||
| func ViewPullFilesForSingleCommit(ctx *context.Context) { | ||||
| 	viewPullFiles(ctx, "", ctx.PathParam("sha"), true, true) | ||||
| 	// it doesn't support showing files from mergebase to the special commit | ||||
| 	// otherwise it will be ambiguous | ||||
| 	viewPullFiles(ctx, "", ctx.PathParam("sha")) | ||||
| } | ||||
| 
 | ||||
| func ViewPullFilesForRange(ctx *context.Context) { | ||||
| 	viewPullFiles(ctx, ctx.PathParam("shaFrom"), ctx.PathParam("shaTo"), true, false) | ||||
| } | ||||
| 
 | ||||
| func ViewPullFilesStartingFromCommit(ctx *context.Context) { | ||||
| 	viewPullFiles(ctx, "", ctx.PathParam("sha"), true, false) | ||||
| 	viewPullFiles(ctx, ctx.PathParam("shaFrom"), ctx.PathParam("shaTo")) | ||||
| } | ||||
| 
 | ||||
| func ViewPullFilesForAllCommitsOfPr(ctx *context.Context) { | ||||
| 	viewPullFiles(ctx, "", "", false, false) | ||||
| 	viewPullFiles(ctx, "", "") | ||||
| } | ||||
| 
 | ||||
| // UpdatePullRequest merge PR's baseBranch into headBranch | ||||
|  | ||||
| @ -1531,7 +1531,7 @@ func registerWebRoutes(m *web.Router) { | ||||
| 			m.Group("/commits", func() { | ||||
| 				m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits) | ||||
| 				m.Get("/list", repo.GetPullCommits) | ||||
| 				m.Get("/{sha:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit) | ||||
| 				m.Get("/{sha:[a-f0-9]{7,64}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit) | ||||
| 			}) | ||||
| 			m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), repo.MergePullRequest) | ||||
| 			m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest) | ||||
| @ -1540,8 +1540,7 @@ func registerWebRoutes(m *web.Router) { | ||||
| 			m.Post("/cleanup", context.RepoMustNotBeArchived(), repo.CleanUpPullRequest) | ||||
| 			m.Group("/files", func() { | ||||
| 				m.Get("", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForAllCommitsOfPr) | ||||
| 				m.Get("/{sha:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit) | ||||
| 				m.Get("/{shaFrom:[a-f0-9]{7,40}}..{shaTo:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange) | ||||
| 				m.Get("/{shaFrom:[a-f0-9]{7,64}}..{shaTo:[a-f0-9]{7,64}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange) | ||||
| 				m.Group("/reviews", func() { | ||||
| 					m.Get("/new_comment", repo.RenderNewCodeCommentForm) | ||||
| 					m.Post("/comments", web.Bind(forms.CodeCommentForm{}), repo.SetShowOutdatedComments, repo.CreateCodeComment) | ||||
|  | ||||
| @ -35,7 +35,7 @@ | ||||
| 			{{template "repo/diff/whitespace_dropdown" .}} | ||||
| 			{{template "repo/diff/options_dropdown" .}} | ||||
| 			{{if .PageIsPullFiles}} | ||||
| 				<div id="diff-commit-select" data-issuelink="{{$.Issue.Link}}" data-queryparams="?style={{if $.IsSplitStyle}}split{{else}}unified{{end}}&whitespace={{$.WhitespaceBehavior}}&show-outdated={{$.ShowOutdatedComments}}" data-filter_changes_by_commit="{{ctx.Locale.Tr "repo.pulls.filter_changes_by_commit"}}"> | ||||
| 				<div id="diff-commit-select" data-merge-base="{{.MergeBase}}" data-issuelink="{{$.Issue.Link}}" data-queryparams="?style={{if $.IsSplitStyle}}split{{else}}unified{{end}}&whitespace={{$.WhitespaceBehavior}}&show-outdated={{$.ShowOutdatedComments}}" data-filter_changes_by_commit="{{ctx.Locale.Tr "repo.pulls.filter_changes_by_commit"}}"> | ||||
| 					{{/* the following will be replaced by vue component, but this avoids any loading artifacts till the vue component is initialized */}} | ||||
| 					<div class="ui jump dropdown tiny basic button custom"> | ||||
| 						{{svg "octicon-git-commit"}} | ||||
|  | ||||
| @ -25,10 +25,6 @@ func TestPullDiff_CommitRangePRDiff(t *testing.T) { | ||||
| 	doTestPRDiff(t, "/user2/commitsonpr/pulls/1/files/4ca8bcaf27e28504df7bf996819665986b01c847..23576dd018294e476c06e569b6b0f170d0558705", true, []string{"test2.txt", "test3.txt", "test4.txt"}) | ||||
| } | ||||
| 
 | ||||
| func TestPullDiff_StartingFromBaseToCommitPRDiff(t *testing.T) { | ||||
| 	doTestPRDiff(t, "/user2/commitsonpr/pulls/1/files/c5626fc9eff57eb1bb7b796b01d4d0f2f3f792a2", true, []string{"test1.txt", "test2.txt", "test3.txt"}) | ||||
| } | ||||
| 
 | ||||
| func doTestPRDiff(t *testing.T, prDiffURL string, reviewBtnDisabled bool, expectedFilenames []string) { | ||||
| 	defer tests.PrepareTestEnv(t)() | ||||
| 
 | ||||
|  | ||||
| @ -32,6 +32,7 @@ export default defineComponent({ | ||||
|       locale: { | ||||
|         filter_changes_by_commit: el.getAttribute('data-filter_changes_by_commit'), | ||||
|       } as Record<string, string>, | ||||
|       mergeBase: el.getAttribute('data-merge-base'), | ||||
|       commits: [] as Array<Commit>, | ||||
|       hoverActivated: false, | ||||
|       lastReviewCommitSha: '', | ||||
| @ -176,32 +177,38 @@ export default defineComponent({ | ||||
|       } | ||||
|     }, | ||||
|     /** | ||||
|      * When a commit is clicked with shift this enables the range | ||||
|      * selection. Second click (with shift) defines the end of the | ||||
|      * range. This opens the diff of this range | ||||
|      * Exception: first commit is the first commit of this PR. Then | ||||
|      * the diff from beginning of PR up to the second clicked commit is | ||||
|      * opened | ||||
|      * When a commit is clicked while holding Shift, it enables range selection. | ||||
|      * - The range selection is a half-open, half-closed range, meaning it excludes the start commit but includes the end commit. | ||||
|      * - The start of the commit range is always the previous commit of the first clicked commit. | ||||
|      * - If the first commit in the list is clicked, the mergeBase will be used as the start of the range instead. | ||||
|      * - The second Shift-click defines the end of the range. | ||||
|      * - Once both are selected, the diff view for the selected commit range will open. | ||||
|      */ | ||||
|     commitClickedShift(commit: Commit) { | ||||
|       this.hoverActivated = !this.hoverActivated; | ||||
|       commit.selected = true; | ||||
|       // Second click -> determine our range and open links accordingly | ||||
|       if (!this.hoverActivated) { | ||||
|         // since at least one commit is selected, we can determine the range | ||||
|         // find all selected commits and generate a link | ||||
|         if (this.commits[0].selected) { | ||||
|           // first commit is selected - generate a short url with only target sha | ||||
|           const lastCommitIdx = this.commits.findLastIndex((x) => x.selected); | ||||
|           if (lastCommitIdx === this.commits.length - 1) { | ||||
|             // user selected all commits - just show the normal diff page | ||||
|             window.location.assign(`${this.issueLink}/files${this.queryParams}`); | ||||
|           } else { | ||||
|             window.location.assign(`${this.issueLink}/files/${this.commits[lastCommitIdx].id}${this.queryParams}`); | ||||
|           } | ||||
|         const firstSelected = this.commits.findIndex((x) => x.selected); | ||||
|         const lastSelected = this.commits.findLastIndex((x) => x.selected); | ||||
|         let beforeCommitID: string; | ||||
|         if (firstSelected === 0) { | ||||
|           beforeCommitID = this.mergeBase; | ||||
|         } else { | ||||
|           const start = this.commits[this.commits.findIndex((x) => x.selected) - 1].id; | ||||
|           const end = this.commits.findLast((x) => x.selected).id; | ||||
|           window.location.assign(`${this.issueLink}/files/${start}..${end}${this.queryParams}`); | ||||
|           beforeCommitID = this.commits[firstSelected - 1].id; | ||||
|         } | ||||
|         const afterCommitID = this.commits[lastSelected].id; | ||||
| 
 | ||||
|         if (firstSelected === lastSelected) { | ||||
|           // if the start and end are the same, we show this single commit | ||||
|           window.location.assign(`${this.issueLink}/commits/${afterCommitID}${this.queryParams}`); | ||||
|         } else if (beforeCommitID === this.mergeBase && afterCommitID === this.commits.at(-1).id) { | ||||
|           // if the first commit is selected and the last commit is selected, we show all commits | ||||
|           window.location.assign(`${this.issueLink}/files${this.queryParams}`); | ||||
|         } else { | ||||
|           window.location.assign(`${this.issueLink}/files/${beforeCommitID}..${afterCommitID}${this.queryParams}`); | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user