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 6import argparse 7import subprocess 8import sys 9 10 11def 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 30def 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 49def GetGitCommitHash(treeish): 50 cmd = ["git", "log", "-1", "--format=%H", treeish] 51 return subprocess.check_output(cmd).strip() 52 53 54def 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 68def 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 84if __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) 94