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.

MergeAlgorithm.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. /*
  2. * Copyright (C) 2009, Christian Halstrick <christian.halstrick@sap.com> and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.merge;
  11. import java.util.ArrayList;
  12. import java.util.Iterator;
  13. import java.util.List;
  14. import org.eclipse.jgit.annotations.NonNull;
  15. import org.eclipse.jgit.diff.DiffAlgorithm;
  16. import org.eclipse.jgit.diff.Edit;
  17. import org.eclipse.jgit.diff.EditList;
  18. import org.eclipse.jgit.diff.HistogramDiff;
  19. import org.eclipse.jgit.diff.Sequence;
  20. import org.eclipse.jgit.diff.SequenceComparator;
  21. import org.eclipse.jgit.merge.MergeChunk.ConflictState;
  22. /**
  23. * Provides the merge algorithm which does a three-way merge on content provided
  24. * as RawText. By default {@link org.eclipse.jgit.diff.HistogramDiff} is used as
  25. * diff algorithm.
  26. */
  27. public final class MergeAlgorithm {
  28. private final DiffAlgorithm diffAlg;
  29. @NonNull
  30. private ContentMergeStrategy strategy = ContentMergeStrategy.CONFLICT;
  31. /**
  32. * Creates a new MergeAlgorithm which uses
  33. * {@link org.eclipse.jgit.diff.HistogramDiff} as diff algorithm
  34. */
  35. public MergeAlgorithm() {
  36. this(new HistogramDiff());
  37. }
  38. /**
  39. * Creates a new MergeAlgorithm
  40. *
  41. * @param diff
  42. * the diff algorithm used by this merge
  43. */
  44. public MergeAlgorithm(DiffAlgorithm diff) {
  45. this.diffAlg = diff;
  46. }
  47. /**
  48. * Retrieves the {@link ContentMergeStrategy}.
  49. *
  50. * @return the {@link ContentMergeStrategy} in effect
  51. * @since 5.12
  52. */
  53. @NonNull
  54. public ContentMergeStrategy getContentMergeStrategy() {
  55. return strategy;
  56. }
  57. /**
  58. * Sets the {@link ContentMergeStrategy}.
  59. *
  60. * @param strategy
  61. * {@link ContentMergeStrategy} to set; if {@code null}, set
  62. * {@link ContentMergeStrategy#CONFLICT}
  63. * @since 5.12
  64. */
  65. public void setContentMergeStrategy(ContentMergeStrategy strategy) {
  66. this.strategy = strategy == null ? ContentMergeStrategy.CONFLICT
  67. : strategy;
  68. }
  69. // An special edit which acts as a sentinel value by marking the end the
  70. // list of edits
  71. private static final Edit END_EDIT = new Edit(Integer.MAX_VALUE,
  72. Integer.MAX_VALUE);
  73. @SuppressWarnings("ReferenceEquality")
  74. private static boolean isEndEdit(Edit edit) {
  75. return edit == END_EDIT;
  76. }
  77. /**
  78. * Does the three way merge between a common base and two sequences.
  79. *
  80. * @param cmp comparison method for this execution.
  81. * @param base the common base sequence
  82. * @param ours the first sequence to be merged
  83. * @param theirs the second sequence to be merged
  84. * @return the resulting content
  85. */
  86. public <S extends Sequence> MergeResult<S> merge(
  87. SequenceComparator<S> cmp, S base, S ours, S theirs) {
  88. List<S> sequences = new ArrayList<>(3);
  89. sequences.add(base);
  90. sequences.add(ours);
  91. sequences.add(theirs);
  92. MergeResult<S> result = new MergeResult<>(sequences);
  93. if (ours.size() == 0) {
  94. if (theirs.size() != 0) {
  95. EditList theirsEdits = diffAlg.diff(cmp, base, theirs);
  96. if (!theirsEdits.isEmpty()) {
  97. // we deleted, they modified
  98. switch (strategy) {
  99. case OURS:
  100. result.add(1, 0, 0, ConflictState.NO_CONFLICT);
  101. break;
  102. case THEIRS:
  103. result.add(2, 0, theirs.size(),
  104. ConflictState.NO_CONFLICT);
  105. break;
  106. default:
  107. // Let their complete content conflict with empty text
  108. result.add(1, 0, 0,
  109. ConflictState.FIRST_CONFLICTING_RANGE);
  110. result.add(2, 0, theirs.size(),
  111. ConflictState.NEXT_CONFLICTING_RANGE);
  112. break;
  113. }
  114. } else {
  115. // we deleted, they didn't modify -> Let our deletion win
  116. result.add(1, 0, 0, ConflictState.NO_CONFLICT);
  117. }
  118. } else {
  119. // we and they deleted -> return a single chunk of nothing
  120. result.add(1, 0, 0, ConflictState.NO_CONFLICT);
  121. }
  122. return result;
  123. } else if (theirs.size() == 0) {
  124. EditList oursEdits = diffAlg.diff(cmp, base, ours);
  125. if (!oursEdits.isEmpty()) {
  126. // we modified, they deleted
  127. switch (strategy) {
  128. case OURS:
  129. result.add(1, 0, ours.size(), ConflictState.NO_CONFLICT);
  130. break;
  131. case THEIRS:
  132. result.add(2, 0, 0, ConflictState.NO_CONFLICT);
  133. break;
  134. default:
  135. // Let our complete content conflict with empty text
  136. result.add(1, 0, ours.size(),
  137. ConflictState.FIRST_CONFLICTING_RANGE);
  138. result.add(2, 0, 0, ConflictState.NEXT_CONFLICTING_RANGE);
  139. break;
  140. }
  141. } else {
  142. // they deleted, we didn't modify -> Let their deletion win
  143. result.add(2, 0, 0, ConflictState.NO_CONFLICT);
  144. }
  145. return result;
  146. }
  147. EditList oursEdits = diffAlg.diff(cmp, base, ours);
  148. Iterator<Edit> baseToOurs = oursEdits.iterator();
  149. EditList theirsEdits = diffAlg.diff(cmp, base, theirs);
  150. Iterator<Edit> baseToTheirs = theirsEdits.iterator();
  151. int current = 0; // points to the next line (first line is 0) of base
  152. // which was not handled yet
  153. Edit oursEdit = nextEdit(baseToOurs);
  154. Edit theirsEdit = nextEdit(baseToTheirs);
  155. // iterate over all edits from base to ours and from base to theirs
  156. // leave the loop when there are no edits more for ours or for theirs
  157. // (or both)
  158. while (!isEndEdit(theirsEdit) || !isEndEdit(oursEdit)) {
  159. if (oursEdit.getEndA() < theirsEdit.getBeginA()) {
  160. // something was changed in ours not overlapping with any change
  161. // from theirs. First add the common part in front of the edit
  162. // then the edit.
  163. if (current != oursEdit.getBeginA()) {
  164. result.add(0, current, oursEdit.getBeginA(),
  165. ConflictState.NO_CONFLICT);
  166. }
  167. result.add(1, oursEdit.getBeginB(), oursEdit.getEndB(),
  168. ConflictState.NO_CONFLICT);
  169. current = oursEdit.getEndA();
  170. oursEdit = nextEdit(baseToOurs);
  171. } else if (theirsEdit.getEndA() < oursEdit.getBeginA()) {
  172. // something was changed in theirs not overlapping with any
  173. // from ours. First add the common part in front of the edit
  174. // then the edit.
  175. if (current != theirsEdit.getBeginA()) {
  176. result.add(0, current, theirsEdit.getBeginA(),
  177. ConflictState.NO_CONFLICT);
  178. }
  179. result.add(2, theirsEdit.getBeginB(), theirsEdit.getEndB(),
  180. ConflictState.NO_CONFLICT);
  181. current = theirsEdit.getEndA();
  182. theirsEdit = nextEdit(baseToTheirs);
  183. } else {
  184. // here we found a real overlapping modification
  185. // if there is a common part in front of the conflict add it
  186. if (oursEdit.getBeginA() != current
  187. && theirsEdit.getBeginA() != current) {
  188. result.add(0, current, Math.min(oursEdit.getBeginA(),
  189. theirsEdit.getBeginA()), ConflictState.NO_CONFLICT);
  190. }
  191. // set some initial values for the ranges in A and B which we
  192. // want to handle
  193. int oursBeginB = oursEdit.getBeginB();
  194. int theirsBeginB = theirsEdit.getBeginB();
  195. // harmonize the start of the ranges in A and B
  196. if (oursEdit.getBeginA() < theirsEdit.getBeginA()) {
  197. theirsBeginB -= theirsEdit.getBeginA()
  198. - oursEdit.getBeginA();
  199. } else {
  200. oursBeginB -= oursEdit.getBeginA() - theirsEdit.getBeginA();
  201. }
  202. // combine edits:
  203. // Maybe an Edit on one side corresponds to multiple Edits on
  204. // the other side. Then we have to combine the Edits of the
  205. // other side - so in the end we can merge together two single
  206. // edits.
  207. //
  208. // It is important to notice that this combining will extend the
  209. // ranges of our conflict always downwards (towards the end of
  210. // the content). The starts of the conflicting ranges in ours
  211. // and theirs are not touched here.
  212. //
  213. // This combining is an iterative process: after we have
  214. // combined some edits we have to do the check again. The
  215. // combined edits could now correspond to multiple edits on the
  216. // other side.
  217. //
  218. // Example: when this combining algorithm works on the following
  219. // edits
  220. // oursEdits=((0-5,0-5),(6-8,6-8),(10-11,10-11)) and
  221. // theirsEdits=((0-1,0-1),(2-3,2-3),(5-7,5-7))
  222. // it will merge them into
  223. // oursEdits=((0-8,0-8),(10-11,10-11)) and
  224. // theirsEdits=((0-7,0-7))
  225. //
  226. // Since the only interesting thing to us is how in ours and
  227. // theirs the end of the conflicting range is changing we let
  228. // oursEdit and theirsEdit point to the last conflicting edit
  229. Edit nextOursEdit = nextEdit(baseToOurs);
  230. Edit nextTheirsEdit = nextEdit(baseToTheirs);
  231. for (;;) {
  232. if (oursEdit.getEndA() >= nextTheirsEdit.getBeginA()) {
  233. theirsEdit = nextTheirsEdit;
  234. nextTheirsEdit = nextEdit(baseToTheirs);
  235. } else if (theirsEdit.getEndA() >= nextOursEdit.getBeginA()) {
  236. oursEdit = nextOursEdit;
  237. nextOursEdit = nextEdit(baseToOurs);
  238. } else {
  239. break;
  240. }
  241. }
  242. // harmonize the end of the ranges in A and B
  243. int oursEndB = oursEdit.getEndB();
  244. int theirsEndB = theirsEdit.getEndB();
  245. if (oursEdit.getEndA() < theirsEdit.getEndA()) {
  246. oursEndB += theirsEdit.getEndA() - oursEdit.getEndA();
  247. } else {
  248. theirsEndB += oursEdit.getEndA() - theirsEdit.getEndA();
  249. }
  250. // A conflicting region is found. Strip off common lines in
  251. // in the beginning and the end of the conflicting region
  252. // Determine the minimum length of the conflicting areas in OURS
  253. // and THEIRS. Also determine how much bigger the conflicting
  254. // area in THEIRS is compared to OURS. All that is needed to
  255. // limit the search for common areas at the beginning or end
  256. // (the common areas cannot be bigger then the smaller
  257. // conflicting area. The delta is needed to know whether the
  258. // complete conflicting area is common in OURS and THEIRS.
  259. int minBSize = oursEndB - oursBeginB;
  260. int BSizeDelta = minBSize - (theirsEndB - theirsBeginB);
  261. if (BSizeDelta > 0)
  262. minBSize -= BSizeDelta;
  263. int commonPrefix = 0;
  264. while (commonPrefix < minBSize
  265. && cmp.equals(ours, oursBeginB + commonPrefix, theirs,
  266. theirsBeginB + commonPrefix))
  267. commonPrefix++;
  268. minBSize -= commonPrefix;
  269. int commonSuffix = 0;
  270. while (commonSuffix < minBSize
  271. && cmp.equals(ours, oursEndB - commonSuffix - 1, theirs,
  272. theirsEndB - commonSuffix - 1))
  273. commonSuffix++;
  274. minBSize -= commonSuffix;
  275. // Add the common lines at start of conflict
  276. if (commonPrefix > 0)
  277. result.add(1, oursBeginB, oursBeginB + commonPrefix,
  278. ConflictState.NO_CONFLICT);
  279. // Add the conflict (Only if there is a conflict left to report)
  280. if (minBSize > 0 || BSizeDelta != 0) {
  281. switch (strategy) {
  282. case OURS:
  283. result.add(1, oursBeginB + commonPrefix,
  284. oursEndB - commonSuffix,
  285. ConflictState.NO_CONFLICT);
  286. break;
  287. case THEIRS:
  288. result.add(2, theirsBeginB + commonPrefix,
  289. theirsEndB - commonSuffix,
  290. ConflictState.NO_CONFLICT);
  291. break;
  292. default:
  293. result.add(1, oursBeginB + commonPrefix,
  294. oursEndB - commonSuffix,
  295. ConflictState.FIRST_CONFLICTING_RANGE);
  296. result.add(2, theirsBeginB + commonPrefix,
  297. theirsEndB - commonSuffix,
  298. ConflictState.NEXT_CONFLICTING_RANGE);
  299. break;
  300. }
  301. }
  302. // Add the common lines at end of conflict
  303. if (commonSuffix > 0)
  304. result.add(1, oursEndB - commonSuffix, oursEndB,
  305. ConflictState.NO_CONFLICT);
  306. current = Math.max(oursEdit.getEndA(), theirsEdit.getEndA());
  307. oursEdit = nextOursEdit;
  308. theirsEdit = nextTheirsEdit;
  309. }
  310. }
  311. // maybe we have a common part behind the last edit: copy it to the
  312. // result
  313. if (current < base.size()) {
  314. result.add(0, current, base.size(), ConflictState.NO_CONFLICT);
  315. }
  316. return result;
  317. }
  318. /**
  319. * Helper method which returns the next Edit for an Iterator over Edits.
  320. * When there are no more edits left this method will return the constant
  321. * END_EDIT.
  322. *
  323. * @param it
  324. * the iterator for which the next edit should be returned
  325. * @return the next edit from the iterator or END_EDIT if there no more
  326. * edits
  327. */
  328. private static Edit nextEdit(Iterator<Edit> it) {
  329. return (it.hasNext() ? it.next() : END_EDIT);
  330. }
  331. }