| #!/usr/bin/env python | |
| # Copyright 2017 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. | |
| """ | |
| Use this script to cherry-pick a V8 commit to backport to a Node.js checkout. | |
| Requirements: | |
| - Node.js checkout to backport to. | |
| - V8 checkout that contains the commit to cherry-pick. | |
| Usage: | |
| $ backport_node.py <path_to_v8> <path_to_node> <commit-hash> | |
| This will apply the commit to <path_to_node>/deps/v8 and create a commit in | |
| the Node.js checkout, increment patch level, and copy over the original | |
| commit message. | |
| Optional flags: | |
| --no-review Run `gclient sync` on the V8 checkout before updating. | |
| """ | |
| import argparse | |
| import os | |
| import subprocess | |
| import re | |
| import sys | |
| TARGET_SUBDIR = os.path.join("deps", "v8") | |
| VERSION_FILE = os.path.join("include", "v8-version.h") | |
| VERSION_PATTERN = r'(?<=#define V8_PATCH_LEVEL )\d+' | |
| def FileToText(file_name): | |
| with open(file_name) as f: | |
| return f.read() | |
| def TextToFile(text, file_name): | |
| with open(file_name, "w") as f: | |
| f.write(text) | |
| def Clean(options): | |
| print ">> Cleaning target directory." | |
| subprocess.check_call(["git", "clean", "-fd"], | |
| cwd = os.path.join(options.node_path, TARGET_SUBDIR)) | |
| def CherryPick(options): | |
| print ">> Apply patch." | |
| patch = subprocess.Popen(["git", "diff-tree", "-p", options.commit], | |
| stdout=subprocess.PIPE, cwd=options.v8_path) | |
| patch.wait() | |
| try: | |
| subprocess.check_output(["git", "apply", "-3", "--directory=%s" % TARGET_SUBDIR], | |
| stdin=patch.stdout, cwd=options.node_path) | |
| except: | |
| print ">> In another shell, please resolve patch conflicts" | |
| print ">> and `git add` affected files." | |
| print ">> Finally continue by entering RESOLVED." | |
| while raw_input("[RESOLVED]") != "RESOLVED": | |
| print ">> You need to type RESOLVED" | |
| def UpdateVersion(options): | |
| print ">> Increment patch level." | |
| version_file = os.path.join(options.node_path, TARGET_SUBDIR, VERSION_FILE) | |
| text = FileToText(version_file) | |
| def increment(match): | |
| patch = int(match.group(0)) | |
| return str(patch + 1) | |
| text = re.sub(VERSION_PATTERN, increment, text, flags=re.MULTILINE) | |
| TextToFile(text, version_file) | |
| def CreateCommit(options): | |
| print ">> Creating commit." | |
| # Find short hash from source. | |
| shorthash = subprocess.check_output( | |
| ["git", "rev-parse", "--short", options.commit], | |
| cwd=options.v8_path).strip() | |
| # Commit message | |
| title = "deps: backport %s from upstream V8" % shorthash | |
| body = subprocess.check_output( | |
| ["git", "log", options.commit, "-1", "--format=%B"], | |
| cwd=options.v8_path).strip() | |
| body = '\n'.join(" " + line for line in body.splitlines()) | |
| message = title + "\n\nOriginal commit message:\n\n" + body | |
| # Create commit at target. | |
| review_message = "--no-edit" if options.no_review else "--edit" | |
| git_commands = [ | |
| ["git", "checkout", "-b", "backport_%s" % shorthash], # new branch | |
| ["git", "add", TARGET_SUBDIR], # add files | |
| ["git", "commit", "-m", message, review_message] # new commit | |
| ] | |
| for command in git_commands: | |
| subprocess.check_call(command, cwd=options.node_path) | |
| def ParseOptions(args): | |
| parser = argparse.ArgumentParser(description="Backport V8 commit to Node.js") | |
| parser.add_argument("v8_path", help="Path to V8 checkout") | |
| parser.add_argument("node_path", help="Path to Node.js checkout") | |
| parser.add_argument("commit", help="Commit to backport") | |
| parser.add_argument("--no-review", action="store_true", | |
| help="Skip editing commit message") | |
| options = parser.parse_args(args) | |
| options.v8_path = os.path.abspath(options.v8_path) | |
| assert os.path.isdir(options.v8_path) | |
| options.node_path = os.path.abspath(options.node_path) | |
| assert os.path.isdir(options.node_path) | |
| return options | |
| def Main(args): | |
| options = ParseOptions(args) | |
| Clean(options) | |
| try: | |
| CherryPick(options) | |
| UpdateVersion(options) | |
| CreateCommit(options) | |
| except: | |
| print ">> Failed. Resetting." | |
| subprocess.check_output(["git", "reset", "--hard"], cwd=options.node_path) | |
| raise | |
| if __name__ == "__main__": | |
| Main(sys.argv[1:]) |