blob: bdc94ebd09164afbacd7232e60b4b7e4e7d1c4cd [file] [log] [blame]
Ben Murdoch014dc512016-03-22 12:00:34 +00001#!/usr/bin/env python
2# Copyright 2014 the V8 project authors. All rights reserved.
3# Redistribution and use in source and binary forms, with or without
4# modification, are permitted provided that the following conditions are
5# met:
6#
7# * Redistributions of source code must retain the above copyright
8# notice, this list of conditions and the following disclaimer.
9# * Redistributions in binary form must reproduce the above
10# copyright notice, this list of conditions and the following
11# disclaimer in the documentation and/or other materials provided
12# with the distribution.
13# * Neither the name of Google Inc. nor the names of its
14# contributors may be used to endorse or promote products derived
15# from this software without specific prior written permission.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29import argparse
30from collections import OrderedDict
31import sys
32
33from common_includes import *
34
35def IsSvnNumber(rev):
36 return rev.isdigit() and len(rev) < 8
37
38class Preparation(Step):
39 MESSAGE = "Preparation."
40
41 def RunStep(self):
42 if os.path.exists(self.Config("ALREADY_MERGING_SENTINEL_FILE")):
43 if self._options.force:
44 os.remove(self.Config("ALREADY_MERGING_SENTINEL_FILE"))
45 elif self._options.step == 0: # pragma: no cover
46 self.Die("A merge is already in progress")
47 open(self.Config("ALREADY_MERGING_SENTINEL_FILE"), "a").close()
48
49 self.InitialEnvironmentChecks(self.default_cwd)
Ben Murdochf91f0612016-11-29 16:50:11 +000050
51 self["merge_to_branch"] = self._options.branch
Ben Murdoch014dc512016-03-22 12:00:34 +000052
53 self.CommonPrepare()
54 self.PrepareBranch()
55
56
57class CreateBranch(Step):
58 MESSAGE = "Create a fresh branch for the patch."
59
60 def RunStep(self):
61 self.GitCreateBranch(self.Config("BRANCHNAME"),
62 self.vc.RemoteBranch(self["merge_to_branch"]))
63
64
65class SearchArchitecturePorts(Step):
66 MESSAGE = "Search for corresponding architecture ports."
67
68 def RunStep(self):
69 self["full_revision_list"] = list(OrderedDict.fromkeys(
70 self._options.revisions))
71 port_revision_list = []
72 for revision in self["full_revision_list"]:
73 # Search for commits which matches the "Port XXX" pattern.
74 git_hashes = self.GitLog(reverse=True, format="%H",
Ben Murdochf91f0612016-11-29 16:50:11 +000075 grep="^[Pp]ort %s" % revision,
Ben Murdoch014dc512016-03-22 12:00:34 +000076 branch=self.vc.RemoteMasterBranch())
77 for git_hash in git_hashes.splitlines():
78 revision_title = self.GitLog(n=1, format="%s", git_hash=git_hash)
79
80 # Is this revision included in the original revision list?
81 if git_hash in self["full_revision_list"]:
82 print("Found port of %s -> %s (already included): %s"
83 % (revision, git_hash, revision_title))
84 else:
85 print("Found port of %s -> %s: %s"
86 % (revision, git_hash, revision_title))
87 port_revision_list.append(git_hash)
88
89 # Do we find any port?
90 if len(port_revision_list) > 0:
91 if self.Confirm("Automatically add corresponding ports (%s)?"
92 % ", ".join(port_revision_list)):
93 #: 'y': Add ports to revision list.
94 self["full_revision_list"].extend(port_revision_list)
95
96
97class CreateCommitMessage(Step):
98 MESSAGE = "Create commit message."
99
Ben Murdochf91f0612016-11-29 16:50:11 +0000100 def _create_commit_description(self, commit_hash):
101 patch_merge_desc = self.GitLog(n=1, format="%s", git_hash=commit_hash)
102 description = "Merged: " + patch_merge_desc + "\n"
103 description += "Revision: " + commit_hash + "\n\n"
104 return description
105
Ben Murdoch014dc512016-03-22 12:00:34 +0000106 def RunStep(self):
107
108 # Stringify: ["abcde", "12345"] -> "abcde, 12345"
109 self["revision_list"] = ", ".join(self["full_revision_list"])
110
111 if not self["revision_list"]: # pragma: no cover
112 self.Die("Revision list is empty.")
113
Ben Murdochf91f0612016-11-29 16:50:11 +0000114 msg_pieces = []
Ben Murdoch014dc512016-03-22 12:00:34 +0000115
Ben Murdochf91f0612016-11-29 16:50:11 +0000116 if len(self["full_revision_list"]) > 1:
117 self["commit_title"] = "Merged: Squashed multiple commits."
118 for commit_hash in self["full_revision_list"]:
119 msg_pieces.append(self._create_commit_description(commit_hash))
120 else:
121 commit_hash = self["full_revision_list"][0]
122 full_description = self._create_commit_description(commit_hash).split("\n")
Ben Murdoch014dc512016-03-22 12:00:34 +0000123
Ben Murdochf91f0612016-11-29 16:50:11 +0000124 #Truncate title because of code review tool
125 title = full_description[0]
126 if len(title) > 100:
127 title = title[:96] + " ..."
128
129 self["commit_title"] = title
130 msg_pieces.append(full_description[1] + "\n\n")
Ben Murdoch014dc512016-03-22 12:00:34 +0000131
132 bugs = []
133 for commit_hash in self["full_revision_list"]:
134 msg = self.GitLog(n=1, git_hash=commit_hash)
135 for bug in re.findall(r"^[ \t]*BUG[ \t]*=[ \t]*(.*?)[ \t]*$", msg, re.M):
136 bugs.extend(s.strip() for s in bug.split(","))
137 bug_aggregate = ",".join(sorted(filter(lambda s: s and s != "none", bugs)))
138 if bug_aggregate:
139 msg_pieces.append("BUG=%s\nLOG=N\n" % bug_aggregate)
140
Ben Murdochf91f0612016-11-29 16:50:11 +0000141 msg_pieces.append("NOTRY=true\nNOPRESUBMIT=true\nNOTREECHECKS=true\n")
142
Ben Murdoch014dc512016-03-22 12:00:34 +0000143 self["new_commit_msg"] = "".join(msg_pieces)
144
145
146class ApplyPatches(Step):
147 MESSAGE = "Apply patches for selected revisions."
148
149 def RunStep(self):
150 for commit_hash in self["full_revision_list"]:
151 print("Applying patch for %s to %s..."
152 % (commit_hash, self["merge_to_branch"]))
153 patch = self.GitGetPatch(commit_hash)
154 TextToFile(patch, self.Config("TEMPORARY_PATCH_FILE"))
155 self.ApplyPatch(self.Config("TEMPORARY_PATCH_FILE"))
156 if self._options.patch:
157 self.ApplyPatch(self._options.patch)
158
Ben Murdoch014dc512016-03-22 12:00:34 +0000159class CommitLocal(Step):
160 MESSAGE = "Commit to local branch."
161
162 def RunStep(self):
163 # Add a commit message title.
Ben Murdoch014dc512016-03-22 12:00:34 +0000164 self["new_commit_msg"] = "%s\n\n%s" % (self["commit_title"],
165 self["new_commit_msg"])
166 TextToFile(self["new_commit_msg"], self.Config("COMMITMSG_FILE"))
167 self.GitCommit(file_name=self.Config("COMMITMSG_FILE"))
168
Ben Murdochf91f0612016-11-29 16:50:11 +0000169class AddInformationalComment(Step):
170 MESSAGE = 'Show additional information.'
171
172 def RunStep(self):
173 message = ("NOTE: This script will no longer automatically "
174 "update include/v8-version.h "
175 "and create a tag. This is done automatically by the autotag bot. "
176 "Please call the merge_to_branch.py with --help for more information.")
177
178 self.GitCLAddComment(message)
Ben Murdoch014dc512016-03-22 12:00:34 +0000179
180class CommitRepository(Step):
181 MESSAGE = "Commit to the repository."
182
183 def RunStep(self):
184 self.GitCheckout(self.Config("BRANCHNAME"))
185 self.WaitForLGTM()
186 self.GitPresubmit()
187 self.vc.CLLand()
188
Ben Murdoch014dc512016-03-22 12:00:34 +0000189class CleanUp(Step):
190 MESSAGE = "Cleanup."
191
192 def RunStep(self):
193 self.CommonCleanup()
194 print "*** SUMMARY ***"
Ben Murdoch014dc512016-03-22 12:00:34 +0000195 print "branch: %s" % self["merge_to_branch"]
196 if self["revision_list"]:
197 print "patches: %s" % self["revision_list"]
198
199
200class MergeToBranch(ScriptsBase):
201 def _Description(self):
202 return ("Performs the necessary steps to merge revisions from "
Ben Murdochf91f0612016-11-29 16:50:11 +0000203 "master to release branches like 4.5. This script does not "
204 "version the commit. See http://goo.gl/9ke2Vw for more "
205 "information.")
Ben Murdoch014dc512016-03-22 12:00:34 +0000206
207 def _PrepareOptions(self, parser):
208 group = parser.add_mutually_exclusive_group(required=True)
209 group.add_argument("--branch", help="The branch to merge to.")
210 parser.add_argument("revisions", nargs="*",
211 help="The revisions to merge.")
212 parser.add_argument("-f", "--force",
213 help="Delete sentinel file.",
214 default=False, action="store_true")
215 parser.add_argument("-m", "--message",
216 help="A commit message for the patch.")
217 parser.add_argument("-p", "--patch",
218 help="A patch file to apply as part of the merge.")
219
220 def _ProcessOptions(self, options):
221 if len(options.revisions) < 1:
222 if not options.patch:
223 print "Either a patch file or revision numbers must be specified"
224 return False
225 if not options.message:
226 print "You must specify a merge comment if no patches are specified"
227 return False
228 options.bypass_upload_hooks = True
229 # CC ulan to make sure that fixes are merged to Google3.
230 options.cc = "[email protected]"
231
Ben Murdochf91f0612016-11-29 16:50:11 +0000232 if len(options.branch.split('.')) > 2:
233 print ("This script does not support merging to roll branches. "
234 "Please use tools/release/roll_merge.py for this use case.")
235 return False
236
Ben Murdoch014dc512016-03-22 12:00:34 +0000237 # Make sure to use git hashes in the new workflows.
238 for revision in options.revisions:
239 if (IsSvnNumber(revision) or
240 (revision[0:1] == "r" and IsSvnNumber(revision[1:]))):
241 print "Please provide full git hashes of the patches to merge."
242 print "Got: %s" % revision
243 return False
244 return True
245
246 def _Config(self):
247 return {
248 "BRANCHNAME": "prepare-merge",
249 "PERSISTFILE_BASENAME": "/tmp/v8-merge-to-branch-tempfile",
250 "ALREADY_MERGING_SENTINEL_FILE":
251 "/tmp/v8-merge-to-branch-tempfile-already-merging",
252 "TEMPORARY_PATCH_FILE": "/tmp/v8-prepare-merge-tempfile-temporary-patch",
253 "COMMITMSG_FILE": "/tmp/v8-prepare-merge-tempfile-commitmsg",
254 }
255
256 def _Steps(self):
257 return [
258 Preparation,
259 CreateBranch,
260 SearchArchitecturePorts,
261 CreateCommitMessage,
262 ApplyPatches,
Ben Murdoch014dc512016-03-22 12:00:34 +0000263 CommitLocal,
264 UploadStep,
Ben Murdochf91f0612016-11-29 16:50:11 +0000265 AddInformationalComment,
Ben Murdoch014dc512016-03-22 12:00:34 +0000266 CommitRepository,
Ben Murdoch014dc512016-03-22 12:00:34 +0000267 CleanUp,
268 ]
269
270
271if __name__ == "__main__": # pragma: no cover
272 sys.exit(MergeToBranch().Run())