blob: c323542525b2acf44298ae40aaa81b42a546028c [file] [log] [blame]
#!/usr/bin/env python
# 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.
# for py2/py3 compatibility
from __future__ import print_function
import argparse
import os
import sys
import tempfile
import urllib2
from common_includes import *
class Preparation(Step):
MESSAGE = "Preparation."
def RunStep(self):
self.Git("fetch origin +refs/heads/*:refs/heads/*")
self.GitCheckout("origin/master")
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/master"))
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 master can be used to bump up major/minor at
# branch time.
self.GitCheckoutFile(VERSION_FILE, self.vc.RemoteMasterBranch())
self.ReadAndPersistVersion("master_")
master_version = self.ArrayToVersion("master_")
# Use the highest version from master or from tags to determine the new
# version.
authoritative_version = sorted(
[master_version, latest_version], key=SortingKey)[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_master"] = 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/master")
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\n\nTBR=%s" % (self["commit_title"], self._options.reviewer)
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,
message_file=self.Config("COMMITMSG_FILE"))
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/master")
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())