11cb0ef41Sopenharmony_ci#!/usr/bin/env python3
21cb0ef41Sopenharmony_ci# Copyright 2014 the V8 project authors. All rights reserved.
31cb0ef41Sopenharmony_ci# Redistribution and use in source and binary forms, with or without
41cb0ef41Sopenharmony_ci# modification, are permitted provided that the following conditions are
51cb0ef41Sopenharmony_ci# met:
61cb0ef41Sopenharmony_ci#
71cb0ef41Sopenharmony_ci#     * Redistributions of source code must retain the above copyright
81cb0ef41Sopenharmony_ci#       notice, this list of conditions and the following disclaimer.
91cb0ef41Sopenharmony_ci#     * Redistributions in binary form must reproduce the above
101cb0ef41Sopenharmony_ci#       copyright notice, this list of conditions and the following
111cb0ef41Sopenharmony_ci#       disclaimer in the documentation and/or other materials provided
121cb0ef41Sopenharmony_ci#       with the distribution.
131cb0ef41Sopenharmony_ci#     * Neither the name of Google Inc. nor the names of its
141cb0ef41Sopenharmony_ci#       contributors may be used to endorse or promote products derived
151cb0ef41Sopenharmony_ci#       from this software without specific prior written permission.
161cb0ef41Sopenharmony_ci#
171cb0ef41Sopenharmony_ci# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
181cb0ef41Sopenharmony_ci# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
191cb0ef41Sopenharmony_ci# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
201cb0ef41Sopenharmony_ci# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
211cb0ef41Sopenharmony_ci# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
221cb0ef41Sopenharmony_ci# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
231cb0ef41Sopenharmony_ci# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
241cb0ef41Sopenharmony_ci# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
251cb0ef41Sopenharmony_ci# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
261cb0ef41Sopenharmony_ci# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
271cb0ef41Sopenharmony_ci# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
281cb0ef41Sopenharmony_ci
291cb0ef41Sopenharmony_ciimport re
301cb0ef41Sopenharmony_ci
311cb0ef41Sopenharmony_ciSHA1_RE = re.compile('^[a-fA-F0-9]{40}$')
321cb0ef41Sopenharmony_ciROLL_DEPS_GIT_SVN_ID_RE = re.compile('^git-svn-id: .*@([0-9]+) .*$')
331cb0ef41Sopenharmony_ci
341cb0ef41Sopenharmony_ci# Regular expression that matches a single commit footer line.
351cb0ef41Sopenharmony_ciCOMMIT_FOOTER_ENTRY_RE = re.compile(r'([^:]+):\s*(.*)')
361cb0ef41Sopenharmony_ci
371cb0ef41Sopenharmony_ci# Footer metadata key for commit position.
381cb0ef41Sopenharmony_ciCOMMIT_POSITION_FOOTER_KEY = 'Cr-Commit-Position'
391cb0ef41Sopenharmony_ci
401cb0ef41Sopenharmony_ci# Regular expression to parse a commit position
411cb0ef41Sopenharmony_ciCOMMIT_POSITION_RE = re.compile(r'(.+)@\{#(\d+)\}')
421cb0ef41Sopenharmony_ci
431cb0ef41Sopenharmony_ci# Key for the 'git-svn' ID metadata commit footer entry.
441cb0ef41Sopenharmony_ciGIT_SVN_ID_FOOTER_KEY = 'git-svn-id'
451cb0ef41Sopenharmony_ci
461cb0ef41Sopenharmony_ci# e.g., git-svn-id: https://v8.googlecode.com/svn/trunk@23117
471cb0ef41Sopenharmony_ci#     ce2b1a6d-e550-0410-aec6-3dcde31c8c00
481cb0ef41Sopenharmony_ciGIT_SVN_ID_RE = re.compile(r'[^@]+@(\d+)\s+(?:[a-zA-Z0-9\-]+)')
491cb0ef41Sopenharmony_ci
501cb0ef41Sopenharmony_ci
511cb0ef41Sopenharmony_ci# Copied from bot_update.py.
521cb0ef41Sopenharmony_cidef GetCommitMessageFooterMap(message):
531cb0ef41Sopenharmony_ci  """Returns: (dict) A dictionary of commit message footer entries.
541cb0ef41Sopenharmony_ci  """
551cb0ef41Sopenharmony_ci  footers = {}
561cb0ef41Sopenharmony_ci
571cb0ef41Sopenharmony_ci  # Extract the lines in the footer block.
581cb0ef41Sopenharmony_ci  lines = []
591cb0ef41Sopenharmony_ci  for line in message.strip().splitlines():
601cb0ef41Sopenharmony_ci    line = line.strip()
611cb0ef41Sopenharmony_ci    if len(line) == 0:
621cb0ef41Sopenharmony_ci      del(lines[:])
631cb0ef41Sopenharmony_ci      continue
641cb0ef41Sopenharmony_ci    lines.append(line)
651cb0ef41Sopenharmony_ci
661cb0ef41Sopenharmony_ci  # Parse the footer
671cb0ef41Sopenharmony_ci  for line in lines:
681cb0ef41Sopenharmony_ci    m = COMMIT_FOOTER_ENTRY_RE.match(line)
691cb0ef41Sopenharmony_ci    if not m:
701cb0ef41Sopenharmony_ci      # If any single line isn't valid, continue anyway for compatibility with
711cb0ef41Sopenharmony_ci      # Gerrit (which itself uses JGit for this).
721cb0ef41Sopenharmony_ci      continue
731cb0ef41Sopenharmony_ci    footers[m.group(1)] = m.group(2).strip()
741cb0ef41Sopenharmony_ci  return footers
751cb0ef41Sopenharmony_ci
761cb0ef41Sopenharmony_ci
771cb0ef41Sopenharmony_ciclass GitFailedException(Exception):
781cb0ef41Sopenharmony_ci  pass
791cb0ef41Sopenharmony_ci
801cb0ef41Sopenharmony_ci
811cb0ef41Sopenharmony_cidef Strip(f):
821cb0ef41Sopenharmony_ci  def new_f(*args, **kwargs):
831cb0ef41Sopenharmony_ci    result = f(*args, **kwargs)
841cb0ef41Sopenharmony_ci    if result is None:
851cb0ef41Sopenharmony_ci      return result
861cb0ef41Sopenharmony_ci    else:
871cb0ef41Sopenharmony_ci      return result.strip()
881cb0ef41Sopenharmony_ci  return new_f
891cb0ef41Sopenharmony_ci
901cb0ef41Sopenharmony_ci
911cb0ef41Sopenharmony_cidef MakeArgs(l):
921cb0ef41Sopenharmony_ci  """['-a', '', 'abc', ''] -> '-a abc'"""
931cb0ef41Sopenharmony_ci  return " ".join(filter(None, l))
941cb0ef41Sopenharmony_ci
951cb0ef41Sopenharmony_ci
961cb0ef41Sopenharmony_cidef Quoted(s):
971cb0ef41Sopenharmony_ci  return "\"%s\"" % s
981cb0ef41Sopenharmony_ci
991cb0ef41Sopenharmony_ci
1001cb0ef41Sopenharmony_ciclass GitRecipesMixin(object):
1011cb0ef41Sopenharmony_ci  def GitIsWorkdirClean(self, **kwargs):
1021cb0ef41Sopenharmony_ci    return self.Git("status -s -uno", **kwargs).strip() == ""
1031cb0ef41Sopenharmony_ci
1041cb0ef41Sopenharmony_ci  @Strip
1051cb0ef41Sopenharmony_ci  def GitBranch(self, **kwargs):
1061cb0ef41Sopenharmony_ci    return self.Git("branch", **kwargs)
1071cb0ef41Sopenharmony_ci
1081cb0ef41Sopenharmony_ci  def GitCreateBranch(self, name, remote="", **kwargs):
1091cb0ef41Sopenharmony_ci    assert name
1101cb0ef41Sopenharmony_ci    remote_args = ["--upstream", remote] if remote else []
1111cb0ef41Sopenharmony_ci    self.Git(MakeArgs(["new-branch", name] + remote_args), **kwargs)
1121cb0ef41Sopenharmony_ci
1131cb0ef41Sopenharmony_ci  def GitDeleteBranch(self, name, **kwargs):
1141cb0ef41Sopenharmony_ci    assert name
1151cb0ef41Sopenharmony_ci    self.Git(MakeArgs(["branch -D", name]), **kwargs)
1161cb0ef41Sopenharmony_ci
1171cb0ef41Sopenharmony_ci  def GitReset(self, name, **kwargs):
1181cb0ef41Sopenharmony_ci    assert name
1191cb0ef41Sopenharmony_ci    self.Git(MakeArgs(["reset --hard", name]), **kwargs)
1201cb0ef41Sopenharmony_ci
1211cb0ef41Sopenharmony_ci  def GitStash(self, **kwargs):
1221cb0ef41Sopenharmony_ci    self.Git(MakeArgs(["stash"]), **kwargs)
1231cb0ef41Sopenharmony_ci
1241cb0ef41Sopenharmony_ci  def GitRemotes(self, **kwargs):
1251cb0ef41Sopenharmony_ci    return map(str.strip,
1261cb0ef41Sopenharmony_ci               self.Git(MakeArgs(["branch -r"]), **kwargs).splitlines())
1271cb0ef41Sopenharmony_ci
1281cb0ef41Sopenharmony_ci  def GitCheckout(self, name, **kwargs):
1291cb0ef41Sopenharmony_ci    assert name
1301cb0ef41Sopenharmony_ci    self.Git(MakeArgs(["checkout -f", name]), **kwargs)
1311cb0ef41Sopenharmony_ci
1321cb0ef41Sopenharmony_ci  def GitCheckoutFile(self, name, branch_or_hash, **kwargs):
1331cb0ef41Sopenharmony_ci    assert name
1341cb0ef41Sopenharmony_ci    assert branch_or_hash
1351cb0ef41Sopenharmony_ci    self.Git(MakeArgs(["checkout -f", branch_or_hash, "--", name]), **kwargs)
1361cb0ef41Sopenharmony_ci
1371cb0ef41Sopenharmony_ci  def GitCheckoutFileSafe(self, name, branch_or_hash, **kwargs):
1381cb0ef41Sopenharmony_ci    try:
1391cb0ef41Sopenharmony_ci      self.GitCheckoutFile(name, branch_or_hash, **kwargs)
1401cb0ef41Sopenharmony_ci    except GitFailedException:  # pragma: no cover
1411cb0ef41Sopenharmony_ci      # The file doesn't exist in that revision.
1421cb0ef41Sopenharmony_ci      return False
1431cb0ef41Sopenharmony_ci    return True
1441cb0ef41Sopenharmony_ci
1451cb0ef41Sopenharmony_ci  def GitChangedFiles(self, git_hash, **kwargs):
1461cb0ef41Sopenharmony_ci    assert git_hash
1471cb0ef41Sopenharmony_ci    try:
1481cb0ef41Sopenharmony_ci      files = self.Git(MakeArgs(["diff --name-only",
1491cb0ef41Sopenharmony_ci                                 git_hash,
1501cb0ef41Sopenharmony_ci                                 "%s^" % git_hash]), **kwargs)
1511cb0ef41Sopenharmony_ci      return map(str.strip, files.splitlines())
1521cb0ef41Sopenharmony_ci    except GitFailedException:  # pragma: no cover
1531cb0ef41Sopenharmony_ci      # Git fails using "^" at branch roots.
1541cb0ef41Sopenharmony_ci      return []
1551cb0ef41Sopenharmony_ci
1561cb0ef41Sopenharmony_ci
1571cb0ef41Sopenharmony_ci  @Strip
1581cb0ef41Sopenharmony_ci  def GitCurrentBranch(self, **kwargs):
1591cb0ef41Sopenharmony_ci    for line in self.Git("status -s -b -uno", **kwargs).strip().splitlines():
1601cb0ef41Sopenharmony_ci      match = re.match(r"^## (.+)", line)
1611cb0ef41Sopenharmony_ci      if match: return match.group(1)
1621cb0ef41Sopenharmony_ci    raise Exception("Couldn't find curent branch.")  # pragma: no cover
1631cb0ef41Sopenharmony_ci
1641cb0ef41Sopenharmony_ci  @Strip
1651cb0ef41Sopenharmony_ci  def GitLog(self, n=0, format="", grep="", git_hash="", parent_hash="",
1661cb0ef41Sopenharmony_ci             branch="", path=None, reverse=False, **kwargs):
1671cb0ef41Sopenharmony_ci    assert not (git_hash and parent_hash)
1681cb0ef41Sopenharmony_ci    args = ["log"]
1691cb0ef41Sopenharmony_ci    if n > 0:
1701cb0ef41Sopenharmony_ci      args.append("-%d" % n)
1711cb0ef41Sopenharmony_ci    if format:
1721cb0ef41Sopenharmony_ci      args.append("--format=%s" % format)
1731cb0ef41Sopenharmony_ci    if grep:
1741cb0ef41Sopenharmony_ci      args.append("--grep=\"%s\"" % grep.replace("\"", "\\\""))
1751cb0ef41Sopenharmony_ci    if reverse:
1761cb0ef41Sopenharmony_ci      args.append("--reverse")
1771cb0ef41Sopenharmony_ci    if git_hash:
1781cb0ef41Sopenharmony_ci      args.append(git_hash)
1791cb0ef41Sopenharmony_ci    if parent_hash:
1801cb0ef41Sopenharmony_ci      args.append("%s^" % parent_hash)
1811cb0ef41Sopenharmony_ci    args.append(branch)
1821cb0ef41Sopenharmony_ci    if path:
1831cb0ef41Sopenharmony_ci      args.extend(["--", path])
1841cb0ef41Sopenharmony_ci    return self.Git(MakeArgs(args), **kwargs)
1851cb0ef41Sopenharmony_ci
1861cb0ef41Sopenharmony_ci  def GitShowFile(self, refspec, path, **kwargs):
1871cb0ef41Sopenharmony_ci    assert refspec
1881cb0ef41Sopenharmony_ci    assert path
1891cb0ef41Sopenharmony_ci    return self.Git(MakeArgs(["show", "%s:%s" % (refspec, path)]), **kwargs)
1901cb0ef41Sopenharmony_ci
1911cb0ef41Sopenharmony_ci  def GitGetPatch(self, git_hash, **kwargs):
1921cb0ef41Sopenharmony_ci    assert git_hash
1931cb0ef41Sopenharmony_ci    return self.Git(MakeArgs(["log", "-1", "-p", git_hash]), **kwargs)
1941cb0ef41Sopenharmony_ci
1951cb0ef41Sopenharmony_ci  # TODO(machenbach): Unused? Remove.
1961cb0ef41Sopenharmony_ci  def GitAdd(self, name, **kwargs):
1971cb0ef41Sopenharmony_ci    assert name
1981cb0ef41Sopenharmony_ci    self.Git(MakeArgs(["add", Quoted(name)]), **kwargs)
1991cb0ef41Sopenharmony_ci
2001cb0ef41Sopenharmony_ci  def GitApplyPatch(self, patch_file, reverse=False, **kwargs):
2011cb0ef41Sopenharmony_ci    assert patch_file
2021cb0ef41Sopenharmony_ci    args = ["apply --index --reject"]
2031cb0ef41Sopenharmony_ci    if reverse:
2041cb0ef41Sopenharmony_ci      args.append("--reverse")
2051cb0ef41Sopenharmony_ci    args.append(Quoted(patch_file))
2061cb0ef41Sopenharmony_ci    self.Git(MakeArgs(args), **kwargs)
2071cb0ef41Sopenharmony_ci
2081cb0ef41Sopenharmony_ci  def GitUpload(self, reviewer="", force=False, cq=False,
2091cb0ef41Sopenharmony_ci                cq_dry_run=False, set_bot_commit=False, bypass_hooks=False,
2101cb0ef41Sopenharmony_ci                cc="", tbr_reviewer="", no_autocc=False, message_file=None,
2111cb0ef41Sopenharmony_ci                **kwargs):
2121cb0ef41Sopenharmony_ci    args = ["cl upload --send-mail"]
2131cb0ef41Sopenharmony_ci    if reviewer:
2141cb0ef41Sopenharmony_ci      args += ["-r", Quoted(reviewer)]
2151cb0ef41Sopenharmony_ci    if tbr_reviewer:
2161cb0ef41Sopenharmony_ci      args += ["--tbrs", Quoted(tbr_reviewer)]
2171cb0ef41Sopenharmony_ci    if force:
2181cb0ef41Sopenharmony_ci      args.append("-f")
2191cb0ef41Sopenharmony_ci    if cq:
2201cb0ef41Sopenharmony_ci      args.append("--use-commit-queue")
2211cb0ef41Sopenharmony_ci    if cq_dry_run:
2221cb0ef41Sopenharmony_ci      args.append("--cq-dry-run")
2231cb0ef41Sopenharmony_ci    if set_bot_commit:
2241cb0ef41Sopenharmony_ci      args.append("--set-bot-commit")
2251cb0ef41Sopenharmony_ci    if bypass_hooks:
2261cb0ef41Sopenharmony_ci      args.append("--bypass-hooks")
2271cb0ef41Sopenharmony_ci    if no_autocc:
2281cb0ef41Sopenharmony_ci      args.append("--no-autocc")
2291cb0ef41Sopenharmony_ci    if cc:
2301cb0ef41Sopenharmony_ci      args += ["--cc", Quoted(cc)]
2311cb0ef41Sopenharmony_ci    if message_file:
2321cb0ef41Sopenharmony_ci      args += ["--message-file", Quoted(message_file)]
2331cb0ef41Sopenharmony_ci    # TODO(machenbach): Check output in forced mode. Verify that all required
2341cb0ef41Sopenharmony_ci    # base files were uploaded, if not retry.
2351cb0ef41Sopenharmony_ci    self.Git(MakeArgs(args), pipe=False, **kwargs)
2361cb0ef41Sopenharmony_ci
2371cb0ef41Sopenharmony_ci  def GitCommit(self, message="", file_name="", author=None, **kwargs):
2381cb0ef41Sopenharmony_ci    assert message or file_name
2391cb0ef41Sopenharmony_ci    args = ["commit"]
2401cb0ef41Sopenharmony_ci    if file_name:
2411cb0ef41Sopenharmony_ci      args += ["-aF", Quoted(file_name)]
2421cb0ef41Sopenharmony_ci    if message:
2431cb0ef41Sopenharmony_ci      args += ["-am", Quoted(message)]
2441cb0ef41Sopenharmony_ci    if author:
2451cb0ef41Sopenharmony_ci      args += ["--author", "\"%s <%s>\"" % (author, author)]
2461cb0ef41Sopenharmony_ci    self.Git(MakeArgs(args), **kwargs)
2471cb0ef41Sopenharmony_ci
2481cb0ef41Sopenharmony_ci  def GitPresubmit(self, **kwargs):
2491cb0ef41Sopenharmony_ci    self.Git("cl presubmit", "PRESUBMIT_TREE_CHECK=\"skip\"", **kwargs)
2501cb0ef41Sopenharmony_ci
2511cb0ef41Sopenharmony_ci  def GitCLLand(self, **kwargs):
2521cb0ef41Sopenharmony_ci    self.Git(
2531cb0ef41Sopenharmony_ci        "cl land -f --bypass-hooks", retry_on=lambda x: x is None, **kwargs)
2541cb0ef41Sopenharmony_ci
2551cb0ef41Sopenharmony_ci  def GitDiff(self, loc1, loc2, **kwargs):
2561cb0ef41Sopenharmony_ci    return self.Git(MakeArgs(["diff", loc1, loc2]), **kwargs)
2571cb0ef41Sopenharmony_ci
2581cb0ef41Sopenharmony_ci  def GitPull(self, **kwargs):
2591cb0ef41Sopenharmony_ci    self.Git("pull", **kwargs)
2601cb0ef41Sopenharmony_ci
2611cb0ef41Sopenharmony_ci  def GitFetchOrigin(self, *refspecs, **kwargs):
2621cb0ef41Sopenharmony_ci    self.Git(MakeArgs(["fetch", "origin"] + list(refspecs)), **kwargs)
2631cb0ef41Sopenharmony_ci
2641cb0ef41Sopenharmony_ci  @Strip
2651cb0ef41Sopenharmony_ci  # Copied from bot_update.py and modified for svn-like numbers only.
2661cb0ef41Sopenharmony_ci  def GetCommitPositionNumber(self, git_hash, **kwargs):
2671cb0ef41Sopenharmony_ci    """Dumps the 'git' log for a specific revision and parses out the commit
2681cb0ef41Sopenharmony_ci    position number.
2691cb0ef41Sopenharmony_ci
2701cb0ef41Sopenharmony_ci    If a commit position metadata key is found, its number will be returned.
2711cb0ef41Sopenharmony_ci
2721cb0ef41Sopenharmony_ci    Otherwise, we will search for a 'git-svn' metadata entry. If one is found,
2731cb0ef41Sopenharmony_ci    its SVN revision value is returned.
2741cb0ef41Sopenharmony_ci    """
2751cb0ef41Sopenharmony_ci    git_log = self.GitLog(format='%B', n=1, git_hash=git_hash, **kwargs)
2761cb0ef41Sopenharmony_ci    footer_map = GetCommitMessageFooterMap(git_log)
2771cb0ef41Sopenharmony_ci
2781cb0ef41Sopenharmony_ci    # Search for commit position metadata
2791cb0ef41Sopenharmony_ci    value = footer_map.get(COMMIT_POSITION_FOOTER_KEY)
2801cb0ef41Sopenharmony_ci    if value:
2811cb0ef41Sopenharmony_ci      match = COMMIT_POSITION_RE.match(value)
2821cb0ef41Sopenharmony_ci      if match:
2831cb0ef41Sopenharmony_ci        return match.group(2)
2841cb0ef41Sopenharmony_ci
2851cb0ef41Sopenharmony_ci    # Extract the svn revision from 'git-svn' metadata
2861cb0ef41Sopenharmony_ci    value = footer_map.get(GIT_SVN_ID_FOOTER_KEY)
2871cb0ef41Sopenharmony_ci    if value:
2881cb0ef41Sopenharmony_ci      match = GIT_SVN_ID_RE.match(value)
2891cb0ef41Sopenharmony_ci      if match:
2901cb0ef41Sopenharmony_ci        return match.group(1)
2911cb0ef41Sopenharmony_ci    raise GitFailedException("Couldn't determine commit position for %s" %
2921cb0ef41Sopenharmony_ci                             git_hash)
2931cb0ef41Sopenharmony_ci
2941cb0ef41Sopenharmony_ci  def GitGetHashOfTag(self, tag_name, **kwargs):
2951cb0ef41Sopenharmony_ci    return self.Git("rev-list -1 " + tag_name).strip().encode("ascii", "ignore")
296