Home | History | Annotate | Download | only in tools
      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)
     94