#!/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 This will apply the commit to /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:])