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