11cb0ef41Sopenharmony_ci#!/usr/bin/env python
21cb0ef41Sopenharmony_ci# Copyright 2014 the V8 project authors. All rights reserved.
31cb0ef41Sopenharmony_ci# Use of this source code is governed by a BSD-style license that can be
41cb0ef41Sopenharmony_ci# found in the LICENSE file.
51cb0ef41Sopenharmony_ci
61cb0ef41Sopenharmony_ci# for py2/py3 compatibility
71cb0ef41Sopenharmony_cifrom __future__ import print_function
81cb0ef41Sopenharmony_ci
91cb0ef41Sopenharmony_ciimport argparse
101cb0ef41Sopenharmony_ciimport subprocess
111cb0ef41Sopenharmony_ciimport sys
121cb0ef41Sopenharmony_ci
131cb0ef41Sopenharmony_ci
141cb0ef41Sopenharmony_cidef GetArgs():
151cb0ef41Sopenharmony_ci  parser = argparse.ArgumentParser(
161cb0ef41Sopenharmony_ci      description="Finds a commit that a given patch can be applied to. "
171cb0ef41Sopenharmony_ci                  "Does not actually apply the patch or modify your checkout "
181cb0ef41Sopenharmony_ci                  "in any way.")
191cb0ef41Sopenharmony_ci  parser.add_argument("patch_file", help="Patch file to match")
201cb0ef41Sopenharmony_ci  parser.add_argument(
211cb0ef41Sopenharmony_ci      "--branch", "-b", default="origin/master", type=str,
221cb0ef41Sopenharmony_ci      help="Git tree-ish where to start searching for commits, "
231cb0ef41Sopenharmony_ci           "default: %(default)s")
241cb0ef41Sopenharmony_ci  parser.add_argument(
251cb0ef41Sopenharmony_ci      "--limit", "-l", default=500, type=int,
261cb0ef41Sopenharmony_ci      help="Maximum number of commits to search, default: %(default)s")
271cb0ef41Sopenharmony_ci  parser.add_argument(
281cb0ef41Sopenharmony_ci      "--verbose", "-v", default=False, action="store_true",
291cb0ef41Sopenharmony_ci      help="Print verbose output for your entertainment")
301cb0ef41Sopenharmony_ci  return parser.parse_args()
311cb0ef41Sopenharmony_ci
321cb0ef41Sopenharmony_ci
331cb0ef41Sopenharmony_cidef FindFilesInPatch(patch_file):
341cb0ef41Sopenharmony_ci  files = {}
351cb0ef41Sopenharmony_ci  next_file = ""
361cb0ef41Sopenharmony_ci  with open(patch_file) as patch:
371cb0ef41Sopenharmony_ci    for line in patch:
381cb0ef41Sopenharmony_ci      if line.startswith("diff --git "):
391cb0ef41Sopenharmony_ci        # diff --git a/src/objects.cc b/src/objects.cc
401cb0ef41Sopenharmony_ci        words = line.split()
411cb0ef41Sopenharmony_ci        assert words[2].startswith("a/") and len(words[2]) > 2
421cb0ef41Sopenharmony_ci        next_file = words[2][2:]
431cb0ef41Sopenharmony_ci      elif line.startswith("index "):
441cb0ef41Sopenharmony_ci        # index add3e61..d1bbf6a 100644
451cb0ef41Sopenharmony_ci        hashes = line.split()[1]
461cb0ef41Sopenharmony_ci        old_hash = hashes.split("..")[0]
471cb0ef41Sopenharmony_ci        if old_hash.startswith("0000000"): continue  # Ignore new files.
481cb0ef41Sopenharmony_ci        files[next_file] = old_hash
491cb0ef41Sopenharmony_ci  return files
501cb0ef41Sopenharmony_ci
511cb0ef41Sopenharmony_ci
521cb0ef41Sopenharmony_cidef GetGitCommitHash(treeish):
531cb0ef41Sopenharmony_ci  cmd = ["git", "log", "-1", "--format=%H", treeish]
541cb0ef41Sopenharmony_ci  return subprocess.check_output(cmd).strip()
551cb0ef41Sopenharmony_ci
561cb0ef41Sopenharmony_ci
571cb0ef41Sopenharmony_cidef CountMatchingFiles(commit, files):
581cb0ef41Sopenharmony_ci  matched_files = 0
591cb0ef41Sopenharmony_ci  # Calling out to git once and parsing the result Python-side is faster
601cb0ef41Sopenharmony_ci  # than calling 'git ls-tree' for every file.
611cb0ef41Sopenharmony_ci  cmd = ["git", "ls-tree", "-r", commit] + [f for f in files]
621cb0ef41Sopenharmony_ci  output = subprocess.check_output(cmd)
631cb0ef41Sopenharmony_ci  for line in output.splitlines():
641cb0ef41Sopenharmony_ci    # 100644 blob c6d5daaa7d42e49a653f9861224aad0a0244b944      src/objects.cc
651cb0ef41Sopenharmony_ci    _, _, actual_hash, filename = line.split()
661cb0ef41Sopenharmony_ci    expected_hash = files[filename]
671cb0ef41Sopenharmony_ci    if actual_hash.startswith(expected_hash): matched_files += 1
681cb0ef41Sopenharmony_ci  return matched_files
691cb0ef41Sopenharmony_ci
701cb0ef41Sopenharmony_ci
711cb0ef41Sopenharmony_cidef FindFirstMatchingCommit(start, files, limit, verbose):
721cb0ef41Sopenharmony_ci  commit = GetGitCommitHash(start)
731cb0ef41Sopenharmony_ci  num_files = len(files)
741cb0ef41Sopenharmony_ci  if verbose: print(">>> Found %d files modified by patch." % num_files)
751cb0ef41Sopenharmony_ci  for _ in range(limit):
761cb0ef41Sopenharmony_ci    matched_files = CountMatchingFiles(commit, files)
771cb0ef41Sopenharmony_ci    if verbose: print("Commit %s matched %d files" % (commit, matched_files))
781cb0ef41Sopenharmony_ci    if matched_files == num_files:
791cb0ef41Sopenharmony_ci      return commit
801cb0ef41Sopenharmony_ci    commit = GetGitCommitHash("%s^" % commit)
811cb0ef41Sopenharmony_ci  print("Sorry, no matching commit found. "
821cb0ef41Sopenharmony_ci        "Try running 'git fetch', specifying the correct --branch, "
831cb0ef41Sopenharmony_ci        "and/or setting a higher --limit.")
841cb0ef41Sopenharmony_ci  sys.exit(1)
851cb0ef41Sopenharmony_ci
861cb0ef41Sopenharmony_ci
871cb0ef41Sopenharmony_ciif __name__ == "__main__":
881cb0ef41Sopenharmony_ci  args = GetArgs()
891cb0ef41Sopenharmony_ci  files = FindFilesInPatch(args.patch_file)
901cb0ef41Sopenharmony_ci  commit = FindFirstMatchingCommit(args.branch, files, args.limit, args.verbose)
911cb0ef41Sopenharmony_ci  if args.verbose:
921cb0ef41Sopenharmony_ci    print(">>> Matching commit: %s" % commit)
931cb0ef41Sopenharmony_ci    print(subprocess.check_output(["git", "log", "-1", commit]))
941cb0ef41Sopenharmony_ci    print(">>> Kthxbai.")
951cb0ef41Sopenharmony_ci  else:
961cb0ef41Sopenharmony_ci    print(commit)
97