--- /dev/null
+#!/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.
+
+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)