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