refactor: GetDiffShortStat and fix panic caused by inconsistent "changed file number" (#39248)

This commit is contained in:
wxiaoguang
2026-09-06 05:20:42 +00:00
committed by GitHub
parent e5e7b2e76e
commit 3bd7ea4c9f
13 changed files with 143 additions and 146 deletions
+8
View File
@@ -330,3 +330,11 @@ func GetAffectedFiles(ctx context.Context, repo *Repository, branchName, oldComm
return affectedFiles, err return affectedFiles, err
} }
// GetReverseRawDiff dumps the reverse diff results of repository in given commit ID to io.Writer.
func GetReverseRawDiff(ctx context.Context, repo RepositoryFacade, commitID string, writer io.Writer) error {
return gitcmd.NewCommand("show", "--pretty=format:revert %H%n", "-R").
AddDynamicArguments(commitID).
WithStdoutCopy(writer).
WithRepo(repo).RunWithStderr(ctx)
}
-71
View File
@@ -1,71 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"context"
"fmt"
"io"
"regexp"
"strconv"
"gitea.dev/modules/git/gitcmd"
)
// GetDiffShortStatByCmdArgs counts number of changed files, number of additions and deletions
// TODO: it can be merged with another "GetDiffShortStat" in the future
func GetDiffShortStatByCmdArgs(ctx context.Context, repo RepositoryFacade, trustedArgs gitcmd.TrustedCmdArgs, dynamicArgs ...string) (numFiles, totalAdditions, totalDeletions int, err error) {
// Now if we call:
// $ git diff --shortstat 1ebb35b98889ff77299f24d82da426b434b0cca0...788b8b1440462d477f45b0088875
// we get:
// " 9902 files changed, 2034198 insertions(+), 298800 deletions(-)\n"
cmd := gitcmd.NewCommand("diff", "--shortstat").AddArguments(trustedArgs...).AddDynamicArguments(dynamicArgs...)
stdout, _, err := cmd.WithRepo(repo).RunStdString(ctx)
if err != nil {
return 0, 0, 0, err
}
return parseDiffStat(stdout)
}
var shortStatFormat = regexp.MustCompile(
`\s*(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?`)
func parseDiffStat(stdout string) (numFiles, totalAdditions, totalDeletions int, err error) {
if len(stdout) == 0 || stdout == "\n" {
return 0, 0, 0, nil
}
groups := shortStatFormat.FindStringSubmatch(stdout)
if len(groups) != 4 {
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s groups: %s", stdout, groups)
}
numFiles, err = strconv.Atoi(groups[1])
if err != nil {
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumFiles %w", stdout, err)
}
if len(groups[2]) != 0 {
totalAdditions, err = strconv.Atoi(groups[2])
if err != nil {
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumAdditions %w", stdout, err)
}
}
if len(groups[3]) != 0 {
totalDeletions, err = strconv.Atoi(groups[3])
if err != nil {
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumDeletions %w", stdout, err)
}
}
return numFiles, totalAdditions, totalDeletions, err
}
// GetReverseRawDiff dumps the reverse diff results of repository in given commit ID to io.Writer.
func GetReverseRawDiff(ctx context.Context, repo RepositoryFacade, commitID string, writer io.Writer) error {
return gitcmd.NewCommand("show", "--pretty=format:revert %H%n", "-R").
AddDynamicArguments(commitID).
WithStdoutCopy(writer).
WithRepo(repo).RunWithStderr(ctx)
}
+2 -1
View File
@@ -35,7 +35,8 @@ type FastImportCommit struct {
func ForceFastImportWithInit(ctx context.Context, repoLocalPath string, commits []FastImportCommit, initOpts ...FastImportInit) (RepositoryFacade, error) { func ForceFastImportWithInit(ctx context.Context, repoLocalPath string, commits []FastImportCommit, initOpts ...FastImportInit) (RepositoryFacade, error) {
repo := gitrepo.RepositoryUnmanaged(repoLocalPath) repo := gitrepo.RepositoryUnmanaged(repoLocalPath)
initOpt := util.OptionalArg(initOpts, FastImportInit{Bare: true}) initOpt := util.OptionalArg(initOpts, FastImportInit{Bare: true})
if exist, _ := IsRepositoryExist(ctx, repo); !exist { dirEntries, err := os.ReadDir(repoLocalPath)
if os.IsNotExist(err) || (err == nil && len(dirEntries) == 0) {
_ = os.MkdirAll(repoLocalPath, 0o755) _ = os.MkdirAll(repoLocalPath, 0o755)
err := InitRepositoryLocal(ctx, repoLocalPath, initOpt.Bare, util.IfZero(initOpt.ObjectFormat, "sha1")) err := InitRepositoryLocal(ctx, repoLocalPath, initOpt.Bare, util.IfZero(initOpt.ObjectFormat, "sha1"))
if err != nil { if err != nil {
+2 -4
View File
@@ -37,10 +37,8 @@ type Sha1ObjectFormatImpl struct{}
var ( var (
emptySha1ObjectID = &Sha1Hash{} emptySha1ObjectID = &Sha1Hash{}
emptySha1Tree = &Sha1Hash{ // emptySha1Tree: 4b825dc642cb6eb9a060e54bf8d69288fbee4904
0x4b, 0x82, 0x5d, 0xc6, 0x42, 0xcb, 0x6e, 0xb9, 0xa0, 0x60, emptySha1Tree = &Sha1Hash{0x4b, 0x82, 0x5d, 0xc6, 0x42, 0xcb, 0x6e, 0xb9, 0xa0, 0x60, 0xe5, 0x4b, 0xf8, 0xd6, 0x92, 0x88, 0xfb, 0xee, 0x49, 0x04}
0xe5, 0x4b, 0xf8, 0xd6, 0x92, 0x88, 0xfb, 0xee, 0x49, 0x04,
}
) )
func (Sha1ObjectFormatImpl) Name() string { return "sha1" } func (Sha1ObjectFormatImpl) Name() string { return "sha1" }
+13 -12
View File
@@ -1580,39 +1580,40 @@ func GetPullRequestFiles(ctx *context.APIContext) {
maxLines := setting.Git.MaxGitDiffLines maxLines := setting.Git.MaxGitDiffLines
// FIXME: If there are too many files in the repo, may cause some unpredictable issues. // FIXME: If there are too many files in the repo, may cause some unpredictable issues.
diffCommonOptions := gitdiff.DiffCommonOptions{
BeforeCommitID: startCommitID,
AfterCommitID: endCommitID,
WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.FormString("whitespace")),
}
diff, err := gitdiff.GetDiffForAPI(ctx, baseGitRepo, diff, err := gitdiff.GetDiffForAPI(ctx, baseGitRepo,
&gitdiff.DiffOptions{ &gitdiff.DiffOptions{
BeforeCommitID: startCommitID, DiffCommonOptions: diffCommonOptions,
AfterCommitID: endCommitID, SkipTo: ctx.FormString("skip-to"),
SkipTo: ctx.FormString("skip-to"), MaxLines: maxLines,
MaxLines: maxLines, MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters,
MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters, MaxFiles: -1, // GetDiff() will return all files
MaxFiles: -1, // GetDiff() will return all files
WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.FormString("whitespace")),
}) })
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
return return
} }
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, baseGitRepo, startCommitID, endCommitID) diffShortStat, err := gitdiff.GetDiffShortStat(ctx, baseGitRepo, &diffCommonOptions)
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
return return
} }
listOptions := utils.GetListOptions(ctx)
listOptions := utils.GetListOptions(ctx)
totalNumberOfFiles := diffShortStat.NumFiles totalNumberOfFiles := diffShortStat.NumFiles
totalNumberOfPages := int(math.Ceil(float64(totalNumberOfFiles) / float64(listOptions.PageSize))) totalNumberOfPages := int(math.Ceil(float64(totalNumberOfFiles) / float64(listOptions.PageSize)))
start, limit := listOptions.GetSkipTake() start, limit := listOptions.GetSkipTake()
limit = min(limit, totalNumberOfFiles-start) limit = min(limit, totalNumberOfFiles-start)
limit = max(limit, 0) limit = max(limit, 0)
apiFiles := make([]*api.ChangedFile, 0, limit) apiFiles := make([]*api.ChangedFile, 0, limit)
for i := start; i < start+limit; i++ { for i := start; i < start+limit && i < len(diff.Files); i++ {
// refs/pull/1/head stores the HEAD commit ID, allowing all related commits to be found in the base repository. // refs/pull/1/head stores the HEAD commit ID, allowing all related commits to be found in the base repository.
// The head repository might have been deleted, so we should not rely on it here. // The head repository might have been deleted, so we should not rely on it here.
apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.BaseRepo, endCommitID)) apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.BaseRepo, endCommitID))
+10 -7
View File
@@ -318,19 +318,22 @@ func Diff(ctx *context.Context) {
maxLines, maxFiles = -1, -1 maxLines, maxFiles = -1, -1
} }
diff, err := gitdiff.GetDiffForRender(ctx, ctx.Repo.RepoLink, gitRepo, &gitdiff.DiffOptions{ diffCommonOptions := gitdiff.DiffCommonOptions{
AfterCommitID: commitID,
SkipTo: ctx.FormString("skip-to"),
MaxLines: maxLines,
MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters,
MaxFiles: maxFiles,
WhitespaceBehavior: gitdiff.GetWhitespaceFlag(GetWhitespaceBehavior(ctx)), WhitespaceBehavior: gitdiff.GetWhitespaceFlag(GetWhitespaceBehavior(ctx)),
AfterCommitID: commitID,
}
diff, err := gitdiff.GetDiffForRender(ctx, ctx.Repo.RepoLink, gitRepo, &gitdiff.DiffOptions{
DiffCommonOptions: diffCommonOptions,
SkipTo: ctx.FormString("skip-to"),
MaxLines: maxLines,
MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters,
MaxFiles: maxFiles,
}, files...) }, files...)
if err != nil { if err != nil {
ctx.NotFound(err) ctx.NotFound(err)
return return
} }
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, gitRepo, "", commitID) diffShortStat, err := gitdiff.GetDiffShortStat(ctx, gitRepo, &diffCommonOptions)
if err != nil { if err != nil {
ctx.ServerError("GetDiffShortStat", err) ctx.ServerError("GetDiffShortStat", err)
return return
+11 -10
View File
@@ -409,23 +409,24 @@ func (cpi *comparePageInfoType) prepareCompareDiff(ctx *context.Context, whitesp
} }
fileOnly := ctx.FormBool("file-only") fileOnly := ctx.FormBool("file-only")
diffCommonOptions := gitdiff.DiffCommonOptions{
BeforeCommitID: beforeCommitID,
AfterCommitID: headCommitID,
WhitespaceBehavior: whitespaceBehavior,
}
diff, err := gitdiff.GetDiffForRender(ctx, ci.HeadRepo.Link(), ci.HeadGitRepo, diff, err := gitdiff.GetDiffForRender(ctx, ci.HeadRepo.Link(), ci.HeadGitRepo,
&gitdiff.DiffOptions{ &gitdiff.DiffOptions{
BeforeCommitID: beforeCommitID, DiffCommonOptions: diffCommonOptions,
AfterCommitID: headCommitID, SkipTo: ctx.FormString("skip-to"),
SkipTo: ctx.FormString("skip-to"), MaxLines: maxLines,
MaxLines: maxLines, MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters,
MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters, MaxFiles: maxFiles,
MaxFiles: maxFiles,
WhitespaceBehavior: whitespaceBehavior,
DirectComparison: ci.DirectComparison(),
}, ctx.FormStrings("files")...) }, ctx.FormStrings("files")...)
if err != nil { if err != nil {
ctx.ServerError("GetDiff", err) ctx.ServerError("GetDiff", err)
return return
} }
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ci.HeadGitRepo, beforeCommitID, headCommitID) diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ci.HeadGitRepo, &diffCommonOptions)
if err != nil { if err != nil {
ctx.ServerError("GetDiffShortStat", err) ctx.ServerError("GetDiffShortStat", err)
return return
+10 -7
View File
@@ -203,7 +203,7 @@ func GetPullDiffStats(ctx *context.Context) {
log.Error("Failed to GetRefCommitID: %v, repo: %v", err, ctx.Repo.Repository.FullName()) log.Error("Failed to GetRefCommitID: %v, repo: %v", err, ctx.Repo.Repository.FullName())
return return
} }
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ctx.Repo.GitRepo, mergeBaseCommitID, headCommitID) diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ctx.Repo.GitRepo, &gitdiff.DiffCommonOptions{BeforeCommitID: mergeBaseCommitID, AfterCommitID: headCommitID})
if err != nil { if err != nil {
log.Error("Failed to GetDiffShortStat: %v, repo: %v", err, ctx.Repo.Repository.FullName()) log.Error("Failed to GetDiffShortStat: %v, repo: %v", err, ctx.Repo.Repository.FullName())
return return
@@ -770,15 +770,18 @@ func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) {
maxLines, maxFiles = -1, -1 maxLines, maxFiles = -1, -1
} }
diffOptions := &gitdiff.DiffOptions{ diffCommonOptions := gitdiff.DiffCommonOptions{
BeforeCommitID: beforeCommitID, BeforeCommitID: beforeCommitID,
AfterCommitID: afterCommitID, AfterCommitID: afterCommitID,
SkipTo: ctx.FormString("skip-to"),
MaxLines: maxLines,
MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters,
MaxFiles: maxFiles,
WhitespaceBehavior: gitdiff.GetWhitespaceFlag(GetWhitespaceBehavior(ctx)), WhitespaceBehavior: gitdiff.GetWhitespaceFlag(GetWhitespaceBehavior(ctx)),
} }
diffOptions := &gitdiff.DiffOptions{
DiffCommonOptions: diffCommonOptions,
SkipTo: ctx.FormString("skip-to"),
MaxLines: maxLines,
MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters,
MaxFiles: maxFiles,
}
diff, err := gitdiff.GetDiffForRender(ctx, ctx.Repo.RepoLink, gitRepo, diffOptions, files...) diff, err := gitdiff.GetDiffForRender(ctx, ctx.Repo.RepoLink, gitRepo, diffOptions, files...)
if err != nil { if err != nil {
@@ -803,7 +806,7 @@ func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) {
} }
} }
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ctx.Repo.GitRepo, beforeCommitID, afterCommitID) diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ctx.Repo.GitRepo, &diffCommonOptions)
if err != nil { if err != nil {
ctx.ServerError("GetDiffShortStat", err) ctx.ServerError("GetDiffShortStat", err)
return return
+1 -1
View File
@@ -209,7 +209,7 @@ func ToCommit(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Rep
// Get diff stats for commit // Get diff stats for commit
if opts.Stat { if opts.Stat {
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, gitRepo, "", commit.ID.String()) diffShortStat, err := gitdiff.GetDiffShortStat(ctx, gitRepo, &gitdiff.DiffCommonOptions{AfterCommitID: commit.ID.String()})
if err != nil { if err != nil {
return nil, err return nil, err
} }
+1 -1
View File
@@ -229,7 +229,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
// Calculate diff // Calculate diff
startCommitID = pr.MergeBase startCommitID = pr.MergeBase
diffShortStats, err := gitdiff.GetDiffShortStat(ctx, gitRepo, startCommitID, endCommitID) diffShortStats, err := gitdiff.GetDiffShortStat(ctx, gitRepo, &gitdiff.DiffCommonOptions{BeforeCommitID: startCommitID, AfterCommitID: endCommitID})
if err != nil { if err != nil {
log.Error("GetDiffShortStat: %v", err) log.Error("GetDiffShortStat: %v", err)
} else { } else {
+49 -29
View File
@@ -15,6 +15,7 @@ import (
"net/url" "net/url"
"path" "path"
"sort" "sort"
"strconv"
"strings" "strings"
"time" "time"
@@ -1293,36 +1294,45 @@ func readFileName(rd *strings.Reader) (string, bool) {
return name[2:], ambiguity return name[2:], ambiguity
} }
// DiffOptions represents the options for a DiffRange type DiffCommonOptions struct {
type DiffOptions struct {
BeforeCommitID string BeforeCommitID string
AfterCommitID string AfterCommitID string
SkipTo string
MaxLines int
MaxLineCharacters int
MaxFiles int
WhitespaceBehavior gitcmd.TrustedCmdArgs WhitespaceBehavior gitcmd.TrustedCmdArgs
DirectComparison bool
} }
func guessBeforeCommitForDiff(ctx context.Context, gitRepo *git.Repository, beforeCommitID string, afterCommit *git.Commit) (actualBeforeCommit *git.Commit, actualBeforeCommitID git.ObjectID, err error) { type DiffOptions struct {
commitObjectFormat := afterCommit.ID.Type() DiffCommonOptions
isBeforeCommitIDEmpty := beforeCommitID == "" || beforeCommitID == commitObjectFormat.EmptyObjectID().String() SkipTo string
MaxLines int
MaxLineCharacters int
MaxFiles int
}
// prepareDiffCommits prepares the before and after commits for a diff operation based on the provided options.
// The "before commit" can be nil (the empty tree ID is used) if there is no "before commit" can be determined.
func prepareDiffCommits(ctx context.Context, gitRepo *git.Repository, opts *DiffCommonOptions) (actualBeforeCommit *git.Commit, actualBeforeCommitID git.ObjectID, afterCommit *git.Commit, err error) {
afterCommit, err = gitRepo.GetCommit(ctx, opts.AfterCommitID)
if err != nil {
return nil, nil, nil, err
}
commitObjectFormat := afterCommit.ID.Type()
isBeforeCommitIDEmpty := opts.BeforeCommitID == "" || opts.BeforeCommitID == commitObjectFormat.EmptyObjectID().String()
if isBeforeCommitIDEmpty && afterCommit.ParentCount() == 0 { if isBeforeCommitIDEmpty && afterCommit.ParentCount() == 0 {
// "git diff 4b825dc642cb6eb9a060e54bf8d69288fbee4904 after-commit" can work with tree ID as before commit ID
actualBeforeCommitID = commitObjectFormat.EmptyTree() actualBeforeCommitID = commitObjectFormat.EmptyTree()
} else { } else {
if isBeforeCommitIDEmpty { if isBeforeCommitIDEmpty {
actualBeforeCommit, err = afterCommit.Parent(ctx, gitRepo, 0) actualBeforeCommit, err = afterCommit.Parent(ctx, gitRepo, 0)
} else { } else {
actualBeforeCommit, err = gitRepo.GetCommit(ctx, beforeCommitID) actualBeforeCommit, err = gitRepo.GetCommit(ctx, opts.BeforeCommitID)
} }
if err != nil { if err != nil {
return nil, nil, err return nil, nil, nil, err
} }
actualBeforeCommitID = actualBeforeCommit.ID actualBeforeCommitID = actualBeforeCommit.ID
} }
return actualBeforeCommit, actualBeforeCommitID, nil return actualBeforeCommit, actualBeforeCommitID, afterCommit, nil
} }
// getDiffBasic builds a Diff between two commits of a repository. // getDiffBasic builds a Diff between two commits of a repository.
@@ -1330,12 +1340,7 @@ func guessBeforeCommitForDiff(ctx context.Context, gitRepo *git.Repository, befo
// The whitespaceBehavior is either an empty string or a git flag // The whitespaceBehavior is either an empty string or a git flag
// Returned beforeCommit could be nil if the afterCommit doesn't have parent commit // Returned beforeCommit could be nil if the afterCommit doesn't have parent commit
func getDiffBasic(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, files ...string) (_ *Diff, beforeCommit, afterCommit *git.Commit, err error) { func getDiffBasic(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, files ...string) (_ *Diff, beforeCommit, afterCommit *git.Commit, err error) {
afterCommit, err = gitRepo.GetCommit(ctx, opts.AfterCommitID) beforeCommit, beforeCommitID, afterCommit, err := prepareDiffCommits(ctx, gitRepo, &opts.DiffCommonOptions)
if err != nil {
return nil, nil, nil, err
}
beforeCommit, beforeCommitID, err := guessBeforeCommitForDiff(ctx, gitRepo, opts.BeforeCommitID, afterCommit)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
@@ -1494,26 +1499,41 @@ func highlightCodeLines(name, lang string, sections []*DiffSection, isLeft bool,
} }
type DiffShortStat struct { type DiffShortStat struct {
NumFiles, TotalAddition, TotalDeletion int NumFiles, TotalAddition, TotalDeletion int // these fields are used in templates directly
} }
func GetDiffShortStat(ctx context.Context, gitRepo *git.Repository, beforeCommitID, afterCommitID string) (*DiffShortStat, error) { func GetDiffShortStat(ctx context.Context, gitRepo *git.Repository, opts *DiffCommonOptions) (*DiffShortStat, error) {
afterCommit, err := gitRepo.GetCommit(ctx, afterCommitID) _, actualBeforeCommitID, afterCommit, err := prepareDiffCommits(ctx, gitRepo, opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, actualBeforeCommitID, err := guessBeforeCommitForDiff(ctx, gitRepo, beforeCommitID, afterCommit) stat := &DiffShortStat{}
cmd := gitcmd.NewCommand("diff", "--shortstat").
AddArguments(opts.WhitespaceBehavior...).
AddOptionFormat("--find-renames=%s", setting.Git.DiffRenameSimilarityThreshold).
AddDynamicArguments(actualBeforeCommitID.String(), afterCommit.ID.String())
// output: " 9902 files changed, 2034198 insertions(+), 298800 deletions(-)\n"
stdout, _, err := cmd.WithRepo(gitRepo).RunStdString(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
stdout = strings.TrimSpace(stdout)
diff := &DiffShortStat{} for field := range strings.SplitSeq(stdout, ",") {
diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStatByCmdArgs(ctx, gitRepo, nil, actualBeforeCommitID.String(), afterCommitID) field = strings.TrimSpace(field)
if err != nil { num, suffix, ok := strings.Cut(field, " ")
return nil, err switch {
case strings.Contains(suffix, "file") && strings.Contains(suffix, "change"):
stat.NumFiles, _ = strconv.Atoi(num)
case strings.Contains(suffix, "insertion"):
stat.TotalAddition, _ = strconv.Atoi(num)
case strings.Contains(suffix, "deletion"):
stat.TotalDeletion, _ = strconv.Atoi(num)
case ok:
setting.PanicInDevOrTesting("unexpected diff shortstat output: %s", stdout)
}
} }
return diff, nil return stat, nil
} }
// SyncUserSpecificDiff inserts user-specific data such as which files the user has already viewed on the given diff // SyncUserSpecificDiff inserts user-specific data such as which files the user has already viewed on the given diff
+34
View File
@@ -1308,3 +1308,37 @@ D test10.txt`
assert.Equal(t, thirdReviewUpdatedFiles, thirdReview.UpdatedFiles) assert.Equal(t, thirdReviewUpdatedFiles, thirdReview.UpdatedFiles)
assert.Equal(t, 1, thirdReview.GetViewedFileCount()) assert.Equal(t, 1, thirdReview.GetViewedFileCount())
} }
func TestGetDiffShortStatWithOptions(t *testing.T) {
repo, err := git.ForceFastImportWithInit(t.Context(), t.TempDir(), []git.FastImportCommit{
{Ref: "refs/heads/base", Files: []git.FastImportFile{
{Path: "real.txt", Content: "a1\na2\n"},
{Path: "whitespace.txt", Content: "b\n"},
}},
{Ref: "refs/heads/head", Files: []git.FastImportFile{
{Path: "real.txt", Content: "A1\nA2\nA3\n"},
{Path: "whitespace.txt", Content: "b \n"},
}},
})
require.NoError(t, err)
gitRepo, err := git.OpenRepository(t.Context(), repo)
require.NoError(t, err)
defer gitRepo.Close()
t.Run("NoParent", func(t *testing.T) {
stat, err := GetDiffShortStat(t.Context(), gitRepo, &DiffCommonOptions{AfterCommitID: "refs/heads/head"})
require.NoError(t, err)
assert.Equal(t, &DiffShortStat{NumFiles: 2, TotalAddition: 4, TotalDeletion: 0}, stat)
})
diffOptions := DiffCommonOptions{BeforeCommitID: "refs/heads/base", AfterCommitID: "refs/heads/head"}
t.Run("NormalDiff", func(t *testing.T) {
stat, err := GetDiffShortStat(t.Context(), gitRepo, &diffOptions)
require.NoError(t, err)
assert.Equal(t, &DiffShortStat{NumFiles: 2, TotalAddition: 4, TotalDeletion: 3}, stat)
})
t.Run("IgnoreSpace", func(t *testing.T) {
diffOptions.WhitespaceBehavior = GetWhitespaceFlag("ignore-all")
stat, err := GetDiffShortStat(t.Context(), gitRepo, &diffOptions)
require.NoError(t, err)
assert.Equal(t, &DiffShortStat{NumFiles: 1, TotalAddition: 3, TotalDeletion: 2}, stat)
})
}
+2 -3
View File
@@ -4,7 +4,6 @@
package pull package pull
import ( import (
"path/filepath"
"testing" "testing"
"gitea.dev/modules/git" "gitea.dev/modules/git"
@@ -97,7 +96,7 @@ func TestAddCommitMessageTailer(t *testing.T) {
func TestResolveMergeMessageTemplate(t *testing.T) { func TestResolveMergeMessageTemplate(t *testing.T) {
t.Run("NoDefault", func(t *testing.T) { t.Run("NoDefault", func(t *testing.T) {
repo, err := git.ForceFastImportWithInit(t.Context(), filepath.Join(t.TempDir(), "test-repo"), []git.FastImportCommit{ repo, err := git.ForceFastImportWithInit(t.Context(), t.TempDir(), []git.FastImportCommit{
{Ref: "refs/heads/master", Files: []git.FastImportFile{ {Ref: "refs/heads/master", Files: []git.FastImportFile{
{Path: ".gitea/default_merge_message/REBASE_TEMPLATE.md", Content: "rebase template"}, {Path: ".gitea/default_merge_message/REBASE_TEMPLATE.md", Content: "rebase template"},
}}, }},
@@ -117,7 +116,7 @@ func TestResolveMergeMessageTemplate(t *testing.T) {
assert.Equal(t, "rebase template", tmpl) assert.Equal(t, "rebase template", tmpl)
}) })
t.Run("WithDefault", func(t *testing.T) { t.Run("WithDefault", func(t *testing.T) {
repo, err := git.ForceFastImportWithInit(t.Context(), filepath.Join(t.TempDir(), "test-repo"), []git.FastImportCommit{ repo, err := git.ForceFastImportWithInit(t.Context(), t.TempDir(), []git.FastImportCommit{
{Ref: "refs/heads/master", Files: []git.FastImportFile{ {Ref: "refs/heads/master", Files: []git.FastImportFile{
{Path: ".gitea/default_merge_message/DEFAULT_TEMPLATE.md", Content: "default template"}, {Path: ".gitea/default_merge_message/DEFAULT_TEMPLATE.md", Content: "default template"},
{Path: ".gitea/default_merge_message/REBASE_TEMPLATE.md", Content: "rebase template"}, {Path: ".gitea/default_merge_message/REBASE_TEMPLATE.md", Content: "rebase template"},