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