| OLD | NEW | 
|---|
|  | (Empty) | 
| 1 #!/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 |  | 
| 29 import argparse |  | 
| 30 from collections import OrderedDict |  | 
| 31 import sys |  | 
| 32 |  | 
| 33 from common_includes import * |  | 
| 34 |  | 
| 35 def IsSvnNumber(rev): |  | 
| 36   return rev.isdigit() and len(rev) < 8 |  | 
| 37 |  | 
| 38 class 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) |  | 
| 50     if self._options.revert_master: |  | 
| 51       # FIXME(machenbach): Make revert master obsolete? |  | 
| 52       self["merge_to_branch"] = "master" |  | 
| 53     elif self._options.branch: |  | 
| 54       self["merge_to_branch"] = self._options.branch |  | 
| 55     else:  # pragma: no cover |  | 
| 56       self.Die("Please specify a branch to merge to") |  | 
| 57 |  | 
| 58     self.CommonPrepare() |  | 
| 59     self.PrepareBranch() |  | 
| 60 |  | 
| 61 |  | 
| 62 class CreateBranch(Step): |  | 
| 63   MESSAGE = "Create a fresh branch for the patch." |  | 
| 64 |  | 
| 65   def RunStep(self): |  | 
| 66     self.GitCreateBranch(self.Config("BRANCHNAME"), |  | 
| 67                          self.vc.RemoteBranch(self["merge_to_branch"])) |  | 
| 68 |  | 
| 69 |  | 
| 70 class SearchArchitecturePorts(Step): |  | 
| 71   MESSAGE = "Search for corresponding architecture ports." |  | 
| 72 |  | 
| 73   def RunStep(self): |  | 
| 74     self["full_revision_list"] = list(OrderedDict.fromkeys( |  | 
| 75         self._options.revisions)) |  | 
| 76     port_revision_list = [] |  | 
| 77     for revision in self["full_revision_list"]: |  | 
| 78       # Search for commits which matches the "Port XXX" pattern. |  | 
| 79       git_hashes = self.GitLog(reverse=True, format="%H", |  | 
| 80                                grep="Port %s" % revision, |  | 
| 81                                branch=self.vc.RemoteMasterBranch()) |  | 
| 82       for git_hash in git_hashes.splitlines(): |  | 
| 83         revision_title = self.GitLog(n=1, format="%s", git_hash=git_hash) |  | 
| 84 |  | 
| 85         # Is this revision included in the original revision list? |  | 
| 86         if git_hash in self["full_revision_list"]: |  | 
| 87           print("Found port of %s -> %s (already included): %s" |  | 
| 88                 % (revision, git_hash, revision_title)) |  | 
| 89         else: |  | 
| 90           print("Found port of %s -> %s: %s" |  | 
| 91                 % (revision, git_hash, revision_title)) |  | 
| 92           port_revision_list.append(git_hash) |  | 
| 93 |  | 
| 94     # Do we find any port? |  | 
| 95     if len(port_revision_list) > 0: |  | 
| 96       if self.Confirm("Automatically add corresponding ports (%s)?" |  | 
| 97                       % ", ".join(port_revision_list)): |  | 
| 98         #: 'y': Add ports to revision list. |  | 
| 99         self["full_revision_list"].extend(port_revision_list) |  | 
| 100 |  | 
| 101 |  | 
| 102 class CreateCommitMessage(Step): |  | 
| 103   MESSAGE = "Create commit message." |  | 
| 104 |  | 
| 105   def RunStep(self): |  | 
| 106 |  | 
| 107     # Stringify: [123, 234] -> "r123, r234" |  | 
| 108     self["revision_list"] = ", ".join(map(lambda s: "r%s" % s, |  | 
| 109                                       self["full_revision_list"])) |  | 
| 110 |  | 
| 111     if not self["revision_list"]:  # pragma: no cover |  | 
| 112       self.Die("Revision list is empty.") |  | 
| 113 |  | 
| 114     if self._options.revert and not self._options.revert_master: |  | 
| 115       action_text = "Rollback of %s" |  | 
| 116     else: |  | 
| 117       action_text = "Merged %s" |  | 
| 118 |  | 
| 119     # The commit message title is added below after the version is specified. |  | 
| 120     msg_pieces = [ |  | 
| 121       "\n".join(action_text % s for s in self["full_revision_list"]), |  | 
| 122     ] |  | 
| 123     msg_pieces.append("\n\n") |  | 
| 124 |  | 
| 125     for commit_hash in self["full_revision_list"]: |  | 
| 126       patch_merge_desc = self.GitLog(n=1, format="%s", git_hash=commit_hash) |  | 
| 127       msg_pieces.append("%s\n\n" % patch_merge_desc) |  | 
| 128 |  | 
| 129     bugs = [] |  | 
| 130     for commit_hash in self["full_revision_list"]: |  | 
| 131       msg = self.GitLog(n=1, git_hash=commit_hash) |  | 
| 132       for bug in re.findall(r"^[ \t]*BUG[ \t]*=[ \t]*(.*?)[ \t]*$", msg, re.M): |  | 
| 133         bugs.extend(s.strip() for s in bug.split(",")) |  | 
| 134     bug_aggregate = ",".join(sorted(filter(lambda s: s and s != "none", bugs))) |  | 
| 135     if bug_aggregate: |  | 
| 136       msg_pieces.append("BUG=%s\nLOG=N\n" % bug_aggregate) |  | 
| 137 |  | 
| 138     self["new_commit_msg"] = "".join(msg_pieces) |  | 
| 139 |  | 
| 140 |  | 
| 141 class ApplyPatches(Step): |  | 
| 142   MESSAGE = "Apply patches for selected revisions." |  | 
| 143 |  | 
| 144   def RunStep(self): |  | 
| 145     for commit_hash in self["full_revision_list"]: |  | 
| 146       print("Applying patch for %s to %s..." |  | 
| 147             % (commit_hash, self["merge_to_branch"])) |  | 
| 148       patch = self.GitGetPatch(commit_hash) |  | 
| 149       TextToFile(patch, self.Config("TEMPORARY_PATCH_FILE")) |  | 
| 150       self.ApplyPatch(self.Config("TEMPORARY_PATCH_FILE"), self._options.revert) |  | 
| 151     if self._options.patch: |  | 
| 152       self.ApplyPatch(self._options.patch, self._options.revert) |  | 
| 153 |  | 
| 154 |  | 
| 155 class PrepareVersion(Step): |  | 
| 156   MESSAGE = "Prepare version file." |  | 
| 157 |  | 
| 158   def RunStep(self): |  | 
| 159     if self._options.revert_master: |  | 
| 160       return |  | 
| 161     # This is used to calculate the patch level increment. |  | 
| 162     self.ReadAndPersistVersion() |  | 
| 163 |  | 
| 164 |  | 
| 165 class IncrementVersion(Step): |  | 
| 166   MESSAGE = "Increment version number." |  | 
| 167 |  | 
| 168   def RunStep(self): |  | 
| 169     if self._options.revert_master: |  | 
| 170       return |  | 
| 171     new_patch = str(int(self["patch"]) + 1) |  | 
| 172     if self.Confirm("Automatically increment PATCH_LEVEL? (Saying 'n' will " |  | 
| 173                     "fire up your EDITOR on %s so you can make arbitrary " |  | 
| 174                     "changes. When you're done, save the file and exit your " |  | 
| 175                     "EDITOR.)" % VERSION_FILE): |  | 
| 176       text = FileToText(os.path.join(self.default_cwd, VERSION_FILE)) |  | 
| 177       text = MSub(r"(?<=#define PATCH_LEVEL)(?P<space>\s+)\d*$", |  | 
| 178                   r"\g<space>%s" % new_patch, |  | 
| 179                   text) |  | 
| 180       TextToFile(text, os.path.join(self.default_cwd, VERSION_FILE)) |  | 
| 181     else: |  | 
| 182       self.Editor(os.path.join(self.default_cwd, VERSION_FILE)) |  | 
| 183     self.ReadAndPersistVersion("new_") |  | 
| 184     self["version"] = "%s.%s.%s.%s" % (self["new_major"], |  | 
| 185                                        self["new_minor"], |  | 
| 186                                        self["new_build"], |  | 
| 187                                        self["new_patch"]) |  | 
| 188 |  | 
| 189 |  | 
| 190 class CommitLocal(Step): |  | 
| 191   MESSAGE = "Commit to local branch." |  | 
| 192 |  | 
| 193   def RunStep(self): |  | 
| 194     # Add a commit message title. |  | 
| 195     if self._options.revert and self._options.revert_master: |  | 
| 196       # TODO(machenbach): Find a better convention if multiple patches are |  | 
| 197       # reverted in one CL. |  | 
| 198       self["commit_title"] = "Revert on master" |  | 
| 199     else: |  | 
| 200       self["commit_title"] = "Version %s (cherry-pick)" % self["version"] |  | 
| 201     self["new_commit_msg"] = "%s\n\n%s" % (self["commit_title"], |  | 
| 202                                            self["new_commit_msg"]) |  | 
| 203     TextToFile(self["new_commit_msg"], self.Config("COMMITMSG_FILE")) |  | 
| 204     self.GitCommit(file_name=self.Config("COMMITMSG_FILE")) |  | 
| 205 |  | 
| 206 |  | 
| 207 class CommitRepository(Step): |  | 
| 208   MESSAGE = "Commit to the repository." |  | 
| 209 |  | 
| 210   def RunStep(self): |  | 
| 211     self.GitCheckout(self.Config("BRANCHNAME")) |  | 
| 212     self.WaitForLGTM() |  | 
| 213     self.GitPresubmit() |  | 
| 214     self.vc.CLLand() |  | 
| 215 |  | 
| 216 |  | 
| 217 class TagRevision(Step): |  | 
| 218   MESSAGE = "Create the tag." |  | 
| 219 |  | 
| 220   def RunStep(self): |  | 
| 221     if self._options.revert_master: |  | 
| 222       return |  | 
| 223     print "Creating tag %s" % self["version"] |  | 
| 224     self.vc.Tag(self["version"], |  | 
| 225                 self.vc.RemoteBranch(self["merge_to_branch"]), |  | 
| 226                 self["commit_title"]) |  | 
| 227 |  | 
| 228 |  | 
| 229 class CleanUp(Step): |  | 
| 230   MESSAGE = "Cleanup." |  | 
| 231 |  | 
| 232   def RunStep(self): |  | 
| 233     self.CommonCleanup() |  | 
| 234     if not self._options.revert_master: |  | 
| 235       print "*** SUMMARY ***" |  | 
| 236       print "version: %s" % self["version"] |  | 
| 237       print "branch: %s" % self["merge_to_branch"] |  | 
| 238       if self["revision_list"]: |  | 
| 239         print "patches: %s" % self["revision_list"] |  | 
| 240 |  | 
| 241 |  | 
| 242 class MergeToBranch(ScriptsBase): |  | 
| 243   def _Description(self): |  | 
| 244     return ("Performs the necessary steps to merge revisions from " |  | 
| 245             "master to other branches, including candidates.") |  | 
| 246 |  | 
| 247   def _PrepareOptions(self, parser): |  | 
| 248     group = parser.add_mutually_exclusive_group(required=True) |  | 
| 249     group.add_argument("--branch", help="The branch to merge to.") |  | 
| 250     group.add_argument("-R", "--revert-master", |  | 
| 251                        help="Revert specified patches from master.", |  | 
| 252                        default=False, action="store_true") |  | 
| 253     parser.add_argument("revisions", nargs="*", |  | 
| 254                         help="The revisions to merge.") |  | 
| 255     parser.add_argument("-f", "--force", |  | 
| 256                         help="Delete sentinel file.", |  | 
| 257                         default=False, action="store_true") |  | 
| 258     parser.add_argument("-m", "--message", |  | 
| 259                         help="A commit message for the patch.") |  | 
| 260     parser.add_argument("--revert", |  | 
| 261                         help="Revert specified patches.", |  | 
| 262                         default=False, action="store_true") |  | 
| 263     parser.add_argument("-p", "--patch", |  | 
| 264                         help="A patch file to apply as part of the merge.") |  | 
| 265 |  | 
| 266   def _ProcessOptions(self, options): |  | 
| 267     # TODO(machenbach): Add a test that covers revert from master |  | 
| 268     if len(options.revisions) < 1: |  | 
| 269       if not options.patch: |  | 
| 270         print "Either a patch file or revision numbers must be specified" |  | 
| 271         return False |  | 
| 272       if not options.message: |  | 
| 273         print "You must specify a merge comment if no patches are specified" |  | 
| 274         return False |  | 
| 275     options.bypass_upload_hooks = True |  | 
| 276     # CC ulan to make sure that fixes are merged to Google3. |  | 
| 277     options.cc = "ulan@chromium.org" |  | 
| 278 |  | 
| 279     # Make sure to use git hashes in the new workflows. |  | 
| 280     for revision in options.revisions: |  | 
| 281       if (IsSvnNumber(revision) or |  | 
| 282           (revision[0:1] == "r" and IsSvnNumber(revision[1:]))): |  | 
| 283         print "Please provide full git hashes of the patches to merge." |  | 
| 284         print "Got: %s" % revision |  | 
| 285         return False |  | 
| 286     return True |  | 
| 287 |  | 
| 288   def _Config(self): |  | 
| 289     return { |  | 
| 290       "BRANCHNAME": "prepare-merge", |  | 
| 291       "PERSISTFILE_BASENAME": "/tmp/v8-merge-to-branch-tempfile", |  | 
| 292       "ALREADY_MERGING_SENTINEL_FILE": |  | 
| 293           "/tmp/v8-merge-to-branch-tempfile-already-merging", |  | 
| 294       "TEMPORARY_PATCH_FILE": "/tmp/v8-prepare-merge-tempfile-temporary-patch", |  | 
| 295       "COMMITMSG_FILE": "/tmp/v8-prepare-merge-tempfile-commitmsg", |  | 
| 296     } |  | 
| 297 |  | 
| 298   def _Steps(self): |  | 
| 299     return [ |  | 
| 300       Preparation, |  | 
| 301       CreateBranch, |  | 
| 302       SearchArchitecturePorts, |  | 
| 303       CreateCommitMessage, |  | 
| 304       ApplyPatches, |  | 
| 305       PrepareVersion, |  | 
| 306       IncrementVersion, |  | 
| 307       CommitLocal, |  | 
| 308       UploadStep, |  | 
| 309       CommitRepository, |  | 
| 310       TagRevision, |  | 
| 311       CleanUp, |  | 
| 312     ] |  | 
| 313 |  | 
| 314 |  | 
| 315 if __name__ == "__main__":  # pragma: no cover |  | 
| 316   sys.exit(MergeToBranch().Run()) |  | 
| OLD | NEW | 
|---|