You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RecursiveMerger.java 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. /*
  2. * Copyright (C) 2012, Research In Motion Limited
  3. * Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com> and others
  4. *
  5. * This program and the accompanying materials are made available under the
  6. * terms of the Eclipse Distribution License v. 1.0 which is available at
  7. * https://www.eclipse.org/org/documents/edl-v10.php.
  8. *
  9. * SPDX-License-Identifier: BSD-3-Clause
  10. */
  11. /*
  12. * Contributors:
  13. * George Young - initial API and implementation
  14. * Christian Halstrick - initial API and implementation
  15. */
  16. package org.eclipse.jgit.merge;
  17. import java.io.IOException;
  18. import java.text.MessageFormat;
  19. import java.util.ArrayList;
  20. import java.util.Date;
  21. import java.util.List;
  22. import java.util.TimeZone;
  23. import org.eclipse.jgit.dircache.DirCache;
  24. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  25. import org.eclipse.jgit.errors.NoMergeBaseException;
  26. import org.eclipse.jgit.internal.JGitText;
  27. import org.eclipse.jgit.lib.CommitBuilder;
  28. import org.eclipse.jgit.lib.Config;
  29. import org.eclipse.jgit.lib.ObjectId;
  30. import org.eclipse.jgit.lib.ObjectInserter;
  31. import org.eclipse.jgit.lib.PersonIdent;
  32. import org.eclipse.jgit.lib.Repository;
  33. import org.eclipse.jgit.revwalk.RevCommit;
  34. import org.eclipse.jgit.revwalk.filter.RevFilter;
  35. import org.eclipse.jgit.treewalk.AbstractTreeIterator;
  36. import org.eclipse.jgit.treewalk.EmptyTreeIterator;
  37. import org.eclipse.jgit.treewalk.WorkingTreeIterator;
  38. /**
  39. * A three-way merger performing a content-merge if necessary across multiple
  40. * bases using recursion
  41. *
  42. * This merger extends the resolve merger and does several things differently:
  43. *
  44. * - allow more than one merge base, up to a maximum
  45. *
  46. * - uses "Lists" instead of Arrays for chained types
  47. *
  48. * - recursively merges the merge bases together to compute a usable base
  49. *
  50. * @since 3.0
  51. */
  52. public class RecursiveMerger extends ResolveMerger {
  53. /**
  54. * The maximum number of merge bases. This merge will stop when the number
  55. * of merge bases exceeds this value
  56. */
  57. public final int MAX_BASES = 200;
  58. /**
  59. * Normal recursive merge when you want a choice of DirCache placement
  60. * inCore
  61. *
  62. * @param local
  63. * a {@link org.eclipse.jgit.lib.Repository} object.
  64. * @param inCore
  65. * a boolean.
  66. */
  67. protected RecursiveMerger(Repository local, boolean inCore) {
  68. super(local, inCore);
  69. }
  70. /**
  71. * Normal recursive merge, implies not inCore
  72. *
  73. * @param local a {@link org.eclipse.jgit.lib.Repository} object.
  74. */
  75. protected RecursiveMerger(Repository local) {
  76. this(local, false);
  77. }
  78. /**
  79. * Normal recursive merge, implies inCore.
  80. *
  81. * @param inserter
  82. * an {@link org.eclipse.jgit.lib.ObjectInserter} object.
  83. * @param config
  84. * the repository configuration
  85. * @since 4.8
  86. */
  87. protected RecursiveMerger(ObjectInserter inserter, Config config) {
  88. super(inserter, config);
  89. }
  90. /**
  91. * {@inheritDoc}
  92. * <p>
  93. * Get a single base commit for two given commits. If the two source commits
  94. * have more than one base commit recursively merge the base commits
  95. * together until you end up with a single base commit.
  96. */
  97. @Override
  98. protected RevCommit getBaseCommit(RevCommit a, RevCommit b)
  99. throws IncorrectObjectTypeException, IOException {
  100. return getBaseCommit(a, b, 0);
  101. }
  102. /**
  103. * Get a single base commit for two given commits. If the two source commits
  104. * have more than one base commit recursively merge the base commits
  105. * together until a virtual common base commit has been found.
  106. *
  107. * @param a
  108. * the first commit to be merged
  109. * @param b
  110. * the second commit to be merged
  111. * @param callDepth
  112. * the callDepth when this method is called recursively
  113. * @return the merge base of two commits. If a criss-cross merge required a
  114. * synthetic merge base this commit is visible only the merger's
  115. * RevWalk and will not be in the repository.
  116. * @throws java.io.IOException
  117. * @throws IncorrectObjectTypeException
  118. * one of the input objects is not a commit.
  119. * @throws NoMergeBaseException
  120. * too many merge bases are found or the computation of a common
  121. * merge base failed (e.g. because of a conflict).
  122. */
  123. protected RevCommit getBaseCommit(RevCommit a, RevCommit b, int callDepth)
  124. throws IOException {
  125. ArrayList<RevCommit> baseCommits = new ArrayList<>();
  126. walk.reset();
  127. walk.setRevFilter(RevFilter.MERGE_BASE);
  128. walk.markStart(a);
  129. walk.markStart(b);
  130. RevCommit c;
  131. while ((c = walk.next()) != null)
  132. baseCommits.add(c);
  133. if (baseCommits.isEmpty())
  134. return null;
  135. if (baseCommits.size() == 1)
  136. return baseCommits.get(0);
  137. if (baseCommits.size() >= MAX_BASES)
  138. throw new NoMergeBaseException(NoMergeBaseException.MergeBaseFailureReason.TOO_MANY_MERGE_BASES, MessageFormat.format(
  139. JGitText.get().mergeRecursiveTooManyMergeBasesFor,
  140. Integer.valueOf(MAX_BASES), a.name(), b.name(),
  141. Integer.valueOf(baseCommits.size())));
  142. // We know we have more than one base commit. We have to do merges now
  143. // to determine a single base commit. We don't want to spoil the current
  144. // dircache and working tree with the results of this intermediate
  145. // merges. Therefore set the dircache to a new in-memory dircache and
  146. // disable that we update the working-tree. We set this back to the
  147. // original values once a single base commit is created.
  148. RevCommit currentBase = baseCommits.get(0);
  149. DirCache oldDircache = dircache;
  150. boolean oldIncore = inCore;
  151. WorkingTreeIterator oldWTreeIt = workingTreeIterator;
  152. workingTreeIterator = null;
  153. try {
  154. dircache = DirCache.read(reader, currentBase.getTree());
  155. inCore = true;
  156. List<RevCommit> parents = new ArrayList<>();
  157. parents.add(currentBase);
  158. for (int commitIdx = 1; commitIdx < baseCommits.size(); commitIdx++) {
  159. RevCommit nextBase = baseCommits.get(commitIdx);
  160. if (commitIdx >= MAX_BASES)
  161. throw new NoMergeBaseException(
  162. NoMergeBaseException.MergeBaseFailureReason.TOO_MANY_MERGE_BASES,
  163. MessageFormat.format(
  164. JGitText.get().mergeRecursiveTooManyMergeBasesFor,
  165. Integer.valueOf(MAX_BASES), a.name(), b.name(),
  166. Integer.valueOf(baseCommits.size())));
  167. parents.add(nextBase);
  168. RevCommit bc = getBaseCommit(currentBase, nextBase,
  169. callDepth + 1);
  170. AbstractTreeIterator bcTree = (bc == null) ? new EmptyTreeIterator()
  171. : openTree(bc.getTree());
  172. if (mergeTrees(bcTree, currentBase.getTree(),
  173. nextBase.getTree(), true))
  174. currentBase = createCommitForTree(resultTree, parents);
  175. else
  176. throw new NoMergeBaseException(
  177. NoMergeBaseException.MergeBaseFailureReason.CONFLICTS_DURING_MERGE_BASE_CALCULATION,
  178. MessageFormat.format(
  179. JGitText.get().mergeRecursiveConflictsWhenMergingCommonAncestors,
  180. currentBase.getName(), nextBase.getName()));
  181. }
  182. } finally {
  183. inCore = oldIncore;
  184. dircache = oldDircache;
  185. workingTreeIterator = oldWTreeIt;
  186. toBeCheckedOut.clear();
  187. toBeDeleted.clear();
  188. modifiedFiles.clear();
  189. unmergedPaths.clear();
  190. mergeResults.clear();
  191. failingPaths.clear();
  192. }
  193. return currentBase;
  194. }
  195. /**
  196. * Create a new commit by explicitly specifying the content tree and the
  197. * parents. The commit message is not set and author/committer are set to
  198. * the current user.
  199. *
  200. * @param tree
  201. * the tree this commit should capture
  202. * @param parents
  203. * the list of parent commits
  204. * @return a new commit visible only within this merger's RevWalk.
  205. * @throws IOException
  206. */
  207. private RevCommit createCommitForTree(ObjectId tree, List<RevCommit> parents)
  208. throws IOException {
  209. CommitBuilder c = new CommitBuilder();
  210. c.setTreeId(tree);
  211. c.setParentIds(parents);
  212. c.setAuthor(mockAuthor(parents));
  213. c.setCommitter(c.getAuthor());
  214. return RevCommit.parse(walk, c.build());
  215. }
  216. private static PersonIdent mockAuthor(List<RevCommit> parents) {
  217. String name = RecursiveMerger.class.getSimpleName();
  218. int time = 0;
  219. for (RevCommit p : parents)
  220. time = Math.max(time, p.getCommitTime());
  221. return new PersonIdent(
  222. name, name + "@JGit", //$NON-NLS-1$
  223. new Date((time + 1) * 1000L),
  224. TimeZone.getTimeZone("GMT+0000")); //$NON-NLS-1$
  225. }
  226. }