backport_node.py 4.14 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
#!/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
17 18
  the Node.js checkout, increment patch level, and copy over the original
  commit message.
19 20 21 22 23 24 25 26

Optional flags:
  --no-review  Run `gclient sync` on the V8 checkout before updating.
"""

import argparse
import os
import subprocess
27
import re
28 29 30
import sys

TARGET_SUBDIR = os.path.join("deps", "v8")
31 32 33
VERSION_FILE = os.path.join("include", "v8-version.h")
VERSION_PATTERN = r'(?<=#define V8_PATCH_LEVEL )\d+'

34 35 36 37 38 39 40 41 42
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)


43 44 45 46
def Clean(options):
  print ">> Cleaning target directory."
  subprocess.check_call(["git", "clean", "-fd"],
                        cwd = os.path.join(options.node_path, TARGET_SUBDIR))
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62

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"

63 64 65 66 67 68 69 70 71 72
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)

73 74 75 76 77 78 79 80 81 82 83 84
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()
Yang Guo's avatar
Yang Guo committed
85
  body = '\n'.join("  " + line for line in body.splitlines())
86 87 88 89

  message = title + "\n\nOriginal commit message:\n\n" + body

  # Create commit at target.
Yang Guo's avatar
Yang Guo committed
90
  review_message = "--no-edit" if options.no_review else "--edit"
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
  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)
115
  Clean(options)
116 117
  try:
    CherryPick(options)
118
    UpdateVersion(options)
119 120 121 122 123 124 125 126
    CreateCommit(options)
  except:
    print ">> Failed. Resetting."
    subprocess.check_output(["git", "reset", "--hard"], cwd=options.node_path)
    raise

if __name__ == "__main__":
  Main(sys.argv[1:])