Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(773)

Side by Side Diff: tools/release/merge_to_branch.py

Issue 1398033003: [Release] Update merge script to leverage auto-tag bot (Closed) Base URL: https://chromium.googlesource.com/v8/v8.git@master
Patch Set: Add CQ options to enable submit via CQ Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tools/release/git_recipes.py ('k') | tools/release/roll_merge.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2014 the V8 project authors. All rights reserved. 2 # Copyright 2014 the V8 project authors. All rights reserved.
3 # Redistribution and use in source and binary forms, with or without 3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are 4 # modification, are permitted provided that the following conditions are
5 # met: 5 # met:
6 # 6 #
7 # * Redistributions of source code must retain the above copyright 7 # * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer. 8 # notice, this list of conditions and the following disclaimer.
9 # * Redistributions in binary form must reproduce the above 9 # * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following 10 # copyright notice, this list of conditions and the following
(...skipping 29 matching lines...) Expand all
40 40
41 def RunStep(self): 41 def RunStep(self):
42 if os.path.exists(self.Config("ALREADY_MERGING_SENTINEL_FILE")): 42 if os.path.exists(self.Config("ALREADY_MERGING_SENTINEL_FILE")):
43 if self._options.force: 43 if self._options.force:
44 os.remove(self.Config("ALREADY_MERGING_SENTINEL_FILE")) 44 os.remove(self.Config("ALREADY_MERGING_SENTINEL_FILE"))
45 elif self._options.step == 0: # pragma: no cover 45 elif self._options.step == 0: # pragma: no cover
46 self.Die("A merge is already in progress") 46 self.Die("A merge is already in progress")
47 open(self.Config("ALREADY_MERGING_SENTINEL_FILE"), "a").close() 47 open(self.Config("ALREADY_MERGING_SENTINEL_FILE"), "a").close()
48 48
49 self.InitialEnvironmentChecks(self.default_cwd) 49 self.InitialEnvironmentChecks(self.default_cwd)
50 if self._options.branch: 50
51 self["merge_to_branch"] = self._options.branch 51 self["merge_to_branch"] = self._options.branch
52 else: # pragma: no cover
53 self.Die("Please specify a branch to merge to")
54 52
55 self.CommonPrepare() 53 self.CommonPrepare()
56 self.PrepareBranch() 54 self.PrepareBranch()
57 55
58 56
59 class CreateBranch(Step): 57 class CreateBranch(Step):
60 MESSAGE = "Create a fresh branch for the patch." 58 MESSAGE = "Create a fresh branch for the patch."
61 59
62 def RunStep(self): 60 def RunStep(self):
63 self.GitCreateBranch(self.Config("BRANCHNAME"), 61 self.GitCreateBranch(self.Config("BRANCHNAME"),
64 self.vc.RemoteBranch(self["merge_to_branch"])) 62 self.vc.RemoteBranch(self["merge_to_branch"]))
65 63
66 64
67 class SearchArchitecturePorts(Step): 65 class SearchArchitecturePorts(Step):
68 MESSAGE = "Search for corresponding architecture ports." 66 MESSAGE = "Search for corresponding architecture ports."
69 67
70 def RunStep(self): 68 def RunStep(self):
71 self["full_revision_list"] = list(OrderedDict.fromkeys( 69 self["full_revision_list"] = list(OrderedDict.fromkeys(
72 self._options.revisions)) 70 self._options.revisions))
73 port_revision_list = [] 71 port_revision_list = []
74 for revision in self["full_revision_list"]: 72 for revision in self["full_revision_list"]:
75 # Search for commits which matches the "Port XXX" pattern. 73 # Search for commits which matches the "Port XXX" pattern.
76 git_hashes = self.GitLog(reverse=True, format="%H", 74 git_hashes = self.GitLog(reverse=True, format="%H",
77 grep="Port %s" % revision, 75 grep="^[Pp]ort %s" % revision,
78 branch=self.vc.RemoteMasterBranch()) 76 branch=self.vc.RemoteMasterBranch())
79 for git_hash in git_hashes.splitlines(): 77 for git_hash in git_hashes.splitlines():
80 revision_title = self.GitLog(n=1, format="%s", git_hash=git_hash) 78 revision_title = self.GitLog(n=1, format="%s", git_hash=git_hash)
81 79
82 # Is this revision included in the original revision list? 80 # Is this revision included in the original revision list?
83 if git_hash in self["full_revision_list"]: 81 if git_hash in self["full_revision_list"]:
84 print("Found port of %s -> %s (already included): %s" 82 print("Found port of %s -> %s (already included): %s"
85 % (revision, git_hash, revision_title)) 83 % (revision, git_hash, revision_title))
86 else: 84 else:
87 print("Found port of %s -> %s: %s" 85 print("Found port of %s -> %s: %s"
88 % (revision, git_hash, revision_title)) 86 % (revision, git_hash, revision_title))
89 port_revision_list.append(git_hash) 87 port_revision_list.append(git_hash)
90 88
91 # Do we find any port? 89 # Do we find any port?
92 if len(port_revision_list) > 0: 90 if len(port_revision_list) > 0:
93 if self.Confirm("Automatically add corresponding ports (%s)?" 91 if self.Confirm("Automatically add corresponding ports (%s)?"
94 % ", ".join(port_revision_list)): 92 % ", ".join(port_revision_list)):
95 #: 'y': Add ports to revision list. 93 #: 'y': Add ports to revision list.
96 self["full_revision_list"].extend(port_revision_list) 94 self["full_revision_list"].extend(port_revision_list)
97 95
98 96
99 class CreateCommitMessage(Step): 97 class CreateCommitMessage(Step):
100 MESSAGE = "Create commit message." 98 MESSAGE = "Create commit message."
101 99
100 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 = description + "Revision: " + commit_hash + "\n\n"
104 return description
105
102 def RunStep(self): 106 def RunStep(self):
103 107
104 # Stringify: ["abcde", "12345"] -> "abcde, 12345" 108 # Stringify: ["abcde", "12345"] -> "abcde, 12345"
105 self["revision_list"] = ", ".join(self["full_revision_list"]) 109 self["revision_list"] = ", ".join(self["full_revision_list"])
106 110
107 if not self["revision_list"]: # pragma: no cover 111 if not self["revision_list"]: # pragma: no cover
108 self.Die("Revision list is empty.") 112 self.Die("Revision list is empty.")
109 113
110 action_text = "Merged %s" 114 msg_pieces = []
111 115
112 # The commit message title is added below after the version is specified. 116 if len(self["full_revision_list"]) > 1:
113 msg_pieces = [ 117 self["commit_title"] = "Merged: Squashed multiple commits."
114 "\n".join(action_text % s for s in self["full_revision_list"]), 118 else:
115 ] 119 commit_hash = self["full_revision_list"][0]
116 msg_pieces.append("\n\n") 120 patch_merge_desc = self.GitLog(n=1, format="%s", git_hash=commit_hash)
121 title = "Merged: " + patch_merge_desc
122 self["commit_title"] = title
117 123
118 for commit_hash in self["full_revision_list"]: 124 for commit_hash in self["full_revision_list"]:
119 patch_merge_desc = self.GitLog(n=1, format="%s", git_hash=commit_hash) 125 msg_pieces.append(self._create_commit_description(commit_hash))
120 msg_pieces.append("%s\n\n" % patch_merge_desc)
121 126
122 bugs = [] 127 bugs = []
123 for commit_hash in self["full_revision_list"]: 128 for commit_hash in self["full_revision_list"]:
124 msg = self.GitLog(n=1, git_hash=commit_hash) 129 msg = self.GitLog(n=1, git_hash=commit_hash)
125 for bug in re.findall(r"^[ \t]*BUG[ \t]*=[ \t]*(.*?)[ \t]*$", msg, re.M): 130 for bug in re.findall(r"^[ \t]*BUG[ \t]*=[ \t]*(.*?)[ \t]*$", msg, re.M):
126 bugs.extend(s.strip() for s in bug.split(",")) 131 bugs.extend(s.strip() for s in bug.split(","))
127 bug_aggregate = ",".join(sorted(filter(lambda s: s and s != "none", bugs))) 132 bug_aggregate = ",".join(sorted(filter(lambda s: s and s != "none", bugs)))
128 if bug_aggregate: 133 if bug_aggregate:
129 msg_pieces.append("BUG=%s\nLOG=N\n" % bug_aggregate) 134 msg_pieces.append("BUG=%s\nLOG=N\n" % bug_aggregate)
130 135
136 msg_pieces.append("NOTRY=true\nNOPRESUBMIT=true\n")
Michael Achenbach 2016/07/22 06:45:56 I'd add NOTREECHECKS=true as well, as otherwise me
Michael Hablich 2016/07/22 08:55:58 Done.
137
131 self["new_commit_msg"] = "".join(msg_pieces) 138 self["new_commit_msg"] = "".join(msg_pieces)
132 139
133 140
134 class ApplyPatches(Step): 141 class ApplyPatches(Step):
135 MESSAGE = "Apply patches for selected revisions." 142 MESSAGE = "Apply patches for selected revisions."
136 143
137 def RunStep(self): 144 def RunStep(self):
138 for commit_hash in self["full_revision_list"]: 145 for commit_hash in self["full_revision_list"]:
139 print("Applying patch for %s to %s..." 146 print("Applying patch for %s to %s..."
140 % (commit_hash, self["merge_to_branch"])) 147 % (commit_hash, self["merge_to_branch"]))
141 patch = self.GitGetPatch(commit_hash) 148 patch = self.GitGetPatch(commit_hash)
142 TextToFile(patch, self.Config("TEMPORARY_PATCH_FILE")) 149 TextToFile(patch, self.Config("TEMPORARY_PATCH_FILE"))
143 self.ApplyPatch(self.Config("TEMPORARY_PATCH_FILE")) 150 self.ApplyPatch(self.Config("TEMPORARY_PATCH_FILE"))
144 if self._options.patch: 151 if self._options.patch:
145 self.ApplyPatch(self._options.patch) 152 self.ApplyPatch(self._options.patch)
146 153
147
148 class PrepareVersion(Step):
149 MESSAGE = "Prepare version file."
150
151 def RunStep(self):
152 # This is used to calculate the patch level increment.
153 self.ReadAndPersistVersion()
154
155
156 class IncrementVersion(Step):
157 MESSAGE = "Increment version number."
158
159 def RunStep(self):
160 new_patch = str(int(self["patch"]) + 1)
161 if self.Confirm("Automatically increment V8_PATCH_LEVEL? (Saying 'n' will "
162 "fire up your EDITOR on %s so you can make arbitrary "
163 "changes. When you're done, save the file and exit your "
164 "EDITOR.)" % VERSION_FILE):
165 text = FileToText(os.path.join(self.default_cwd, VERSION_FILE))
166 text = MSub(r"(?<=#define V8_PATCH_LEVEL)(?P<space>\s+)\d*$",
167 r"\g<space>%s" % new_patch,
168 text)
169 TextToFile(text, os.path.join(self.default_cwd, VERSION_FILE))
170 else:
171 self.Editor(os.path.join(self.default_cwd, VERSION_FILE))
172 self.ReadAndPersistVersion("new_")
173 self["version"] = "%s.%s.%s.%s" % (self["new_major"],
174 self["new_minor"],
175 self["new_build"],
176 self["new_patch"])
177
178
179 class CommitLocal(Step): 154 class CommitLocal(Step):
180 MESSAGE = "Commit to local branch." 155 MESSAGE = "Commit to local branch."
181 156
182 def RunStep(self): 157 def RunStep(self):
183 # Add a commit message title. 158 # Add a commit message title.
184 self["commit_title"] = "Version %s (cherry-pick)" % self["version"]
185 self["new_commit_msg"] = "%s\n\n%s" % (self["commit_title"], 159 self["new_commit_msg"] = "%s\n\n%s" % (self["commit_title"],
186 self["new_commit_msg"]) 160 self["new_commit_msg"])
187 TextToFile(self["new_commit_msg"], self.Config("COMMITMSG_FILE")) 161 TextToFile(self["new_commit_msg"], self.Config("COMMITMSG_FILE"))
188 self.GitCommit(file_name=self.Config("COMMITMSG_FILE")) 162 self.GitCommit(file_name=self.Config("COMMITMSG_FILE"))
189 163
164 class AddInformationalComment(Step):
165 MESSAGE = 'Show additional information.'
166
167 def RunStep(self):
168 message = ("IMPORTANT: This script will no longer automatically "
169 "update include/v8-version.h "
170 "and create a tag. Please call the script with --help for more "
171 "information.")
172
173 self.GitCLAddComment(message)
190 174
191 class CommitRepository(Step): 175 class CommitRepository(Step):
192 MESSAGE = "Commit to the repository." 176 MESSAGE = "Commit to the repository."
193 177
194 def RunStep(self): 178 def RunStep(self):
195 self.GitCheckout(self.Config("BRANCHNAME")) 179 self.GitCheckout(self.Config("BRANCHNAME"))
196 self.WaitForLGTM() 180 self.WaitForLGTM()
197 self.GitPresubmit() 181 self.GitPresubmit()
198 self.vc.CLLand() 182 self.vc.CLLand()
199 183
200
201 class TagRevision(Step):
202 MESSAGE = "Create the tag."
203
204 def RunStep(self):
205 print "Creating tag %s" % self["version"]
206 self.vc.Tag(self["version"],
207 self.vc.RemoteBranch(self["merge_to_branch"]),
208 self["commit_title"])
209
210
211 class CleanUp(Step): 184 class CleanUp(Step):
212 MESSAGE = "Cleanup." 185 MESSAGE = "Cleanup."
213 186
214 def RunStep(self): 187 def RunStep(self):
215 self.CommonCleanup() 188 self.CommonCleanup()
216 print "*** SUMMARY ***" 189 print "*** SUMMARY ***"
217 print "version: %s" % self["version"]
218 print "branch: %s" % self["merge_to_branch"] 190 print "branch: %s" % self["merge_to_branch"]
219 if self["revision_list"]: 191 if self["revision_list"]:
220 print "patches: %s" % self["revision_list"] 192 print "patches: %s" % self["revision_list"]
221 193
222 194
223 class MergeToBranch(ScriptsBase): 195 class MergeToBranch(ScriptsBase):
224 def _Description(self): 196 def _Description(self):
225 return ("Performs the necessary steps to merge revisions from " 197 return ("Performs the necessary steps to merge revisions from "
226 "master to other branches, including candidates.") 198 "master to release branches like 4.5. This script does not "
199 "version the commit. See http://goo.gl/9ke2Vw for more "
200 "information.")
227 201
228 def _PrepareOptions(self, parser): 202 def _PrepareOptions(self, parser):
229 group = parser.add_mutually_exclusive_group(required=True) 203 group = parser.add_mutually_exclusive_group(required=True)
230 group.add_argument("--branch", help="The branch to merge to.") 204 group.add_argument("--branch", help="The branch to merge to.")
231 parser.add_argument("revisions", nargs="*", 205 parser.add_argument("revisions", nargs="*",
232 help="The revisions to merge.") 206 help="The revisions to merge.")
233 parser.add_argument("-f", "--force", 207 parser.add_argument("-f", "--force",
234 help="Delete sentinel file.", 208 help="Delete sentinel file.",
235 default=False, action="store_true") 209 default=False, action="store_true")
236 parser.add_argument("-m", "--message", 210 parser.add_argument("-m", "--message",
237 help="A commit message for the patch.") 211 help="A commit message for the patch.")
238 parser.add_argument("-p", "--patch", 212 parser.add_argument("-p", "--patch",
239 help="A patch file to apply as part of the merge.") 213 help="A patch file to apply as part of the merge.")
240 214
241 def _ProcessOptions(self, options): 215 def _ProcessOptions(self, options):
242 if len(options.revisions) < 1: 216 if len(options.revisions) < 1:
243 if not options.patch: 217 if not options.patch:
244 print "Either a patch file or revision numbers must be specified" 218 print "Either a patch file or revision numbers must be specified"
245 return False 219 return False
246 if not options.message: 220 if not options.message:
247 print "You must specify a merge comment if no patches are specified" 221 print "You must specify a merge comment if no patches are specified"
248 return False 222 return False
249 options.bypass_upload_hooks = True 223 options.bypass_upload_hooks = True
250 # CC ulan to make sure that fixes are merged to Google3. 224 # CC ulan to make sure that fixes are merged to Google3.
251 options.cc = "ulan@chromium.org" 225 options.cc = "ulan@chromium.org"
252 226
227 # This script only supports official release branches, not roll branches
228 if len(options.branch.split('.')) > 2:
229 print ("This script does not support merging to roll branches (yet). "
230 "Please use tools/release/roll_merge.py for this use case.")
231 return False
232
253 # Make sure to use git hashes in the new workflows. 233 # Make sure to use git hashes in the new workflows.
254 for revision in options.revisions: 234 for revision in options.revisions:
255 if (IsSvnNumber(revision) or 235 if (IsSvnNumber(revision) or
256 (revision[0:1] == "r" and IsSvnNumber(revision[1:]))): 236 (revision[0:1] == "r" and IsSvnNumber(revision[1:]))):
257 print "Please provide full git hashes of the patches to merge." 237 print "Please provide full git hashes of the patches to merge."
258 print "Got: %s" % revision 238 print "Got: %s" % revision
259 return False 239 return False
260 return True 240 return True
261 241
262 def _Config(self): 242 def _Config(self):
263 return { 243 return {
264 "BRANCHNAME": "prepare-merge", 244 "BRANCHNAME": "prepare-merge",
265 "PERSISTFILE_BASENAME": "/tmp/v8-merge-to-branch-tempfile", 245 "PERSISTFILE_BASENAME": "/tmp/v8-merge-to-branch-tempfile",
266 "ALREADY_MERGING_SENTINEL_FILE": 246 "ALREADY_MERGING_SENTINEL_FILE":
267 "/tmp/v8-merge-to-branch-tempfile-already-merging", 247 "/tmp/v8-merge-to-branch-tempfile-already-merging",
268 "TEMPORARY_PATCH_FILE": "/tmp/v8-prepare-merge-tempfile-temporary-patch", 248 "TEMPORARY_PATCH_FILE": "/tmp/v8-prepare-merge-tempfile-temporary-patch",
269 "COMMITMSG_FILE": "/tmp/v8-prepare-merge-tempfile-commitmsg", 249 "COMMITMSG_FILE": "/tmp/v8-prepare-merge-tempfile-commitmsg",
270 } 250 }
271 251
272 def _Steps(self): 252 def _Steps(self):
273 return [ 253 return [
274 Preparation, 254 Preparation,
275 CreateBranch, 255 CreateBranch,
276 SearchArchitecturePorts, 256 SearchArchitecturePorts,
277 CreateCommitMessage, 257 CreateCommitMessage,
278 ApplyPatches, 258 ApplyPatches,
279 PrepareVersion,
280 IncrementVersion,
281 CommitLocal, 259 CommitLocal,
282 UploadStep, 260 UploadStep,
261 AddInformationalComment,
283 CommitRepository, 262 CommitRepository,
284 TagRevision,
285 CleanUp, 263 CleanUp,
286 ] 264 ]
287 265
288 266
289 if __name__ == "__main__": # pragma: no cover 267 if __name__ == "__main__": # pragma: no cover
290 sys.exit(MergeToBranch().Run()) 268 sys.exit(MergeToBranch().Run())
OLDNEW
« no previous file with comments | « tools/release/git_recipes.py ('k') | tools/release/roll_merge.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698