#!/usr/bin/env python3
# Copyright 2015 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import os
import sys
from common_includes import *

class Preparation(Step):
  MESSAGE = "Preparation."

  def RunStep(self):
    self.Git("fetch origin +refs/heads/*:refs/heads/*")
    self.GitCheckout("origin/main")
    self.DeleteBranch("work-branch")


class PrepareBranchRevision(Step):
  MESSAGE = "Check from which revision to branch off."

  def RunStep(self):
    self["push_hash"] = (self._options.revision or
                         self.GitLog(n=1, format="%H", branch="origin/main"))
    assert self["push_hash"]
    print("Release revision %s" % self["push_hash"])


class IncrementVersion(Step):
  MESSAGE = "Increment version number."

  def RunStep(self):
    latest_version = self.GetLatestVersion()

    # The version file on main can be used to bump up major/minor at
    # branch time.
    self.GitCheckoutFile(VERSION_FILE, self.vc.RemoteMainBranch())
    self.ReadAndPersistVersion("main_")
    main_version = self.ArrayToVersion("main_")

    # Use the highest version from main or from tags to determine the new
    # version.
    authoritative_version = sorted(
        [main_version, latest_version], key=LooseVersion)[1]
    self.StoreVersion(authoritative_version, "authoritative_")

    # Variables prefixed with 'new_' contain the new version numbers for the
    # ongoing candidates push.
    self["new_major"] = self["authoritative_major"]
    self["new_minor"] = self["authoritative_minor"]
    self["new_build"] = str(int(self["authoritative_build"]) + 1)

    # Make sure patch level is 0 in a new push.
    self["new_patch"] = "0"

    # The new version is not a candidate.
    self["new_candidate"] = "0"

    self["version"] = "%s.%s.%s" % (self["new_major"],
                                    self["new_minor"],
                                    self["new_build"])

    print ("Incremented version to %s" % self["version"])


class DetectLastRelease(Step):
  MESSAGE = "Detect commit ID of last release base."

  def RunStep(self):
    self["last_push_main"] = self.GetLatestReleaseBase()


class DeleteBranchRef(Step):
  MESSAGE = "Delete branch ref."

  def RunStep(self):
    cmd = "push origin :refs/heads/%s" % self["version"]
    if self._options.dry_run:
      print("Dry run. Command:\ngit %s" % cmd)
    else:
      try:
        self.Git(cmd)
      except Exception:
        # Be forgiving if branch ref does not exist.
        pass


class PushBranchRef(Step):
  MESSAGE = "Create branch ref."

  def RunStep(self):
    cmd = "push origin %s:refs/heads/%s" % (self["push_hash"], self["version"])
    if self._options.dry_run:
      print("Dry run. Command:\ngit %s" % cmd)
    else:
      self.Git(cmd)


class MakeBranch(Step):
  MESSAGE = "Create the branch."

  def RunStep(self):
    self.Git("reset --hard origin/main")
    self.Git("new-branch work-branch --upstream origin/%s" % self["version"])
    self.GitCheckoutFile(VERSION_FILE, self["latest_version"])


class SetVersion(Step):
  MESSAGE = "Set correct version for candidates."

  def RunStep(self):
    self.SetVersion(os.path.join(self.default_cwd, VERSION_FILE), "new_")


class EnableMergeWatchlist(Step):
  MESSAGE = "Enable watchlist entry for merge notifications."

  def RunStep(self):
    old_watchlist_content = FileToText(os.path.join(self.default_cwd,
                                                    WATCHLISTS_FILE))
    new_watchlist_content = re.sub("(# 'v8-merges@googlegroups\.com',)",
                                   "'v8-merges@googlegroups.com',",
                                   old_watchlist_content)
    TextToFile(new_watchlist_content, os.path.join(self.default_cwd,
                                                   WATCHLISTS_FILE))


class CommitBranch(Step):
  MESSAGE = "Commit version to new branch."

  def RunStep(self):
    self["commit_title"] = "Version %s" % self["version"]
    text = "%s" % (self["commit_title"])
    TextToFile(text, self.Config("COMMITMSG_FILE"))

    self.GitCommit(file_name=self.Config("COMMITMSG_FILE"))


class LandBranch(Step):
  MESSAGE = "Upload and land changes."

  def RunStep(self):
    if self._options.dry_run:
      print("Dry run - upload CL.")
    else:
      self.GitUpload(force=True,
                     bypass_hooks=True,
                     no_autocc=True,
                     set_bot_commit=True,
                     message_file=self.Config("COMMITMSG_FILE"))
    # TODO(crbug.com/1176141): This might need to go through CQ.
    # We'd need to wait for it to land and then tag it.
    cmd = "cl land --bypass-hooks -f"
    if self._options.dry_run:
      print("Dry run. Command:\ngit %s" % cmd)
    else:
      self.Git(cmd)

    os.remove(self.Config("COMMITMSG_FILE"))


class TagRevision(Step):
  MESSAGE = "Tag the new revision."

  def RunStep(self):
    if self._options.dry_run:
      print ("Dry run. Tagging \"%s\" with %s" %
             (self["commit_title"], self["version"]))
    else:
      self.vc.Tag(self["version"],
                  "origin/%s" % self["version"],
                  self["commit_title"])


class CleanUp(Step):
  MESSAGE = "Done!"

  def RunStep(self):
    print("Congratulations, you have successfully created version %s."
          % self["version"])

    self.GitCheckout("origin/main")
    self.DeleteBranch("work-branch")
    self.Git("gc")


class CreateRelease(ScriptsBase):
  def _PrepareOptions(self, parser):
    group = parser.add_mutually_exclusive_group()
    group.add_argument("-f", "--force",
                      help="Don't prompt the user.",
                      default=True, action="store_true")
    group.add_argument("-m", "--manual",
                      help="Prompt the user at every important step.",
                      default=False, action="store_true")
    parser.add_argument("-R", "--revision",
                        help="The git commit ID to push (defaults to HEAD).")

  def _ProcessOptions(self, options):  # pragma: no cover
    if not options.author or not options.reviewer:
      print("Reviewer (-r) and author (-a) are required.")
      return False
    return True

  def _Config(self):
    return {
      "PERSISTFILE_BASENAME": "/tmp/create-releases-tempfile",
      "COMMITMSG_FILE": "/tmp/v8-create-releases-tempfile-commitmsg",
    }

  def _Steps(self):
    return [
      Preparation,
      PrepareBranchRevision,
      IncrementVersion,
      DetectLastRelease,
      DeleteBranchRef,
      PushBranchRef,
      MakeBranch,
      SetVersion,
      EnableMergeWatchlist,
      CommitBranch,
      LandBranch,
      TagRevision,
      CleanUp,
    ]


if __name__ == "__main__":  # pragma: no cover
  sys.exit(CreateRelease().Run())
