Index: tools/find-commit-for-patch.py |
diff --git a/tools/find-commit-for-patch.py b/tools/find-commit-for-patch.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..657826c13216c6a1424c8d7cc9e9def850b4ca15 |
--- /dev/null |
+++ b/tools/find-commit-for-patch.py |
@@ -0,0 +1,93 @@ |
+#!/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) |