#!/usr/bin/env python # Copyright 2014 the V8 project authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # for py2/py3 compatibility from __future__ import print_function import argparse import subprocess import sys def GetArgs(): parser = argparse.ArgumentParser( description="Finds a commit that a given patch can be applied to. " "Does not actually apply the patch or modify your checkout " "in any way.") parser.add_argument("patch_file", help="Patch file to match") parser.add_argument( "--branch", "-b", default="origin/master", type=str, help="Git tree-ish where to start searching for commits, " "default: %(default)s") parser.add_argument( "--limit", "-l", default=500, type=int, help="Maximum number of commits to search, default: %(default)s") parser.add_argument( "--verbose", "-v", default=False, action="store_true", help="Print verbose output for your entertainment") return parser.parse_args() def FindFilesInPatch(patch_file): files = {} next_file = "" with open(patch_file) as patch: for line in patch: if line.startswith("diff --git "): # diff --git a/src/objects.cc b/src/objects.cc words = line.split() assert words[2].startswith("a/") and len(words[2]) > 2 next_file = words[2][2:] elif line.startswith("index "): # index add3e61..d1bbf6a 100644 hashes = line.split()[1] old_hash = hashes.split("..")[0] if old_hash.startswith("0000000"): continue # Ignore new files. files[next_file] = old_hash return files def GetGitCommitHash(treeish): cmd = ["git", "log", "-1", "--format=%H", treeish] return subprocess.check_output(cmd).strip() def CountMatchingFiles(commit, files): matched_files = 0 # Calling out to git once and parsing the result Python-side is faster # than calling 'git ls-tree' for every file. cmd = ["git", "ls-tree", "-r", commit] + [f for f in files] output = subprocess.check_output(cmd) for line in output.splitlines(): # 100644 blob c6d5daaa7d42e49a653f9861224aad0a0244b944 src/objects.cc _, _, actual_hash, filename = line.split() expected_hash = files[filename] if actual_hash.startswith(expected_hash): matched_files += 1 return matched_files def FindFirstMatchingCommit(start, files, limit, verbose): commit = GetGitCommitHash(start) num_files = len(files) if verbose: print(">>> Found %d files modified by patch." % num_files) for _ in range(limit): matched_files = CountMatchingFiles(commit, files) if verbose: print("Commit %s matched %d files" % (commit, matched_files)) if matched_files == num_files: return commit commit = GetGitCommitHash("%s^" % commit) print("Sorry, no matching commit found. " "Try running 'git fetch', specifying the correct --branch, " "and/or setting a higher --limit.") sys.exit(1) if __name__ == "__main__": args = GetArgs() files = FindFilesInPatch(args.patch_file) commit = FindFirstMatchingCommit(args.branch, files, args.limit, args.verbose) if args.verbose: print(">>> Matching commit: %s" % commit) print(subprocess.check_output(["git", "log", "-1", commit])) print(">>> Kthxbai.") else: print(commit)