* Fix flaw in the commit history lookup that caused unnecessary traversal when the repository contains a lot of merge commits. Also return the merge commit as the changed one if the file or directory was changed as part of the merge, eg. through conflict resolution. Signed-off-by: Filip Navara <filip.navara@gmail.com> * Perform history simplification. If a file is present on multiple parents in a merge commit follow only the first parent.
This commit is contained in:
		
							parent
							
								
									04ff3dd510
								
							
						
					
					
						commit
						b83114f140
					
				
					 1 changed files with 33 additions and 38 deletions
				
			
		|  | @ -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() | ||||
|  | @ -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) | ||||
|  | @ -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 { | ||||
| 			// 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 | ||||
| 				} | ||||
| 			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 { | ||||
| 					remainingPaths = append(remainingPaths, path) | ||||
| 				} | ||||
| 			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) | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
|  | @ -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 | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue