Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / v8 / tools / push-to-trunk / merge_to_branch.py
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 ALREADY_MERGING_SENTINEL_FILE = "ALREADY_MERGING_SENTINEL_FILE"
36 COMMIT_HASHES_FILE = "COMMIT_HASHES_FILE"
37 TEMPORARY_PATCH_FILE = "TEMPORARY_PATCH_FILE"
38
39 CONFIG = {
40   BRANCHNAME: "prepare-merge",
41   PERSISTFILE_BASENAME: "/tmp/v8-merge-to-branch-tempfile",
42   ALREADY_MERGING_SENTINEL_FILE:
43       "/tmp/v8-merge-to-branch-tempfile-already-merging",
44   DOT_GIT_LOCATION: ".git",
45   VERSION_FILE: "src/version.cc",
46   TEMPORARY_PATCH_FILE: "/tmp/v8-prepare-merge-tempfile-temporary-patch",
47   COMMITMSG_FILE: "/tmp/v8-prepare-merge-tempfile-commitmsg",
48   COMMIT_HASHES_FILE: "/tmp/v8-merge-to-branch-tempfile-PATCH_COMMIT_HASHES",
49 }
50
51
52 class Preparation(Step):
53   MESSAGE = "Preparation."
54
55   def RunStep(self):
56     if os.path.exists(self.Config(ALREADY_MERGING_SENTINEL_FILE)):
57       if self._options.force:
58         os.remove(self.Config(ALREADY_MERGING_SENTINEL_FILE))
59       elif self._options.step == 0:  # pragma: no cover
60         self.Die("A merge is already in progress")
61     open(self.Config(ALREADY_MERGING_SENTINEL_FILE), "a").close()
62
63     self.InitialEnvironmentChecks()
64     if self._options.revert_bleeding_edge:
65       self["merge_to_branch"] = "bleeding_edge"
66     elif self._options.branch:
67       self["merge_to_branch"] = self._options.branch
68     else:  # pragma: no cover
69       self.Die("Please specify a branch to merge to")
70
71     self.CommonPrepare()
72     self.PrepareBranch()
73
74
75 class CreateBranch(Step):
76   MESSAGE = "Create a fresh branch for the patch."
77
78   def RunStep(self):
79     self.GitCreateBranch(self.Config(BRANCHNAME),
80                          "svn/%s" % self["merge_to_branch"])
81
82
83 class SearchArchitecturePorts(Step):
84   MESSAGE = "Search for corresponding architecture ports."
85
86   def RunStep(self):
87     self["full_revision_list"] = list(OrderedDict.fromkeys(
88         self._options.revisions))
89     port_revision_list = []
90     for revision in self["full_revision_list"]:
91       # Search for commits which matches the "Port rXXX" pattern.
92       git_hashes = self.GitLog(reverse=True, format="%H",
93                                grep="Port r%d" % int(revision),
94                                branch="svn/bleeding_edge")
95       for git_hash in git_hashes.splitlines():
96         svn_revision = self.GitSVNFindSVNRev(git_hash, "svn/bleeding_edge")
97         if not svn_revision:  # pragma: no cover
98           self.Die("Cannot determine svn revision for %s" % git_hash)
99         revision_title = self.GitLog(n=1, format="%s", git_hash=git_hash)
100
101         # Is this revision included in the original revision list?
102         if svn_revision in self["full_revision_list"]:
103           print("Found port of r%s -> r%s (already included): %s"
104                 % (revision, svn_revision, revision_title))
105         else:
106           print("Found port of r%s -> r%s: %s"
107                 % (revision, svn_revision, revision_title))
108           port_revision_list.append(svn_revision)
109
110     # Do we find any port?
111     if len(port_revision_list) > 0:
112       if self.Confirm("Automatically add corresponding ports (%s)?"
113                       % ", ".join(port_revision_list)):
114         #: 'y': Add ports to revision list.
115         self["full_revision_list"].extend(port_revision_list)
116
117
118 class FindGitRevisions(Step):
119   MESSAGE = "Find the git revisions associated with the patches."
120
121   def RunStep(self):
122     self["patch_commit_hashes"] = []
123     for revision in self["full_revision_list"]:
124       next_hash = self.GitSVNFindGitHash(revision, "svn/bleeding_edge")
125       if not next_hash:  # pragma: no cover
126         self.Die("Cannot determine git hash for r%s" % revision)
127       self["patch_commit_hashes"].append(next_hash)
128
129     # Stringify: [123, 234] -> "r123, r234"
130     self["revision_list"] = ", ".join(map(lambda s: "r%s" % s,
131                                       self["full_revision_list"]))
132
133     if not self["revision_list"]:  # pragma: no cover
134       self.Die("Revision list is empty.")
135
136     # The commit message title is added below after the version is specified.
137     self["new_commit_msg"] = ""
138
139     for commit_hash in self["patch_commit_hashes"]:
140       patch_merge_desc = self.GitLog(n=1, format="%s", git_hash=commit_hash)
141       self["new_commit_msg"] += "%s\n\n" % patch_merge_desc
142
143     bugs = []
144     for commit_hash in self["patch_commit_hashes"]:
145       msg = self.GitLog(n=1, git_hash=commit_hash)
146       for bug in re.findall(r"^[ \t]*BUG[ \t]*=[ \t]*(.*?)[ \t]*$", msg,
147                             re.M):
148         bugs.extend(map(lambda s: s.strip(), bug.split(",")))
149     bug_aggregate = ",".join(sorted(filter(lambda s: s and s != "none", bugs)))
150     if bug_aggregate:
151       self["new_commit_msg"] += "BUG=%s\nLOG=N\n" % bug_aggregate
152
153
154 class ApplyPatches(Step):
155   MESSAGE = "Apply patches for selected revisions."
156
157   def RunStep(self):
158     for commit_hash in self["patch_commit_hashes"]:
159       print("Applying patch for %s to %s..."
160             % (commit_hash, self["merge_to_branch"]))
161       patch = self.GitGetPatch(commit_hash)
162       TextToFile(patch, self.Config(TEMPORARY_PATCH_FILE))
163       self.ApplyPatch(self.Config(TEMPORARY_PATCH_FILE), self._options.revert)
164     if self._options.patch:
165       self.ApplyPatch(self._options.patch, self._options.revert)
166
167
168 class PrepareVersion(Step):
169   MESSAGE = "Prepare version file."
170
171   def RunStep(self):
172     if self._options.revert_bleeding_edge:
173       return
174     # This is used to calculate the patch level increment.
175     self.ReadAndPersistVersion()
176
177
178 class IncrementVersion(Step):
179   MESSAGE = "Increment version number."
180
181   def RunStep(self):
182     if self._options.revert_bleeding_edge:
183       return
184     new_patch = str(int(self["patch"]) + 1)
185     if self.Confirm("Automatically increment PATCH_LEVEL? (Saying 'n' will "
186                     "fire up your EDITOR on %s so you can make arbitrary "
187                     "changes. When you're done, save the file and exit your "
188                     "EDITOR.)" % self.Config(VERSION_FILE)):
189       text = FileToText(self.Config(VERSION_FILE))
190       text = MSub(r"(?<=#define PATCH_LEVEL)(?P<space>\s+)\d*$",
191                   r"\g<space>%s" % new_patch,
192                   text)
193       TextToFile(text, self.Config(VERSION_FILE))
194     else:
195       self.Editor(self.Config(VERSION_FILE))
196     self.ReadAndPersistVersion("new_")
197     self["version"] = "%s.%s.%s.%s" % (self["new_major"],
198                                        self["new_minor"],
199                                        self["new_build"],
200                                        self["new_patch"])
201
202
203 class CommitLocal(Step):
204   MESSAGE = "Commit to local branch."
205
206   def RunStep(self):
207     # Add a commit message title.
208     if self._options.revert:
209       if not self._options.revert_bleeding_edge:
210         title = ("Version %s (rollback of %s)"
211                  % (self["version"], self["revision_list"]))
212       else:
213         title = "Revert %s." % self["revision_list"]
214     else:
215       title = ("Version %s (merged %s)"
216                % (self["version"], self["revision_list"]))
217     self["new_commit_msg"] = "%s\n\n%s" % (title, self["new_commit_msg"])
218     TextToFile(self["new_commit_msg"], self.Config(COMMITMSG_FILE))
219     self.GitCommit(file_name=self.Config(COMMITMSG_FILE))
220
221
222 class CommitRepository(Step):
223   MESSAGE = "Commit to the repository."
224
225   def RunStep(self):
226     self.GitCheckout(self.Config(BRANCHNAME))
227     self.WaitForLGTM()
228     self.GitPresubmit()
229     self.GitDCommit()
230
231
232 class PrepareSVN(Step):
233   MESSAGE = "Determine svn commit revision."
234
235   def RunStep(self):
236     if self._options.revert_bleeding_edge:
237       return
238     self.GitSVNFetch()
239     commit_hash = self.GitLog(n=1, format="%H", grep=self["new_commit_msg"],
240                               branch="svn/%s" % self["merge_to_branch"])
241     if not commit_hash:  # pragma: no cover
242       self.Die("Unable to map git commit to svn revision.")
243     self["svn_revision"] = self.GitSVNFindSVNRev(commit_hash)
244     print "subversion revision number is r%s" % self["svn_revision"]
245
246
247 class TagRevision(Step):
248   MESSAGE = "Create the tag."
249
250   def RunStep(self):
251     if self._options.revert_bleeding_edge:
252       return
253     print "Creating tag svn/tags/%s" % self["version"]
254     if self["merge_to_branch"] == "trunk":
255       self["to_url"] = "trunk"
256     else:
257       self["to_url"] = "branches/%s" % self["merge_to_branch"]
258     self.SVN("copy -r %s https://v8.googlecode.com/svn/%s "
259              "https://v8.googlecode.com/svn/tags/%s -m "
260              "\"Tagging version %s\""
261              % (self["svn_revision"], self["to_url"],
262                 self["version"], self["version"]))
263
264
265 class CleanUp(Step):
266   MESSAGE = "Cleanup."
267
268   def RunStep(self):
269     self.CommonCleanup()
270     if not self._options.revert_bleeding_edge:
271       print "*** SUMMARY ***"
272       print "version: %s" % self["version"]
273       print "branch: %s" % self["to_url"]
274       print "svn revision: %s" % self["svn_revision"]
275       if self["revision_list"]:
276         print "patches: %s" % self["revision_list"]
277
278
279 class MergeToBranch(ScriptsBase):
280   def _Description(self):
281     return ("Performs the necessary steps to merge revisions from "
282             "bleeding_edge to other branches, including trunk.")
283
284   def _PrepareOptions(self, parser):
285     group = parser.add_mutually_exclusive_group(required=True)
286     group.add_argument("--branch", help="The branch to merge to.")
287     group.add_argument("-R", "--revert-bleeding-edge",
288                        help="Revert specified patches from bleeding edge.",
289                        default=False, action="store_true")
290     parser.add_argument("revisions", nargs="*",
291                         help="The revisions to merge.")
292     parser.add_argument("-f", "--force",
293                         help="Delete sentinel file.",
294                         default=False, action="store_true")
295     parser.add_argument("-m", "--message",
296                         help="A commit message for the patch.")
297     parser.add_argument("--revert",
298                         help="Revert specified patches.",
299                         default=False, action="store_true")
300     parser.add_argument("-p", "--patch",
301                         help="A patch file to apply as part of the merge.")
302
303   def _ProcessOptions(self, options):
304     # TODO(machenbach): Add a test that covers revert from bleeding_edge
305     if len(options.revisions) < 1:
306       if not options.patch:
307         print "Either a patch file or revision numbers must be specified"
308         return False
309       if not options.message:
310         print "You must specify a merge comment if no patches are specified"
311         return False
312     return True
313
314   def _Steps(self):
315     return [
316       Preparation,
317       CreateBranch,
318       SearchArchitecturePorts,
319       FindGitRevisions,
320       ApplyPatches,
321       PrepareVersion,
322       IncrementVersion,
323       CommitLocal,
324       UploadStep,
325       CommitRepository,
326       PrepareSVN,
327       TagRevision,
328       CleanUp,
329     ]
330
331
332 if __name__ == "__main__":  # pragma: no cover
333   sys.exit(MergeToBranch(CONFIG).Run())