OLD | NEW |
---|---|
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 Loading... | |
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 += "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 for commit_hash in self["full_revision_list"]: |
115 ] | 119 msg_pieces.append(self._create_commit_description(commit_hash)) |
116 msg_pieces.append("\n\n") | 120 else: |
121 commit_hash = self["full_revision_list"][0] | |
122 full_description = self._create_commit_description(commit_hash).split("\n" ) | |
117 | 123 |
118 for commit_hash in self["full_revision_list"]: | 124 #Truncate title because of code review tool |
Michael Achenbach
2016/07/22 09:43:36
nit: space after #
| |
119 patch_merge_desc = self.GitLog(n=1, format="%s", git_hash=commit_hash) | 125 title = full_description[0] |
120 msg_pieces.append("%s\n\n" % patch_merge_desc) | 126 if len(title) > 100: |
127 title = title[:96] + " ..." | |
128 | |
129 self["commit_title"] = title | |
130 msg_pieces.append(full_description[1] + "\n\n") | |
121 | 131 |
122 bugs = [] | 132 bugs = [] |
123 for commit_hash in self["full_revision_list"]: | 133 for commit_hash in self["full_revision_list"]: |
124 msg = self.GitLog(n=1, git_hash=commit_hash) | 134 msg = self.GitLog(n=1, git_hash=commit_hash) |
125 for bug in re.findall(r"^[ \t]*BUG[ \t]*=[ \t]*(.*?)[ \t]*$", msg, re.M): | 135 for bug in re.findall(r"^[ \t]*BUG[ \t]*=[ \t]*(.*?)[ \t]*$", msg, re.M): |
126 bugs.extend(s.strip() for s in bug.split(",")) | 136 bugs.extend(s.strip() for s in bug.split(",")) |
127 bug_aggregate = ",".join(sorted(filter(lambda s: s and s != "none", bugs))) | 137 bug_aggregate = ",".join(sorted(filter(lambda s: s and s != "none", bugs))) |
128 if bug_aggregate: | 138 if bug_aggregate: |
129 msg_pieces.append("BUG=%s\nLOG=N\n" % bug_aggregate) | 139 msg_pieces.append("BUG=%s\nLOG=N\n" % bug_aggregate) |
130 | 140 |
141 msg_pieces.append("NOTRY=true\nNOPRESUBMIT=true\nNOTREECHECKS=true\n") | |
142 | |
131 self["new_commit_msg"] = "".join(msg_pieces) | 143 self["new_commit_msg"] = "".join(msg_pieces) |
132 | 144 |
133 | 145 |
134 class ApplyPatches(Step): | 146 class ApplyPatches(Step): |
135 MESSAGE = "Apply patches for selected revisions." | 147 MESSAGE = "Apply patches for selected revisions." |
136 | 148 |
137 def RunStep(self): | 149 def RunStep(self): |
138 for commit_hash in self["full_revision_list"]: | 150 for commit_hash in self["full_revision_list"]: |
139 print("Applying patch for %s to %s..." | 151 print("Applying patch for %s to %s..." |
140 % (commit_hash, self["merge_to_branch"])) | 152 % (commit_hash, self["merge_to_branch"])) |
141 patch = self.GitGetPatch(commit_hash) | 153 patch = self.GitGetPatch(commit_hash) |
142 TextToFile(patch, self.Config("TEMPORARY_PATCH_FILE")) | 154 TextToFile(patch, self.Config("TEMPORARY_PATCH_FILE")) |
143 self.ApplyPatch(self.Config("TEMPORARY_PATCH_FILE")) | 155 self.ApplyPatch(self.Config("TEMPORARY_PATCH_FILE")) |
144 if self._options.patch: | 156 if self._options.patch: |
145 self.ApplyPatch(self._options.patch) | 157 self.ApplyPatch(self._options.patch) |
146 | 158 |
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): | 159 class CommitLocal(Step): |
180 MESSAGE = "Commit to local branch." | 160 MESSAGE = "Commit to local branch." |
181 | 161 |
182 def RunStep(self): | 162 def RunStep(self): |
183 # Add a commit message title. | 163 # 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"], | 164 self["new_commit_msg"] = "%s\n\n%s" % (self["commit_title"], |
186 self["new_commit_msg"]) | 165 self["new_commit_msg"]) |
187 TextToFile(self["new_commit_msg"], self.Config("COMMITMSG_FILE")) | 166 TextToFile(self["new_commit_msg"], self.Config("COMMITMSG_FILE")) |
188 self.GitCommit(file_name=self.Config("COMMITMSG_FILE")) | 167 self.GitCommit(file_name=self.Config("COMMITMSG_FILE")) |
189 | 168 |
169 class 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) | |
190 | 179 |
191 class CommitRepository(Step): | 180 class CommitRepository(Step): |
192 MESSAGE = "Commit to the repository." | 181 MESSAGE = "Commit to the repository." |
193 | 182 |
194 def RunStep(self): | 183 def RunStep(self): |
195 self.GitCheckout(self.Config("BRANCHNAME")) | 184 self.GitCheckout(self.Config("BRANCHNAME")) |
196 self.WaitForLGTM() | 185 self.WaitForLGTM() |
197 self.GitPresubmit() | 186 self.GitPresubmit() |
198 self.vc.CLLand() | 187 self.vc.CLLand() |
199 | 188 |
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): | 189 class CleanUp(Step): |
212 MESSAGE = "Cleanup." | 190 MESSAGE = "Cleanup." |
213 | 191 |
214 def RunStep(self): | 192 def RunStep(self): |
215 self.CommonCleanup() | 193 self.CommonCleanup() |
216 print "*** SUMMARY ***" | 194 print "*** SUMMARY ***" |
217 print "version: %s" % self["version"] | |
218 print "branch: %s" % self["merge_to_branch"] | 195 print "branch: %s" % self["merge_to_branch"] |
219 if self["revision_list"]: | 196 if self["revision_list"]: |
220 print "patches: %s" % self["revision_list"] | 197 print "patches: %s" % self["revision_list"] |
221 | 198 |
222 | 199 |
223 class MergeToBranch(ScriptsBase): | 200 class MergeToBranch(ScriptsBase): |
224 def _Description(self): | 201 def _Description(self): |
225 return ("Performs the necessary steps to merge revisions from " | 202 return ("Performs the necessary steps to merge revisions from " |
226 "master to other branches, including candidates.") | 203 "master to release branches like 4.5. This script does not " |
204 "version the commit. See http://goo.gl/9ke2Vw for more " | |
205 "information.") | |
227 | 206 |
228 def _PrepareOptions(self, parser): | 207 def _PrepareOptions(self, parser): |
229 group = parser.add_mutually_exclusive_group(required=True) | 208 group = parser.add_mutually_exclusive_group(required=True) |
230 group.add_argument("--branch", help="The branch to merge to.") | 209 group.add_argument("--branch", help="The branch to merge to.") |
231 parser.add_argument("revisions", nargs="*", | 210 parser.add_argument("revisions", nargs="*", |
232 help="The revisions to merge.") | 211 help="The revisions to merge.") |
233 parser.add_argument("-f", "--force", | 212 parser.add_argument("-f", "--force", |
234 help="Delete sentinel file.", | 213 help="Delete sentinel file.", |
235 default=False, action="store_true") | 214 default=False, action="store_true") |
236 parser.add_argument("-m", "--message", | 215 parser.add_argument("-m", "--message", |
237 help="A commit message for the patch.") | 216 help="A commit message for the patch.") |
238 parser.add_argument("-p", "--patch", | 217 parser.add_argument("-p", "--patch", |
239 help="A patch file to apply as part of the merge.") | 218 help="A patch file to apply as part of the merge.") |
240 | 219 |
241 def _ProcessOptions(self, options): | 220 def _ProcessOptions(self, options): |
242 if len(options.revisions) < 1: | 221 if len(options.revisions) < 1: |
243 if not options.patch: | 222 if not options.patch: |
244 print "Either a patch file or revision numbers must be specified" | 223 print "Either a patch file or revision numbers must be specified" |
245 return False | 224 return False |
246 if not options.message: | 225 if not options.message: |
247 print "You must specify a merge comment if no patches are specified" | 226 print "You must specify a merge comment if no patches are specified" |
248 return False | 227 return False |
249 options.bypass_upload_hooks = True | 228 options.bypass_upload_hooks = True |
250 # CC ulan to make sure that fixes are merged to Google3. | 229 # CC ulan to make sure that fixes are merged to Google3. |
251 options.cc = "ulan@chromium.org" | 230 options.cc = "ulan@chromium.org" |
252 | 231 |
232 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 | |
253 # Make sure to use git hashes in the new workflows. | 237 # Make sure to use git hashes in the new workflows. |
254 for revision in options.revisions: | 238 for revision in options.revisions: |
255 if (IsSvnNumber(revision) or | 239 if (IsSvnNumber(revision) or |
256 (revision[0:1] == "r" and IsSvnNumber(revision[1:]))): | 240 (revision[0:1] == "r" and IsSvnNumber(revision[1:]))): |
257 print "Please provide full git hashes of the patches to merge." | 241 print "Please provide full git hashes of the patches to merge." |
258 print "Got: %s" % revision | 242 print "Got: %s" % revision |
259 return False | 243 return False |
260 return True | 244 return True |
261 | 245 |
262 def _Config(self): | 246 def _Config(self): |
263 return { | 247 return { |
264 "BRANCHNAME": "prepare-merge", | 248 "BRANCHNAME": "prepare-merge", |
265 "PERSISTFILE_BASENAME": "/tmp/v8-merge-to-branch-tempfile", | 249 "PERSISTFILE_BASENAME": "/tmp/v8-merge-to-branch-tempfile", |
266 "ALREADY_MERGING_SENTINEL_FILE": | 250 "ALREADY_MERGING_SENTINEL_FILE": |
267 "/tmp/v8-merge-to-branch-tempfile-already-merging", | 251 "/tmp/v8-merge-to-branch-tempfile-already-merging", |
268 "TEMPORARY_PATCH_FILE": "/tmp/v8-prepare-merge-tempfile-temporary-patch", | 252 "TEMPORARY_PATCH_FILE": "/tmp/v8-prepare-merge-tempfile-temporary-patch", |
269 "COMMITMSG_FILE": "/tmp/v8-prepare-merge-tempfile-commitmsg", | 253 "COMMITMSG_FILE": "/tmp/v8-prepare-merge-tempfile-commitmsg", |
270 } | 254 } |
271 | 255 |
272 def _Steps(self): | 256 def _Steps(self): |
273 return [ | 257 return [ |
274 Preparation, | 258 Preparation, |
275 CreateBranch, | 259 CreateBranch, |
276 SearchArchitecturePorts, | 260 SearchArchitecturePorts, |
277 CreateCommitMessage, | 261 CreateCommitMessage, |
278 ApplyPatches, | 262 ApplyPatches, |
279 PrepareVersion, | |
280 IncrementVersion, | |
281 CommitLocal, | 263 CommitLocal, |
282 UploadStep, | 264 UploadStep, |
265 AddInformationalComment, | |
283 CommitRepository, | 266 CommitRepository, |
284 TagRevision, | |
285 CleanUp, | 267 CleanUp, |
286 ] | 268 ] |
287 | 269 |
288 | 270 |
289 if __name__ == "__main__": # pragma: no cover | 271 if __name__ == "__main__": # pragma: no cover |
290 sys.exit(MergeToBranch().Run()) | 272 sys.exit(MergeToBranch().Run()) |
OLD | NEW |