1935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org#!/usr/bin/env python
2935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org# Copyright 2013 the V8 project authors. All rights reserved.
3935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org# Redistribution and use in source and binary forms, with or without
4935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org# modification, are permitted provided that the following conditions are
5935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org# met:
6935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org#
7935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org#     * Redistributions of source code must retain the above copyright
8935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org#       notice, this list of conditions and the following disclaimer.
9935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org#     * Redistributions in binary form must reproduce the above
10935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org#       copyright notice, this list of conditions and the following
11935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org#       disclaimer in the documentation and/or other materials provided
12935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org#       with the distribution.
13935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org#     * Neither the name of Google Inc. nor the names of its
14935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org#       contributors may be used to endorse or promote products derived
15935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org#       from this software without specific prior written permission.
16935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org#
17935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
29bc176057ae476990672de915df235c9aeadc8521titzer@chromium.orgimport argparse
30ddf3811f8018dfe9e8ec7d1b8f4a8be1122fd767machenbach@chromium.orgimport datetime
31c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.orgimport httplib
3242ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.orgimport glob
339e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.orgimport imp
34f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.orgimport json
35935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.orgimport os
36935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.orgimport re
3742ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.orgimport shutil
38935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.orgimport subprocess
39935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.orgimport sys
409af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.orgimport textwrap
418a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.orgimport time
42c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.orgimport urllib
4390dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.orgimport urllib2
44935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
45f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.orgfrom git_recipes import GitRecipesMixin
465b080567cf135f6dbaf23973ba6b6fa1d6af83b3machenbach@chromium.orgfrom git_recipes import GitFailedException
47f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org
4806b2696801712948b665372a38f96b1f10be6997machenbach@chromium.orgVERSION_FILE = os.path.join("src", "version.cc")
49935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
5042ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org# V8 base directory.
5142ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.orgDEFAULT_CWD = os.path.dirname(
5242ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org    os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
5342ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org
54935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
55935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.orgdef TextToFile(text, file_name):
56935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  with open(file_name, "w") as f:
57935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    f.write(text)
58935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
59935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
60935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.orgdef AppendToFile(text, file_name):
61935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  with open(file_name, "a") as f:
62935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    f.write(text)
63935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
64935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
65935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.orgdef LinesInFile(file_name):
66935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  with open(file_name) as f:
67935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    for line in f:
68935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org      yield line
69935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
70935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
71935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.orgdef FileToText(file_name):
72935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  with open(file_name) as f:
73935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    return f.read()
74935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
75935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
76935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.orgdef MSub(rexp, replacement, text):
77935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  return re.sub(rexp, replacement, text, flags=re.MULTILINE)
78935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
79935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
809af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.orgdef Fill80(line):
8190dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org  # Replace tabs and remove surrounding space.
8290dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org  line = re.sub(r"\t", r"        ", line.strip())
8390dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org
8490dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org  # Format with 8 characters indentation and line width 80.
859af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org  return textwrap.fill(line, width=80, initial_indent="        ",
869af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org                       subsequent_indent="        ")
879af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org
889af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org
897ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.orgdef MakeComment(text):
907ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org  return MSub(r"^( ?)", "#", text)
917ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org
927ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org
937ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.orgdef StripComments(text):
947ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org  # Use split not splitlines to keep terminal newlines.
957ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org  return "\n".join(filter(lambda x: not x.startswith("#"), text.split("\n")))
967ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org
977ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org
987ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.orgdef MakeChangeLogBody(commit_messages, auto_format=False):
99af9cfcbed5daf6e636e189bce451c6fafdbb127dmachenbach@chromium.org  result = ""
1007ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org  added_titles = set()
1017ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org  for (title, body, author) in commit_messages:
10237be408adf363bbe682921a4a690752fa0ec33femachenbach@chromium.org    # TODO(machenbach): Better check for reverts. A revert should remove the
10337be408adf363bbe682921a4a690752fa0ec33femachenbach@chromium.org    # original CL from the actual log entry.
10490dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org    title = title.strip()
1057ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org    if auto_format:
1067ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org      # Only add commits that set the LOG flag correctly.
107ddf3811f8018dfe9e8ec7d1b8f4a8be1122fd767machenbach@chromium.org      log_exp = r"^[ \t]*LOG[ \t]*=[ \t]*(?:(?:Y(?:ES)?)|TRUE)"
1087ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org      if not re.search(log_exp, body, flags=re.I | re.M):
1097ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org        continue
1107ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org      # Never include reverts.
1117ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org      if title.startswith("Revert "):
1127ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org        continue
1137ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org      # Don't include duplicates.
1147ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org      if title in added_titles:
1157ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org        continue
1167ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org
11737be408adf363bbe682921a4a690752fa0ec33femachenbach@chromium.org    # Add and format the commit's title and bug reference. Move dot to the end.
1187ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org    added_titles.add(title)
11937be408adf363bbe682921a4a690752fa0ec33femachenbach@chromium.org    raw_title = re.sub(r"(\.|\?|!)$", "", title)
12037be408adf363bbe682921a4a690752fa0ec33femachenbach@chromium.org    bug_reference = MakeChangeLogBugReference(body)
12137be408adf363bbe682921a4a690752fa0ec33femachenbach@chromium.org    space = " " if bug_reference else ""
12237be408adf363bbe682921a4a690752fa0ec33femachenbach@chromium.org    result += "%s\n" % Fill80("%s%s%s." % (raw_title, space, bug_reference))
123af9cfcbed5daf6e636e189bce451c6fafdbb127dmachenbach@chromium.org
1247ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org    # Append the commit's author for reference if not in auto-format mode.
1257ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org    if not auto_format:
12690dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org      result += "%s\n" % Fill80("(%s)" % author.strip())
1277ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org
1287ff7607c2315ea91e4d13330ce14125e4bb4851amachenbach@chromium.org    result += "\n"
129af9cfcbed5daf6e636e189bce451c6fafdbb127dmachenbach@chromium.org  return result
130af9cfcbed5daf6e636e189bce451c6fafdbb127dmachenbach@chromium.org
131af9cfcbed5daf6e636e189bce451c6fafdbb127dmachenbach@chromium.org
1329af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.orgdef MakeChangeLogBugReference(body):
1339af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org  """Grep for "BUG=xxxx" lines in the commit message and convert them to
1349af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org  "(issue xxxx)".
1359af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org  """
1369af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org  crbugs = []
1379af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org  v8bugs = []
1389af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org
1399af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org  def AddIssues(text):
1409af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org    ref = re.match(r"^BUG[ \t]*=[ \t]*(.+)$", text.strip())
1419af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org    if not ref:
1429af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org      return
1439af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org    for bug in ref.group(1).split(","):
1449af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org      bug = bug.strip()
1459af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org      match = re.match(r"^v8:(\d+)$", bug)
1469af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org      if match: v8bugs.append(int(match.group(1)))
1479af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org      else:
1489af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org        match = re.match(r"^(?:chromium:)?(\d+)$", bug)
1499af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org        if match: crbugs.append(int(match.group(1)))
1509af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org
1519af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org  # Add issues to crbugs and v8bugs.
1529af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org  map(AddIssues, body.splitlines())
1539af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org
1549af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org  # Filter duplicates, sort, stringify.
1559af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org  crbugs = map(str, sorted(set(crbugs)))
1569af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org  v8bugs = map(str, sorted(set(v8bugs)))
1579af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org
1589af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org  bug_groups = []
1599af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org  def FormatIssues(prefix, bugs):
1609af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org    if len(bugs) > 0:
1619af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org      plural = "s" if len(bugs) > 1 else ""
1629af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org      bug_groups.append("%sissue%s %s" % (prefix, plural, ", ".join(bugs)))
1639af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org
1649af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org  FormatIssues("", v8bugs)
1659af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org  FormatIssues("Chromium ", crbugs)
1669af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org
1679af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org  if len(bug_groups) > 0:
16837be408adf363bbe682921a4a690752fa0ec33femachenbach@chromium.org    return "(%s)" % ", ".join(bug_groups)
1699af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org  else:
1709af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org    return ""
1719af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org
1729af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org
1734c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.orgdef SortingKey(version):
1744c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org  """Key for sorting version number strings: '3.11' > '3.2.1.1'"""
1754c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org  version_keys = map(int, version.split("."))
1764c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org  # Fill up to full version numbers to normalize comparison.
1774c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org  while len(version_keys) < 4:  # pragma: no cover
1784c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org    version_keys.append(0)
1794c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org  # Fill digits.
1804c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org  return ".".join(map("{0:04d}".format, version_keys))
1814c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org
1824c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org
183935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org# Some commands don't like the pipe, e.g. calling vi from within the script or
184935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org# from subscripts like git cl upload.
18542ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.orgdef Command(cmd, args="", prefix="", pipe=True, cwd=None):
18642ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org  cwd = cwd or os.getcwd()
1878a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org  # TODO(machenbach): Use timeout.
188935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  cmd_line = "%s %s %s" % (prefix, cmd, args)
189935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  print "Command: %s" % cmd_line
19042ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org  print "in %s" % cwd
191af6f699b0be532b73bc2f6c9e1cf40a57fa7e234machenbach@chromium.org  sys.stdout.flush()
192935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  try:
193935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    if pipe:
19442ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org      return subprocess.check_output(cmd_line, shell=True, cwd=cwd)
195935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    else:
19642ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org      return subprocess.check_call(cmd_line, shell=True, cwd=cwd)
197935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  except subprocess.CalledProcessError:
198935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    return None
199af6f699b0be532b73bc2f6c9e1cf40a57fa7e234machenbach@chromium.org  finally:
200af6f699b0be532b73bc2f6c9e1cf40a57fa7e234machenbach@chromium.org    sys.stdout.flush()
201af6f699b0be532b73bc2f6c9e1cf40a57fa7e234machenbach@chromium.org    sys.stderr.flush()
202935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
203935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
204935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org# Wrapper for side effects.
205486536df718553960f9700559e80e5b10b0d5994dslomov@chromium.orgclass SideEffectHandler(object):  # pragma: no cover
20643c51e51fafad9405752a3d7e953367531469575machenbach@chromium.org  def Call(self, fun, *args, **kwargs):
20743c51e51fafad9405752a3d7e953367531469575machenbach@chromium.org    return fun(*args, **kwargs)
20843c51e51fafad9405752a3d7e953367531469575machenbach@chromium.org
20942ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org  def Command(self, cmd, args="", prefix="", pipe=True, cwd=None):
21042ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org    return Command(cmd, args, prefix, pipe, cwd=cwd)
211935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
212935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  def ReadLine(self):
213935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    return sys.stdin.readline().strip()
214935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
21526ca35cc4ec47151d9c6d3890b0f052fc79cb8afmachenbach@chromium.org  def ReadURL(self, url, params=None):
21690dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org    # pylint: disable=E1121
21726ca35cc4ec47151d9c6d3890b0f052fc79cb8afmachenbach@chromium.org    url_fh = urllib2.urlopen(url, params, 60)
21890dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org    try:
21990dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org      return url_fh.read()
22090dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org    finally:
22190dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org      url_fh.close()
22290dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org
223c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.org  def ReadClusterFuzzAPI(self, api_key, **params):
224c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.org    params["api_key"] = api_key.strip()
225c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.org    params = urllib.urlencode(params)
226c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.org
227c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.org    headers = {"Content-type": "application/x-www-form-urlencoded"}
228c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.org
229c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.org    conn = httplib.HTTPSConnection("backend-dot-cluster-fuzz.appspot.com")
230c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.org    conn.request("POST", "/_api/", params, headers)
231c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.org
232c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.org    response = conn.getresponse()
233c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.org    data = response.read()
234c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.org
235c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.org    try:
236c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.org      return json.loads(data)
237c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.org    except:
238c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.org      print data
239c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.org      print "ERROR: Could not read response. Is your key valid?"
240c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.org      raise
241c3564d8de4ebfc4fa3dc009fc9f6f18968ffcbd7machenbach@chromium.org
242ddf3811f8018dfe9e8ec7d1b8f4a8be1122fd767machenbach@chromium.org  def Sleep(self, seconds):
2438a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org    time.sleep(seconds)
2448a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org
245ddf3811f8018dfe9e8ec7d1b8f4a8be1122fd767machenbach@chromium.org  def GetDate(self):
246ddf3811f8018dfe9e8ec7d1b8f4a8be1122fd767machenbach@chromium.org    return datetime.date.today().strftime("%Y-%m-%d")
247ddf3811f8018dfe9e8ec7d1b8f4a8be1122fd767machenbach@chromium.org
248474e8b19cf12dc057572a8543864dd6586ee0a65machenbach@chromium.org  def GetUTCStamp(self):
249f2af15a6b44ea6276bdd609ee122babe52842a42machenbach@chromium.org    return time.mktime(datetime.datetime.utcnow().timetuple())
250474e8b19cf12dc057572a8543864dd6586ee0a65machenbach@chromium.org
251935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.orgDEFAULT_SIDE_EFFECT_HANDLER = SideEffectHandler()
252935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
253935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
25480108f7c5afc50b5289595a9384e7d2f085cb679hpayer@chromium.orgclass NoRetryException(Exception):
25580108f7c5afc50b5289595a9384e7d2f085cb679hpayer@chromium.org  pass
25680108f7c5afc50b5289595a9384e7d2f085cb679hpayer@chromium.org
2579cbaabda8b4daeb06759ace10c926ab55bb69d7bulan@chromium.org
258f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.orgclass Step(GitRecipesMixin):
25906b2696801712948b665372a38f96b1f10be6997machenbach@chromium.org  def __init__(self, text, number, config, state, options, handler):
260935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    self._text = text
261935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    self._number = number
262935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    self._config = config
263935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    self._state = state
264935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    self._options = options
265935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    self._side_effect_handler = handler
26642ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org
26742ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org    # The testing configuration might set a different default cwd.
26842ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org    self.default_cwd = self._config.get("DEFAULT_CWD") or DEFAULT_CWD
26942ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org
27090dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org    assert self._number >= 0
27190dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org    assert self._config is not None
27290dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org    assert self._state is not None
27390dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org    assert self._side_effect_handler is not None
274935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
275f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org  def __getitem__(self, key):
276f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org    # Convenience method to allow direct [] access on step classes for
277f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org    # manipulating the backed state dict.
278f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org    return self._state[key]
279f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org
280f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org  def __setitem__(self, key, value):
281f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org    # Convenience method to allow direct [] access on step classes for
282f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org    # manipulating the backed state dict.
283f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org    self._state[key] = value
284f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org
285935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  def Config(self, key):
286935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    return self._config[key]
287935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
288935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  def Run(self):
289f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org    # Restore state.
29006b2696801712948b665372a38f96b1f10be6997machenbach@chromium.org    state_file = "%s-state.json" % self._config["PERSISTFILE_BASENAME"]
291f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org    if not self._state and os.path.exists(state_file):
292f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org      self._state.update(json.loads(FileToText(state_file)))
293f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org
294935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    print ">>> Step %d: %s" % (self._number, self._text)
2954edebd5691ee147fa134ad8aaf6cc3c939831b93machenbach@chromium.org    try:
2964edebd5691ee147fa134ad8aaf6cc3c939831b93machenbach@chromium.org      return self.RunStep()
2974edebd5691ee147fa134ad8aaf6cc3c939831b93machenbach@chromium.org    finally:
2984edebd5691ee147fa134ad8aaf6cc3c939831b93machenbach@chromium.org      # Persist state.
2994edebd5691ee147fa134ad8aaf6cc3c939831b93machenbach@chromium.org      TextToFile(json.dumps(self._state), state_file)
300f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org
301486536df718553960f9700559e80e5b10b0d5994dslomov@chromium.org  def RunStep(self):  # pragma: no cover
302935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    raise NotImplementedError
303935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
3048a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org  def Retry(self, cb, retry_on=None, wait_plan=None):
3058a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org    """ Retry a function.
3068a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org    Params:
3078a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org      cb: The function to retry.
3088a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org      retry_on: A callback that takes the result of the function and returns
3098a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org                True if the function should be retried. A function throwing an
3108a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org                exception is always retried.
3118a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org      wait_plan: A list of waiting delays between retries in seconds. The
3128a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org                 maximum number of retries is len(wait_plan).
3138a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org    """
3148a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org    retry_on = retry_on or (lambda x: False)
3158a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org    wait_plan = list(wait_plan or [])
3168a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org    wait_plan.reverse()
3178a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org    while True:
3188a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org      got_exception = False
3198a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org      try:
3208a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org        result = cb()
3219aaa825cf89e1bcfece269a453300ebf4a26d64dmachenbach@chromium.org      except NoRetryException as e:
32280108f7c5afc50b5289595a9384e7d2f085cb679hpayer@chromium.org        raise e
3239aaa825cf89e1bcfece269a453300ebf4a26d64dmachenbach@chromium.org      except Exception as e:
3249aaa825cf89e1bcfece269a453300ebf4a26d64dmachenbach@chromium.org        got_exception = e
3258a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org      if got_exception or retry_on(result):
326486536df718553960f9700559e80e5b10b0d5994dslomov@chromium.org        if not wait_plan:  # pragma: no cover
3279aaa825cf89e1bcfece269a453300ebf4a26d64dmachenbach@chromium.org          raise Exception("Retried too often. Giving up. Reason: %s" %
3289aaa825cf89e1bcfece269a453300ebf4a26d64dmachenbach@chromium.org                          str(got_exception))
3298a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org        wait_time = wait_plan.pop()
3308a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org        print "Waiting for %f seconds." % wait_time
3318a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org        self._side_effect_handler.Sleep(wait_time)
3328a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org        print "Retrying..."
3338a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org      else:
3348a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org        return result
3358a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org
3369af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org  def ReadLine(self, default=None):
3379af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org    # Don't prompt in forced mode.
33880108f7c5afc50b5289595a9384e7d2f085cb679hpayer@chromium.org    if self._options.force_readline_defaults and default is not None:
3399af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org      print "%s (forced)" % default
3409af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org      return default
3419af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org    else:
3429af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org      return self._side_effect_handler.ReadLine()
343935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
34442ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org  def Command(self, name, args, cwd=None):
34542ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org    cmd = lambda: self._side_effect_handler.Command(
34642ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org        name, args, "", True, cwd=cwd or self.default_cwd)
34742ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org    return self.Retry(cmd, None, [5])
34842ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org
34942ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org  def Git(self, args="", prefix="", pipe=True, retry_on=None, cwd=None):
35042ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org    cmd = lambda: self._side_effect_handler.Command(
35142ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org        "git", args, prefix, pipe, cwd=cwd or self.default_cwd)
352f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org    result = self.Retry(cmd, retry_on, [5, 30])
353f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org    if result is None:
354f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org      raise GitFailedException("'git %s' failed." % args)
355f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org    return result
356f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org
35742ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org  def SVN(self, args="", prefix="", pipe=True, retry_on=None, cwd=None):
35842ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org    cmd = lambda: self._side_effect_handler.Command(
35942ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org        "svn", args, prefix, pipe, cwd=cwd or self.default_cwd)
360d91c2cb3b3e3a5e4d2eed51586755c9d747e9508machenbach@chromium.org    return self.Retry(cmd, retry_on, [5, 30])
361d91c2cb3b3e3a5e4d2eed51586755c9d747e9508machenbach@chromium.org
362935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  def Editor(self, args):
36380108f7c5afc50b5289595a9384e7d2f085cb679hpayer@chromium.org    if self._options.requires_editor:
36442ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org      return self._side_effect_handler.Command(
36542ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org          os.environ["EDITOR"],
36642ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org          args,
36742ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org          pipe=False,
36842ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org          cwd=self.default_cwd)
369935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
37026ca35cc4ec47151d9c6d3890b0f052fc79cb8afmachenbach@chromium.org  def ReadURL(self, url, params=None, retry_on=None, wait_plan=None):
3718a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org    wait_plan = wait_plan or [3, 60, 600]
37226ca35cc4ec47151d9c6d3890b0f052fc79cb8afmachenbach@chromium.org    cmd = lambda: self._side_effect_handler.ReadURL(url, params)
3738a58f6420f995bb19fff9babb261458d49d90cb1machenbach@chromium.org    return self.Retry(cmd, retry_on, wait_plan)
37490dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org
375ddf3811f8018dfe9e8ec7d1b8f4a8be1122fd767machenbach@chromium.org  def GetDate(self):
376ddf3811f8018dfe9e8ec7d1b8f4a8be1122fd767machenbach@chromium.org    return self._side_effect_handler.GetDate()
377ddf3811f8018dfe9e8ec7d1b8f4a8be1122fd767machenbach@chromium.org
378935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  def Die(self, msg=""):
379935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    if msg != "":
380935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org      print "Error: %s" % msg
381935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    print "Exiting"
382935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    raise Exception(msg)
383935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
384ddf3811f8018dfe9e8ec7d1b8f4a8be1122fd767machenbach@chromium.org  def DieNoManualMode(self, msg=""):
385486536df718553960f9700559e80e5b10b0d5994dslomov@chromium.org    if not self._options.manual:  # pragma: no cover
386ddf3811f8018dfe9e8ec7d1b8f4a8be1122fd767machenbach@chromium.org      msg = msg or "Only available in manual mode."
3879af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org      self.Die(msg)
3889af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org
389935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  def Confirm(self, msg):
390935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    print "%s [Y/n] " % msg,
3919af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org    answer = self.ReadLine(default="Y")
392935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    return answer == "" or answer == "Y" or answer == "y"
393935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
394935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  def DeleteBranch(self, name):
395f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org    for line in self.GitBranch().splitlines():
3964c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org      if re.match(r"\*?\s*%s$" % re.escape(name), line):
397935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org        msg = "Branch %s exists, do you want to delete it?" % name
398935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org        if self.Confirm(msg):
399f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org          self.GitDeleteBranch(name)
400935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org          print "Branch %s deleted." % name
401935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org        else:
402935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org          msg = "Can't continue. Please delete branch %s and try again." % name
403935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org          self.Die(msg)
404935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
405a2c0c1516848536a514b3178d2c040b7df0ceb5bmachenbach@chromium.org  def InitialEnvironmentChecks(self, cwd):
406935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    # Cancel if this is not a git checkout.
407a2c0c1516848536a514b3178d2c040b7df0ceb5bmachenbach@chromium.org    if not os.path.exists(os.path.join(cwd, ".git")):  # pragma: no cover
408935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org      self.Die("This is not a git checkout, this script won't work for you.")
409935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
410935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    # Cancel if EDITOR is unset or not executable.
41180108f7c5afc50b5289595a9384e7d2f085cb679hpayer@chromium.org    if (self._options.requires_editor and (not os.environ.get("EDITOR") or
41242ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org        self.Command(
41342ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org            "which", os.environ["EDITOR"]) is None)):  # pragma: no cover
414935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org      self.Die("Please set your EDITOR environment variable, you'll need it.")
415935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
416935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  def CommonPrepare(self):
417935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    # Check for a clean workdir.
418486536df718553960f9700559e80e5b10b0d5994dslomov@chromium.org    if not self.GitIsWorkdirClean():  # pragma: no cover
419935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org      self.Die("Workspace is not clean. Please commit or undo your changes.")
420935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
421935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    # Persist current branch.
422f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org    self["current_branch"] = self.GitCurrentBranch()
423935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
424935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    # Fetch unfetched revisions.
425f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org    self.GitSVNFetch()
426935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
427af9cfcbed5daf6e636e189bce451c6fafdbb127dmachenbach@chromium.org  def PrepareBranch(self):
428935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    # Delete the branch that will be created later if it exists already.
42906b2696801712948b665372a38f96b1f10be6997machenbach@chromium.org    self.DeleteBranch(self._config["BRANCHNAME"])
430935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
431935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  def CommonCleanup(self):
432f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org    self.GitCheckout(self["current_branch"])
43306b2696801712948b665372a38f96b1f10be6997machenbach@chromium.org    if self._config["BRANCHNAME"] != self["current_branch"]:
43406b2696801712948b665372a38f96b1f10be6997machenbach@chromium.org      self.GitDeleteBranch(self._config["BRANCHNAME"])
435935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
436935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    # Clean up all temporary files.
43706b2696801712948b665372a38f96b1f10be6997machenbach@chromium.org    for f in glob.iglob("%s*" % self._config["PERSISTFILE_BASENAME"]):
43842ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org      if os.path.isfile(f):
43942ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org        os.remove(f)
44042ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org      if os.path.isdir(f):
44142ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org        shutil.rmtree(f)
442935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
443935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  def ReadAndPersistVersion(self, prefix=""):
444935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    def ReadAndPersist(var_name, def_name):
445935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org      match = re.match(r"^#define %s\s+(\d*)" % def_name, line)
446935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org      if match:
447935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org        value = match.group(1)
448f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org        self["%s%s" % (prefix, var_name)] = value
44906b2696801712948b665372a38f96b1f10be6997machenbach@chromium.org    for line in LinesInFile(os.path.join(self.default_cwd, VERSION_FILE)):
450935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org      for (var_name, def_name) in [("major", "MAJOR_VERSION"),
451935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org                                   ("minor", "MINOR_VERSION"),
452935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org                                   ("build", "BUILD_NUMBER"),
453935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org                                   ("patch", "PATCH_LEVEL")]:
454935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org        ReadAndPersist(var_name, def_name)
455935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
456935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  def WaitForLGTM(self):
457935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit "
458935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org           "your change. (If you need to iterate on the patch or double check "
459935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org           "that it's sane, do so in another shell, but remember to not "
460935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org           "change the headline of the uploaded CL.")
461935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    answer = ""
462935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    while answer != "LGTM":
463935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org      print "> ",
46480108f7c5afc50b5289595a9384e7d2f085cb679hpayer@chromium.org      answer = self.ReadLine(None if self._options.wait_for_lgtm else "LGTM")
465935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org      if answer != "LGTM":
466935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org        print "That was not 'LGTM'."
467935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
468935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  def WaitForResolvingConflicts(self, patch_file):
469935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    print("Applying the patch \"%s\" failed. Either type \"ABORT<Return>\", "
470935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org          "or resolve the conflicts, stage *all* touched files with "
471935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org          "'git add', and type \"RESOLVED<Return>\"")
472ddf3811f8018dfe9e8ec7d1b8f4a8be1122fd767machenbach@chromium.org    self.DieNoManualMode()
473935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    answer = ""
474935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org    while answer != "RESOLVED":
475935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org      if answer == "ABORT":
476935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org        self.Die("Applying the patch failed.")
477935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org      if answer != "":
478935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org        print "That was not 'RESOLVED' or 'ABORT'."
479935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org      print "> ",
480935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org      answer = self.ReadLine()
481935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
482935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  # Takes a file containing the patch to apply as first argument.
483f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org  def ApplyPatch(self, patch_file, revert=False):
484f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org    try:
485f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org      self.GitApplyPatch(patch_file, revert)
486f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org    except GitFailedException:
487935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org      self.WaitForResolvingConflicts(patch_file)
488935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
4899aaa825cf89e1bcfece269a453300ebf4a26d64dmachenbach@chromium.org  def FindLastTrunkPush(
4909aaa825cf89e1bcfece269a453300ebf4a26d64dmachenbach@chromium.org      self, parent_hash="", branch="", include_patches=False):
4918f8fe81d7a9cced7a0d30e56124c0428d1a6d229machenbach@chromium.org    push_pattern = "^Version [[:digit:]]*\.[[:digit:]]*\.[[:digit:]]*"
4928f8fe81d7a9cced7a0d30e56124c0428d1a6d229machenbach@chromium.org    if not include_patches:
4938f8fe81d7a9cced7a0d30e56124c0428d1a6d229machenbach@chromium.org      # Non-patched versions only have three numbers followed by the "(based
4948f8fe81d7a9cced7a0d30e56124c0428d1a6d229machenbach@chromium.org      # on...) comment."
4958f8fe81d7a9cced7a0d30e56124c0428d1a6d229machenbach@chromium.org      push_pattern += " (based"
4969aaa825cf89e1bcfece269a453300ebf4a26d64dmachenbach@chromium.org    branch = "" if parent_hash else branch or "svn/trunk"
497f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org    return self.GitLog(n=1, format="%H", grep=push_pattern,
498f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org                       parent_hash=parent_hash, branch=branch)
499f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org
5004c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org  def ArrayToVersion(self, prefix):
5014c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org    return ".".join([self[prefix + "major"],
5024c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org                     self[prefix + "minor"],
5034c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org                     self[prefix + "build"],
5044c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org                     self[prefix + "patch"]])
5054c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org
5064c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org  def SetVersion(self, version_file, prefix):
5074c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org    output = ""
5084c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org    for line in FileToText(version_file).splitlines():
5094c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org      if line.startswith("#define MAJOR_VERSION"):
5104c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org        line = re.sub("\d+$", self[prefix + "major"], line)
5114c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org      elif line.startswith("#define MINOR_VERSION"):
5124c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org        line = re.sub("\d+$", self[prefix + "minor"], line)
5134c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org      elif line.startswith("#define BUILD_NUMBER"):
5144c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org        line = re.sub("\d+$", self[prefix + "build"], line)
5154c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org      elif line.startswith("#define PATCH_LEVEL"):
5164c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org        line = re.sub("\d+$", self[prefix + "patch"], line)
5174c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org      output += "%s\n" % line
5184c3ce7c3fd2802da8f91c6516a9c9aea3cd93f1emachenbach@chromium.org    TextToFile(output, version_file)
519935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
52042ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org  def SVNCommit(self, root, commit_message):
52142ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org    patch = self.GitDiff("HEAD^", "HEAD")
52206b2696801712948b665372a38f96b1f10be6997machenbach@chromium.org    TextToFile(patch, self._config["PATCH_FILE"])
52342ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org    self.Command("svn", "update", cwd=self._options.svn)
52442ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org    if self.Command("svn", "status", cwd=self._options.svn) != "":
52542ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org      self.Die("SVN checkout not clean.")
52642ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org    if not self.Command("patch", "-d %s -p1 -i %s" %
52706b2696801712948b665372a38f96b1f10be6997machenbach@chromium.org                        (root, self._config["PATCH_FILE"]),
52842ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org                        cwd=self._options.svn):
52942ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org      self.Die("Could not apply patch.")
53042ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org    self.Command(
53142ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org        "svn",
53242ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org        "commit --non-interactive --username=%s --config-dir=%s -m \"%s\"" %
53342ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org            (self._options.author, self._options.svn_config, commit_message),
53442ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org        cwd=self._options.svn)
53542ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org
53642ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org
537935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.orgclass UploadStep(Step):
53890dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org  MESSAGE = "Upload for code review."
539935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org
540935a7790c70d49e252069bc2d34eaa72f8c6677fmachenbach@chromium.org  def RunStep(self):
541f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org    if self._options.reviewer:
542f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org      print "Using account %s for review." % self._options.reviewer
543f5a24546072ecdbbd6372c85c42157e01e913561titzer@chromium.org      reviewer = self._options.reviewer
5449af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org    else:
5459af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org      print "Please enter the email address of a V8 reviewer for your patch: ",
546ddf3811f8018dfe9e8ec7d1b8f4a8be1122fd767machenbach@chromium.org      self.DieNoManualMode("A reviewer must be specified in forced mode.")
5479af454f6b1c6a921ac79ba0b9a979c73adb2ca1emachenbach@chromium.org      reviewer = self.ReadLine()
5482c81ceb7f1e1ccf5f304be0646f4c1375941a7f2machenbach@chromium.org    self.GitUpload(reviewer, self._options.author, self._options.force_upload,
5492c81ceb7f1e1ccf5f304be0646f4c1375941a7f2machenbach@chromium.org                   bypass_hooks=self._options.bypass_upload_hooks)
550af9cfcbed5daf6e636e189bce451c6fafdbb127dmachenbach@chromium.org
551af9cfcbed5daf6e636e189bce451c6fafdbb127dmachenbach@chromium.org
5529e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.orgclass DetermineV8Sheriff(Step):
5539e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org  MESSAGE = "Determine the V8 sheriff for code review."
5549e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org
5559e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org  def RunStep(self):
5569e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org    self["sheriff"] = None
5579e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org    if not self._options.sheriff:  # pragma: no cover
5589e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org      return
5599e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org
5609e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org    try:
5619e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org      # The googlers mapping maps @google.com accounts to @chromium.org
5629e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org      # accounts.
5639e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org      googlers = imp.load_source('googlers_mapping',
5649e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org                                 self._options.googlers_mapping)
5659e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org      googlers = googlers.list_to_dict(googlers.get_list())
5669e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org    except:  # pragma: no cover
5679e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org      print "Skip determining sheriff without googler mapping."
5689e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org      return
5699e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org
5709e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org    # The sheriff determined by the rotation on the waterfall has a
5719e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org    # @google.com account.
5729e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org    url = "https://chromium-build.appspot.com/p/chromium/sheriff_v8.js"
5739e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org    match = re.match(r"document\.write\('(\w+)'\)", self.ReadURL(url))
5749e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org
5759e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org    # If "channel is sheriff", we can't match an account.
5769e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org    if match:
5779e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org      g_name = match.group(1)
5789e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org      self["sheriff"] = googlers.get(g_name + "@google.com",
5799e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org                                     g_name + "@chromium.org")
5809e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org      self._options.reviewer = self["sheriff"]
5819e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org      print "Found active sheriff: %s" % self["sheriff"]
5829e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org    else:
5839e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org      print "No active sheriff found."
5849e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org
5859e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org
58690dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.orgdef MakeStep(step_class=Step, number=0, state=None, config=None,
58790dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org             options=None, side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER):
58890dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org    # Allow to pass in empty dictionaries.
58990dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org    state = state if state is not None else {}
59090dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org    config = config if config is not None else {}
59190dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org
59290dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org    try:
59390dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org      message = step_class.MESSAGE
59490dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org    except AttributeError:
59590dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org      message = step_class.__name__
59690dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org
59706b2696801712948b665372a38f96b1f10be6997machenbach@chromium.org    return step_class(message, number=number, config=config,
59890dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org                      state=state, options=options,
59990dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org                      handler=side_effect_handler)
60090dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org
60190dca01eac542464c29011d239bf18f26e0b8f00machenbach@chromium.org
602bc176057ae476990672de915df235c9aeadc8521titzer@chromium.orgclass ScriptsBase(object):
603bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org  # TODO(machenbach): Move static config here.
60406b2696801712948b665372a38f96b1f10be6997machenbach@chromium.org  def __init__(self,
60506b2696801712948b665372a38f96b1f10be6997machenbach@chromium.org               config=None,
60606b2696801712948b665372a38f96b1f10be6997machenbach@chromium.org               side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER,
607bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org               state=None):
60806b2696801712948b665372a38f96b1f10be6997machenbach@chromium.org    self._config = config or self._Config()
609bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    self._side_effect_handler = side_effect_handler
610bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    self._state = state if state is not None else {}
611bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org
612bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org  def _Description(self):
613bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    return None
614bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org
615bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org  def _PrepareOptions(self, parser):
616bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    pass
617bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org
618bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org  def _ProcessOptions(self, options):
619bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    return True
620bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org
621486536df718553960f9700559e80e5b10b0d5994dslomov@chromium.org  def _Steps(self):  # pragma: no cover
622bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    raise Exception("Not implemented.")
623bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org
62406b2696801712948b665372a38f96b1f10be6997machenbach@chromium.org  def _Config(self):
62506b2696801712948b665372a38f96b1f10be6997machenbach@chromium.org    return {}
62606b2696801712948b665372a38f96b1f10be6997machenbach@chromium.org
627bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org  def MakeOptions(self, args=None):
628bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    parser = argparse.ArgumentParser(description=self._Description())
629bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    parser.add_argument("-a", "--author", default="",
630bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org                        help="The author email used for rietveld.")
6319aaa825cf89e1bcfece269a453300ebf4a26d64dmachenbach@chromium.org    parser.add_argument("--dry-run", default=False, action="store_true",
6329aaa825cf89e1bcfece269a453300ebf4a26d64dmachenbach@chromium.org                        help="Perform only read-only actions.")
6339e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org    parser.add_argument("-g", "--googlers-mapping",
6349e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org                        help="Path to the script mapping google accounts.")
635bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    parser.add_argument("-r", "--reviewer", default="",
636bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org                        help="The account name to be used for reviews.")
6379e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org    parser.add_argument("--sheriff", default=False, action="store_true",
6389e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org                        help=("Determine current sheriff to review CLs. On "
6399e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org                              "success, this will overwrite the reviewer "
6409e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org                              "option."))
64142ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org    parser.add_argument("--svn",
64242ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org                        help=("Optional full svn checkout for the commit."
64342ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org                              "The folder needs to be the svn root."))
64442ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org    parser.add_argument("--svn-config",
64542ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org                        help=("Optional folder used as svn --config-dir."))
646bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    parser.add_argument("-s", "--step",
6479e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org        help="Specify the step where to start work. Default: 0.",
6489e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org        default=0, type=int)
649bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    self._PrepareOptions(parser)
650bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org
651486536df718553960f9700559e80e5b10b0d5994dslomov@chromium.org    if args is None:  # pragma: no cover
652bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org      options = parser.parse_args()
653bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    else:
654bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org      options = parser.parse_args(args)
655bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org
656bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    # Process common options.
657486536df718553960f9700559e80e5b10b0d5994dslomov@chromium.org    if options.step < 0:  # pragma: no cover
658bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org      print "Bad step number %d" % options.step
659bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org      parser.print_help()
660bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org      return None
6619e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org    if options.sheriff and not options.googlers_mapping:  # pragma: no cover
6629e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org      print "To determine the current sheriff, requires the googler mapping"
6639e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org      parser.print_help()
6649e41f9ecf5042292a9efcb36e264b37621199553machenbach@chromium.org      return None
66542ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org    if options.svn and not options.svn_config:
66642ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org      print "Using pure svn for committing requires also --svn-config"
66742ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org      parser.print_help()
66842ed2fc449e83fab2ccbf1b769a5e83715c9d783machenbach@chromium.org      return None
669bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org
670bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    # Defaults for options, common to all scripts.
671bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    options.manual = getattr(options, "manual", True)
672bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    options.force = getattr(options, "force", False)
6732c81ceb7f1e1ccf5f304be0646f4c1375941a7f2machenbach@chromium.org    options.bypass_upload_hooks = False
674bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org
675bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    # Derived options.
676bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    options.requires_editor = not options.force
677bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    options.wait_for_lgtm = not options.force
678bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    options.force_readline_defaults = not options.manual
679bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    options.force_upload = not options.manual
680bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org
681bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    # Process script specific options.
682bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    if not self._ProcessOptions(options):
683bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org      parser.print_help()
684bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org      return None
685bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    return options
686bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org
687bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org  def RunSteps(self, step_classes, args=None):
688bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    options = self.MakeOptions(args)
689bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    if not options:
690bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org      return 1
691bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org
69206b2696801712948b665372a38f96b1f10be6997machenbach@chromium.org    state_file = "%s-state.json" % self._config["PERSISTFILE_BASENAME"]
693bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    if options.step == 0 and os.path.exists(state_file):
694bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org      os.remove(state_file)
695bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org
696bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    steps = []
697bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    for (number, step_class) in enumerate(step_classes):
698bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org      steps.append(MakeStep(step_class, number, self._state, self._config,
699bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org                            options, self._side_effect_handler))
700bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    for step in steps[options.step:]:
7014edebd5691ee147fa134ad8aaf6cc3c939831b93machenbach@chromium.org      if step.Run():
7021af4d9551ad496a28c342004b1a4e2a3840228f7machenbach@chromium.org        return 0
703bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    return 0
704bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org
705bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org  def Run(self, args=None):
706bc176057ae476990672de915df235c9aeadc8521titzer@chromium.org    return self.RunSteps(self._Steps(), args)
707