OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 # Copyright 2014 the V8 project authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 import argparse |
| 7 import subprocess |
| 8 import sys |
| 9 |
| 10 |
| 11 def GetArgs(): |
| 12 parser = argparse.ArgumentParser( |
| 13 description="Finds a commit that a given patch can be applied to. " |
| 14 "Does not actually apply the patch or modify your checkout " |
| 15 "in any way.") |
| 16 parser.add_argument("patch_file", help="Patch file to match") |
| 17 parser.add_argument( |
| 18 "--branch", "-b", default="origin/master", type=str, |
| 19 help="Git tree-ish where to start searching for commits, " |
| 20 "default: %(default)s") |
| 21 parser.add_argument( |
| 22 "--limit", "-l", default=500, type=int, |
| 23 help="Maximum number of commits to search, default: %(default)s") |
| 24 parser.add_argument( |
| 25 "--verbose", "-v", default=False, action="store_true", |
| 26 help="Print verbose output for your entertainment") |
| 27 return parser.parse_args() |
| 28 |
| 29 |
| 30 def FindFilesInPatch(patch_file): |
| 31 files = {} |
| 32 next_file = "" |
| 33 with open(patch_file) as patch: |
| 34 for line in patch: |
| 35 if line.startswith("diff --git "): |
| 36 # diff --git a/src/objects.cc b/src/objects.cc |
| 37 words = line.split() |
| 38 assert words[2].startswith("a/") and len(words[2]) > 2 |
| 39 next_file = words[2][2:] |
| 40 elif line.startswith("index "): |
| 41 # index add3e61..d1bbf6a 100644 |
| 42 hashes = line.split()[1] |
| 43 old_hash = hashes.split("..")[0] |
| 44 if old_hash.startswith("0000000"): continue # Ignore new files. |
| 45 files[next_file] = old_hash |
| 46 return files |
| 47 |
| 48 |
| 49 def GetGitCommitHash(treeish): |
| 50 cmd = ["git", "log", "-1", "--format=%H", treeish] |
| 51 return subprocess.check_output(cmd).strip() |
| 52 |
| 53 |
| 54 def CountMatchingFiles(commit, files): |
| 55 matched_files = 0 |
| 56 # Calling out to git once and parsing the result Python-side is faster |
| 57 # than calling 'git ls-tree' for every file. |
| 58 cmd = ["git", "ls-tree", "-r", commit] + [f for f in files] |
| 59 output = subprocess.check_output(cmd) |
| 60 for line in output.splitlines(): |
| 61 # 100644 blob c6d5daaa7d42e49a653f9861224aad0a0244b944 src/objects.cc |
| 62 _, _, actual_hash, filename = line.split() |
| 63 expected_hash = files[filename] |
| 64 if actual_hash.startswith(expected_hash): matched_files += 1 |
| 65 return matched_files |
| 66 |
| 67 |
| 68 def FindFirstMatchingCommit(start, files, limit, verbose): |
| 69 commit = GetGitCommitHash(start) |
| 70 num_files = len(files) |
| 71 if verbose: print(">>> Found %d files modified by patch." % num_files) |
| 72 for _ in range(limit): |
| 73 matched_files = CountMatchingFiles(commit, files) |
| 74 if verbose: print("Commit %s matched %d files" % (commit, matched_files)) |
| 75 if matched_files == num_files: |
| 76 return commit |
| 77 commit = GetGitCommitHash("%s^" % commit) |
| 78 print("Sorry, no matching commit found. " |
| 79 "Try running 'git fetch', specifying the correct --branch, " |
| 80 "and/or setting a higher --limit.") |
| 81 sys.exit(1) |
| 82 |
| 83 |
| 84 if __name__ == "__main__": |
| 85 args = GetArgs() |
| 86 files = FindFilesInPatch(args.patch_file) |
| 87 commit = FindFirstMatchingCommit(args.branch, files, args.limit, args.verbose) |
| 88 if args.verbose: |
| 89 print(">>> Matching commit: %s" % commit) |
| 90 print(subprocess.check_output(["git", "log", "-1", commit])) |
| 91 print(">>> Kthxbai.") |
| 92 else: |
| 93 print(commit) |
OLD | NEW |