History

improve performance of diffs (#32393)

This has two major changes that significantly reduce the amount of work done for large diffs: * Kill a running git process when reaching the maximum number of files in a diff, preventing it from processing the entire diff. * When loading a diff with the URL param `file-only=true`, skip loading stats. This speeds up loading both hidden files of a diff and sections of a diff when clicking the "Show More" button. A couple of minor things from profiling are also included: * Reuse existing repo in `PrepareViewPullInfo` if head and base are the same. The performance impact is going to depend heavily on the individual diff and the hardware it runs on, but when testing locally on a diff changing 100k+ lines over hundreds of files, I'm seeing a roughly 75% reduction in time to load the result of "Show More" ---------

Rowan Bohde committed 2 years ago

Commit befafe91 parent 748ae104 files · +34 −37Browse files

Showing the changes to services/gitdiff/gitdiff.go (1 of 4 files). Show all

Unified · Split

services/gitdiff/gitdiff.go+26−34
@@ -379,18 +379,11 @@ func (diffFile *DiffFile) GetType() int { · 378 unmodified lines
379379 }
380380
381381 // GetTailSection creates a fake DiffLineSection if the last section is not the end of the file
382−func (diffFile *DiffFile) GetTailSection(gitRepo *git.Repository, leftCommitID, rightCommitID string) *DiffSection {
382+func (diffFile *DiffFile) GetTailSection(gitRepo *git.Repository, leftCommit, rightCommit *git.Commit) *DiffSection {
383383 if len(diffFile.Sections) == 0 || diffFile.Type != DiffFileChange || diffFile.IsBin || diffFile.IsLFSFile {
384384 return nil
385385 }
386− leftCommit, err := gitRepo.GetCommit(leftCommitID)
387− if err != nil {
388− return nil
389− }
390− rightCommit, err := gitRepo.GetCommit(rightCommitID)
391− if err != nil {
392− return nil
393− }
386+
394387 lastSection := diffFile.Sections[len(diffFile.Sections)-1]
395388 lastLine := lastSection.Lines[len(lastSection.Lines)-1]
396389 leftLineCount := getCommitFileLineCount(leftCommit, diffFile.Name)
@@ -532,11 +525,6 @@ parsingLoop: · 135 unmodified lines
532525 lastFile := createDiffFile(diff, line)
533526 diff.End = lastFile.Name
534527 diff.IsIncomplete = true
535− _, err := io.Copy(io.Discard, reader)
536− if err != nil {
537− // By the definition of io.Copy this never returns io.EOF
538− return diff, fmt.Errorf("error during io.Copy: %w", err)
539− }
540528 break parsingLoop
541529 }
542530
@@ -1097,6 +1085,7 @@ type DiffOptions struct { · 554 unmodified lines
10971085 MaxFiles int
10981086 WhitespaceBehavior git.TrustedCmdArgs
10991087 DirectComparison bool
1088+ FileOnly bool
11001089 }
11011090
11021091 // GetDiff builds a Diff between two commits of a repository.
@@ -1105,12 +1094,16 @@ type DiffOptions struct { · 2 unmodified lines
11051094 func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, files ...string) (*Diff, error) {
11061095 repoPath := gitRepo.Path
11071096
1097+ var beforeCommit *git.Commit
11081098 commit, err := gitRepo.GetCommit(opts.AfterCommitID)
11091099 if err != nil {
11101100 return nil, err
11111101 }
11121102
1113− cmdDiff := git.NewCommand(gitRepo.Ctx)
1103+ cmdCtx, cmdCancel := context.WithCancel(ctx)
1104+ defer cmdCancel()
1105+
1106+ cmdDiff := git.NewCommand(cmdCtx)
11141107 objectFormat, err := gitRepo.GetObjectFormat()
11151108 if err != nil {
11161109 return nil, err
@@ -1132,6 +1125,12 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi · 15 unmodified lines
11321125 AddArguments(opts.WhitespaceBehavior...).
11331126 AddDynamicArguments(actualBeforeCommitID, opts.AfterCommitID)
11341127 opts.BeforeCommitID = actualBeforeCommitID
1128+
1129+ var err error
1130+ beforeCommit, err = gitRepo.GetCommit(opts.BeforeCommitID)
1131+ if err != nil {
1132+ return nil, err
1133+ }
11351134 }
11361135
11371136 // In git 2.31, git diff learned --skip-to which we can use to shortcut skip to file
@@ -1166,7 +1165,9 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi · 28 unmodified lines
11661165 _ = writer.Close()
11671166 }()
11681167
1169− diff, err := ParsePatch(ctx, opts.MaxLines, opts.MaxLineCharacters, opts.MaxFiles, reader, parsePatchSkipToFile)
1168+ diff, err := ParsePatch(cmdCtx, opts.MaxLines, opts.MaxLineCharacters, opts.MaxFiles, reader, parsePatchSkipToFile)
1169+ // Ensure the git process is killed if it didn't exit already
1170+ cmdCancel()
11701171 if err != nil {
11711172 return nil, fmt.Errorf("unable to ParsePatch: %w", err)
11721173 }
@@ -1207,37 +1208,28 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi · 34 unmodified lines
12071208 diffFile.IsGenerated = analyze.IsGenerated(diffFile.Name)
12081209 }
12091210
1210− tailSection := diffFile.GetTailSection(gitRepo, opts.BeforeCommitID, opts.AfterCommitID)
1211+ tailSection := diffFile.GetTailSection(gitRepo, beforeCommit, commit)
12111212 if tailSection != nil {
12121213 diffFile.Sections = append(diffFile.Sections, tailSection)
12131214 }
12141215 }
12151216
1216− separator := "..."
1217− if opts.DirectComparison {
1218− separator = ".."
1217+ if opts.FileOnly {
1218+ return diff, nil
12191219 }
12201220
1221− diffPaths := []string{opts.BeforeCommitID + separator + opts.AfterCommitID}
1222− if len(opts.BeforeCommitID) == 0 || opts.BeforeCommitID == objectFormat.EmptyObjectID().String() {
1223− diffPaths = []string{objectFormat.EmptyTree().String(), opts.AfterCommitID}
1224− }
1225− diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...)
1226− if err != nil && strings.Contains(err.Error(), "no merge base") {
1227− // git >= 2.28 now returns an error if base and head have become unrelated.
1228− // previously it would return the results of git diff --shortstat base head so let's try that...
1229− diffPaths = []string{opts.BeforeCommitID, opts.AfterCommitID}
1230− diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...)
1231− }
1221+ stats, err := GetPullDiffStats(gitRepo, opts)
12321222 if err != nil {
12331223 return nil, err
12341224 }
12351225
1226+ diff.NumFiles, diff.TotalAddition, diff.TotalDeletion = stats.NumFiles, stats.TotalAddition, stats.TotalDeletion
1227+
12361228 return diff, nil
12371229 }
12381230
12391231 type PullDiffStats struct {
1240− TotalAddition, TotalDeletion int
1232+ NumFiles, TotalAddition, TotalDeletion int
12411233 }
12421234
12431235 // GetPullDiffStats
@@ -1261,12 +1253,12 @@ func GetPullDiffStats(gitRepo *git.Repository, opts *DiffOptions) (*PullDiffStat · 17 unmodified lines
12611253 diffPaths = []string{objectFormat.EmptyTree().String(), opts.AfterCommitID}
12621254 }
12631255
1264− _, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...)
1256+ diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...)
12651257 if err != nil && strings.Contains(err.Error(), "no merge base") {
12661258 // git >= 2.28 now returns an error if base and head have become unrelated.
12671259 // previously it would return the results of git diff --shortstat base head so let's try that...
12681260 diffPaths = []string{opts.BeforeCommitID, opts.AfterCommitID}
1269− _, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...)
1261+ diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...)
12701262 }
12711263 if err != nil {
12721264 return nil, err

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t