[infra] More work on chrome_release_branch

- Actually create the new branch
- Create the CL to update the Chrome milestone

Bug: skia:8932
Change-Id: If49a5b8893b90d0285b3b576c5af94e42d40f803
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/208671
Commit-Queue: Eric Boren <borenet@google.com>
Reviewed-by: Ben Wagner aka dogben <benjaminwagner@google.com>
Reviewed-by: Heather Miller <hcm@google.com>
diff --git a/infra/bots/git_utils.py b/infra/bots/git_utils.py
index 8e623f7..4269ae8 100644
--- a/infra/bots/git_utils.py
+++ b/infra/bots/git_utils.py
@@ -99,7 +99,7 @@
 class NewGitCheckout(utils.tmp_dir):
   """Creates a new local checkout of a Git repository."""
 
-  def __init__(self, repository, commit='HEAD'):
+  def __init__(self, repository, local=None):
     """Set parameters for this local copy of a Git repository.
 
     Because this is a new checkout, rather than a reference to an existing
@@ -116,12 +116,14 @@
       repository: URL of the remote repository (e.g.,
           'https://skia.googlesource.com/common') or path to a local repository
           (e.g., '/path/to/repo/.git') to check out a copy of
-      commit: commit hash, branch, or tag within refspec, indicating what point
-          to update the local checkout to
+      local: optional path to an existing copy of the remote repo on local disk.
+          If provided, the initial clone is performed with the local copy as the
+          upstream, then the upstream is switched to the remote repo and the
+          new copy is updated from there.
     """
     super(NewGitCheckout, self).__init__()
     self._repository = repository
-    self._commit = commit
+    self._local = local
 
   @property
   def root(self):
@@ -134,5 +136,14 @@
     Uses the parameters that were passed into the constructor.
     """
     super(NewGitCheckout, self).__enter__()
-    subprocess.check_output(args=['git', 'clone', self._repository, self.root])
+    remote = self._repository
+    if self._local:
+      remote = self._local
+    subprocess.check_output(args=['git', 'clone', remote, self.root])
+    if self._local:
+      subprocess.check_call([
+          'git', 'remote', 'set-url', 'origin', self._repository])
+      subprocess.check_call(['git', 'remote', 'update'])
+      subprocess.check_call(['git', 'checkout', 'master'])
+      subprocess.check_call(['git', 'reset', '--hard', 'origin/master'])
     return self
diff --git a/tools/chrome_release_branch.py b/tools/chrome_release_branch.py
index 7fba18b..91205ea 100644
--- a/tools/chrome_release_branch.py
+++ b/tools/chrome_release_branch.py
@@ -6,39 +6,75 @@
 # found in the LICENSE file.
 
 
+import os
+import re
 import subprocess
 import sys
 
 from infra import git
 from infra import go
 
+_TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
+_REPO_ROOT = os.path.realpath(os.path.join(_TOOLS_DIR, os.pardir))
+_INFRA_BOTS = os.path.join(_REPO_ROOT, 'infra', 'bots')
+sys.path.insert(0, _INFRA_BOTS)
+import git_utils
+
 
 REFS_HEADS_PREFIX = 'refs/heads/'
 CHROME_REF_PREFIX = REFS_HEADS_PREFIX + 'chrome/m'
+SK_MILESTONE_H = os.path.join('include', 'core', 'SkMilestone.h')
+SK_MILESTONE_TMPL = r'#define SK_MILESTONE %s'
+SK_MILESTONE_RE = SK_MILESTONE_TMPL % r'(\d+)'
+SKIA_REPO = 'https://skia.googlesource.com/skia.git'
 SUPPORTED_CHROME_BRANCHES = 2  # Per infra policy; see skbug.com/8940
+UPDATE_MILESTONE_COMMIT_MSG = '''Update Skia milestone to %d'''
 
 
-def get_chrome_branches():
-  '''Return all Chrome milestone branches as tuples of (milestone, ref).'''
-  refs = git.git('ls-remote', 'origin', 'refs/heads/*')
-  chrome_branches = []
-  for line in refs.splitlines():
-    ref = line.split()[1]
-    if ref.startswith(CHROME_REF_PREFIX):
-      m = int(ref[len(CHROME_REF_PREFIX):])
-      chrome_branches.append((m, ref))
-  chrome_branches.sort(reverse=True)
-  return chrome_branches
+def get_current_milestone():
+  '''Read SkMilestone.h and parse out the current milestone.'''
+  sk_milestone = os.path.join(_REPO_ROOT, SK_MILESTONE_H)
+  with open(sk_milestone, 'r') as f:
+    contents = f.read()
+  for line in contents.splitlines():
+    m = re.match(SK_MILESTONE_RE, line)
+    if m:
+      return int(m.groups()[0])
+  print >> sys.stderr, (
+      'Failed to parse %s; has the format changed?' % SK_MILESTONE_H)
+  sys.exit(1)
 
 
-def main():
+def create_new_branch(new_branch, branch_at):
+  '''Create a temporary checkout of the repo, create the new branch and push.'''
+  b = new_branch[len(REFS_HEADS_PREFIX):]
+  with git_utils.NewGitCheckout(SKIA_REPO, local=_REPO_ROOT):
+    git.git('checkout', '-b', b)
+    git.git('reset', '--hard', branch_at)
+    git.git('push', '--set-upstream', 'origin', b)
+
+
+def update_milestone(m):
+  '''Update SkMilestone.h to match the given milestone number.'''
+  with git_utils.NewGitCheckout(SKIA_REPO, local=_REPO_ROOT):
+    with git_utils.GitBranch(
+        'update_milestone', UPDATE_MILESTONE_COMMIT_MSG % m):
+      with open(SK_MILESTONE_H, 'r+') as f:
+        contents = re.sub(
+            SK_MILESTONE_RE, SK_MILESTONE_TMPL % str(m), f.read(), flags=re.M)
+        f.seek(0)
+        f.write(contents)
+        f.truncate()
+      git.git('diff')
+
+
+def update_infra_config(old_branch, new_branch):
+  '''Create a CL to add infra support for the new branch and remove the old.'''
   owner = git.git('config', 'user.email').rstrip()
   if not owner:
-    print >> sys.stderr, 'No configured git user; please run "git config user.email <your email>".'
+    print >> sys.stderr, ('No configured git user; please run '
+                          '"git config user.email <your email>".')
     sys.exit(1)
-  branches = get_chrome_branches()
-  new_branch = branches[0][1][len(REFS_HEADS_PREFIX):]
-  old_branch = branches[SUPPORTED_CHROME_BRANCHES][1][len(REFS_HEADS_PREFIX):]
   go.get(go.INFRA_GO+'/go/supported_branches/cmd/new-branch')
   subprocess.check_call(['new-branch',
                          '--branch', new_branch,
@@ -46,5 +82,20 @@
                          '--owner', owner])
 
 
+def main():
+  if len(sys.argv) != 2:
+    print >> sys.stderr, 'Usage: %s <commit hash for branch>' % sys.argv[0]
+    sys.exit(1)
+  branch_at = sys.argv[1]
+  m = get_current_milestone()
+  new_branch = '%s%d' % (CHROME_REF_PREFIX, m)
+  old_branch = '%s%d' % (CHROME_REF_PREFIX, m-SUPPORTED_CHROME_BRANCHES)
+  print 'Creating branch %s and removing support (eg. CQ) for %s' % (
+      new_branch, old_branch)
+  create_new_branch(new_branch, branch_at)
+  update_milestone(m+1)
+  update_infra_config(old_branch, new_branch)
+
+
 if __name__ == '__main__':
   main()