 Chromium Code Reviews
 Chromium Code Reviews Issue 1398033003:
  [Release] Update merge script to leverage auto-tag bot  (Closed) 
  Base URL: https://chromium.googlesource.com/v8/v8.git@master
    
  
    Issue 1398033003:
  [Release] Update merge script to leverage auto-tag bot  (Closed) 
  Base URL: https://chromium.googlesource.com/v8/v8.git@master| 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 = 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()) | 
| OLD | NEW |