Skip to content

Fix one performance/correctness regression in #6478 found on Rails repository. #6686

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 21, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 33 additions & 38 deletions modules/git/commit_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,6 @@ func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) (m
break
}
current := cIn.(*commitAndPaths)
currentID := current.commit.ID()

if seen[currentID] {
continue
}
seen[currentID] = true

// Load the parent commits for the one we are currently examining
numParents := current.commit.NumParents()
Expand All @@ -166,8 +160,7 @@ func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) (m
}

// Examine the current commit and set of interesting paths
numOfParentsWithPath := make([]int, len(current.paths))
pathChanged := make([]bool, len(current.paths))
pathUnchanged := make([]bool, len(current.paths))
parentHashes := make([]map[string]plumbing.Hash, len(parents))
for j, parent := range parents {
parentHashes[j], err = getFileHashes(parent, treePath, current.paths)
Expand All @@ -176,42 +169,32 @@ func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) (m
}

for i, path := range current.paths {
if parentHashes[j][path] != plumbing.ZeroHash {
numOfParentsWithPath[i]++
if parentHashes[j][path] != current.hashes[path] {
pathChanged[i] = true
}
if parentHashes[j][path] == current.hashes[path] {
pathUnchanged[i] = true
}
}
}

var remainingPaths []string
for i, path := range current.paths {
switch numOfParentsWithPath[i] {
case 0:
// The path didn't exist in any parent, so it must have been created by
// this commit. The results could already contain some newer change from
// different path, so don't override that.
if result[path] == nil {
result[path] = current.commit
}
case 1:
// The file is present on exactly one parent, so check if it was changed
// and save the revision if it did.
if pathChanged[i] {
if result[path] == nil {
result[path] = current.commit
}
} else {
// The results could already contain some newer change for the same path,
// so don't override that and bail out on the file early.
if result[path] == nil {
if pathUnchanged[i] {
// The path existed with the same hash in at least one parent so it could
// not have been changed in this commit directly.
remainingPaths = append(remainingPaths, path)
} else {
// There are few possible cases how can we get here:
// - The path didn't exist in any parent, so it must have been created by
// this commit.
// - The path did exist in the parent commit, but the hash of the file has
// changed.
// - We are looking at a merge commit and the hash of the file doesn't
// match any of the hashes being merged. This is more common for directories,
// but it can also happen if a file is changed through conflict resolution.
result[path] = current.commit
}
default:
// The file is present on more than one of the parent paths, so this is
// a merge. We have to examine all the parent trees to find out where
// the change occurred. pathChanged[i] would tell us that the file was
// changed during the merge, but it wouldn't tell us the relevant commit
// that introduced it.
remainingPaths = append(remainingPaths, path)
}
}

Expand All @@ -222,17 +205,29 @@ func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) (m
if seen[parent.ID()] {
continue
}
seen[parent.ID()] = true

// Combine remainingPath with paths available on the parent branch
// and make union of them
var remainingPathsForParent []string
var newRemainingPaths []string
for _, path := range remainingPaths {
if parentHashes[j][path] != plumbing.ZeroHash {
if parentHashes[j][path] == current.hashes[path] {
remainingPathsForParent = append(remainingPathsForParent, path)
} else {
newRemainingPaths = append(newRemainingPaths, path)
}
}

heap.Push(&commitAndPaths{parent, remainingPathsForParent, parentHashes[j]})
if remainingPathsForParent != nil {
heap.Push(&commitAndPaths{parent, remainingPathsForParent, parentHashes[j]})
}

if len(newRemainingPaths) == 0 {
break
} else {
remainingPaths = newRemainingPaths
}
}
}
}
Expand Down