View Javadoc
1   /*
2    * Copyright (C) 2010, Stefan Lay <stefan.lay@sap.com>
3    * Copyright (C) 2010-2012, Christian Halstrick <christian.halstrick@sap.com>
4    * and other copyright owners as documented in the project's IP log.
5    *
6    * This program and the accompanying materials are made available
7    * under the terms of the Eclipse Distribution License v1.0 which
8    * accompanies this distribution, is reproduced below, and is
9    * available at http://www.eclipse.org/org/documents/edl-v10.php
10   *
11   * All rights reserved.
12   *
13   * Redistribution and use in source and binary forms, with or
14   * without modification, are permitted provided that the following
15   * conditions are met:
16   *
17   * - Redistributions of source code must retain the above copyright
18   *   notice, this list of conditions and the following disclaimer.
19   *
20   * - Redistributions in binary form must reproduce the above
21   *   copyright notice, this list of conditions and the following
22   *   disclaimer in the documentation and/or other materials provided
23   *   with the distribution.
24   *
25   * - Neither the name of the Eclipse Foundation, Inc. nor the
26   *   names of its contributors may be used to endorse or promote
27   *   products derived from this software without specific prior
28   *   written permission.
29   *
30   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43   */
44  package org.eclipse.jgit.api;
45  
46  import java.text.MessageFormat;
47  import java.util.HashMap;
48  import java.util.List;
49  import java.util.Map;
50  
51  import org.eclipse.jgit.internal.JGitText;
52  import org.eclipse.jgit.lib.ObjectId;
53  import org.eclipse.jgit.merge.MergeChunk;
54  import org.eclipse.jgit.merge.MergeChunk.ConflictState;
55  import org.eclipse.jgit.merge.MergeStrategy;
56  import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
57  
58  /**
59   * Encapsulates the result of a {@link org.eclipse.jgit.api.MergeCommand}.
60   */
61  public class MergeResult {
62  
63  	/**
64  	 * The status the merge resulted in.
65  	 */
66  	public enum MergeStatus {
67  		/** */
68  		FAST_FORWARD {
69  			@Override
70  			public String toString() {
71  				return "Fast-forward"; //$NON-NLS-1$
72  			}
73  
74  			@Override
75  			public boolean isSuccessful() {
76  				return true;
77  			}
78  		},
79  		/**
80  		 * @since 2.0
81  		 */
82  		FAST_FORWARD_SQUASHED {
83  			@Override
84  			public String toString() {
85  				return "Fast-forward-squashed"; //$NON-NLS-1$
86  			}
87  
88  			@Override
89  			public boolean isSuccessful() {
90  				return true;
91  			}
92  		},
93  		/** */
94  		ALREADY_UP_TO_DATE {
95  			@Override
96  			public String toString() {
97  				return "Already-up-to-date"; //$NON-NLS-1$
98  			}
99  
100 			@Override
101 			public boolean isSuccessful() {
102 				return true;
103 			}
104 		},
105 		/** */
106 		FAILED {
107 			@Override
108 			public String toString() {
109 				return "Failed"; //$NON-NLS-1$
110 			}
111 
112 			@Override
113 			public boolean isSuccessful() {
114 				return false;
115 			}
116 		},
117 		/** */
118 		MERGED {
119 			@Override
120 			public String toString() {
121 				return "Merged"; //$NON-NLS-1$
122 			}
123 
124 			@Override
125 			public boolean isSuccessful() {
126 				return true;
127 			}
128 		},
129 		/**
130 		 * @since 2.0
131 		 */
132 		MERGED_SQUASHED {
133 			@Override
134 			public String toString() {
135 				return "Merged-squashed"; //$NON-NLS-1$
136 			}
137 
138 			@Override
139 			public boolean isSuccessful() {
140 				return true;
141 			}
142 		},
143 		/**
144 		 * @since 3.0
145 		 */
146 		MERGED_SQUASHED_NOT_COMMITTED {
147 			@Override
148 			public String toString() {
149 				return "Merged-squashed-not-committed"; //$NON-NLS-1$
150 			}
151 
152 			@Override
153 			public boolean isSuccessful() {
154 				return true;
155 			}
156 		},
157 		/** */
158 		CONFLICTING {
159 			@Override
160 			public String toString() {
161 				return "Conflicting"; //$NON-NLS-1$
162 			}
163 
164 			@Override
165 			public boolean isSuccessful() {
166 				return false;
167 			}
168 		},
169 		/**
170 		 * @since 2.2
171 		 */
172 		ABORTED {
173 			@Override
174 			public String toString() {
175 				return "Aborted"; //$NON-NLS-1$
176 			}
177 
178 			@Override
179 			public boolean isSuccessful() {
180 				return false;
181 			}
182 		},
183 		/**
184 		 * @since 3.0
185 		 **/
186 		MERGED_NOT_COMMITTED {
187 			@Override
188 			public String toString() {
189 				return "Merged-not-committed"; //$NON-NLS-1$
190 			}
191 
192 			@Override
193 			public boolean isSuccessful() {
194 				return true;
195 			}
196 		},
197 		/** */
198 		NOT_SUPPORTED {
199 			@Override
200 			public String toString() {
201 				return "Not-yet-supported"; //$NON-NLS-1$
202 			}
203 
204 			@Override
205 			public boolean isSuccessful() {
206 				return false;
207 			}
208 		},
209 		/**
210 		 * Status representing a checkout conflict, meaning that nothing could
211 		 * be merged, as the pre-scan for the trees already failed for certain
212 		 * files (i.e. local modifications prevent checkout of files).
213 		 */
214 		CHECKOUT_CONFLICT {
215 			@Override
216 			public String toString() {
217 				return "Checkout Conflict"; //$NON-NLS-1$
218 			}
219 
220 			@Override
221 			public boolean isSuccessful() {
222 				return false;
223 			}
224 		};
225 
226 		/**
227 		 * @return whether the status indicates a successful result
228 		 */
229 		public abstract boolean isSuccessful();
230 	}
231 
232 	private ObjectId[] mergedCommits;
233 
234 	private ObjectId base;
235 
236 	private ObjectId newHead;
237 
238 	private Map<String, int[][]> conflicts;
239 
240 	private MergeStatus mergeStatus;
241 
242 	private String description;
243 
244 	private MergeStrategy mergeStrategy;
245 
246 	private Map<String, MergeFailureReason> failingPaths;
247 
248 	private List<String> checkoutConflicts;
249 
250 	/**
251 	 * Constructor for MergeResult.
252 	 *
253 	 * @param newHead
254 	 *            the object the head points at after the merge
255 	 * @param base
256 	 *            the common base which was used to produce a content-merge. May
257 	 *            be <code>null</code> if the merge-result was produced without
258 	 *            computing a common base
259 	 * @param mergedCommits
260 	 *            all the commits which have been merged together
261 	 * @param mergeStatus
262 	 *            the status the merge resulted in
263 	 * @param mergeStrategy
264 	 *            the used {@link org.eclipse.jgit.merge.MergeStrategy}
265 	 * @param lowLevelResults
266 	 *            merge results as returned by
267 	 *            {@link org.eclipse.jgit.merge.ResolveMerger#getMergeResults()}
268 	 * @since 2.0
269 	 */
270 	public MergeResult(ObjectId newHead, ObjectId base,
271 			ObjectId[] mergedCommits, MergeStatus mergeStatus,
272 			MergeStrategy mergeStrategy,
273 			Map<String, org.eclipse.jgit.merge.MergeResult<?>> lowLevelResults) {
274 		this(newHead, base, mergedCommits, mergeStatus, mergeStrategy,
275 				lowLevelResults, null);
276 	}
277 
278 	/**
279 	 * Constructor for MergeResult.
280 	 *
281 	 * @param newHead
282 	 *            the object the head points at after the merge
283 	 * @param base
284 	 *            the common base which was used to produce a content-merge. May
285 	 *            be <code>null</code> if the merge-result was produced without
286 	 *            computing a common base
287 	 * @param mergedCommits
288 	 *            all the commits which have been merged together
289 	 * @param mergeStatus
290 	 *            the status the merge resulted in
291 	 * @param mergeStrategy
292 	 *            the used {@link org.eclipse.jgit.merge.MergeStrategy}
293 	 * @param lowLevelResults
294 	 *            merge results as returned by
295 	 *            {@link org.eclipse.jgit.merge.ResolveMerger#getMergeResults()}
296 	 * @param description
297 	 *            a user friendly description of the merge result
298 	 */
299 	public MergeResult(ObjectId newHead, ObjectId base,
300 			ObjectId[] mergedCommits, MergeStatus mergeStatus,
301 			MergeStrategy mergeStrategy,
302 			Map<String, org.eclipse.jgit.merge.MergeResult<?>> lowLevelResults,
303 			String description) {
304 		this(newHead, base, mergedCommits, mergeStatus, mergeStrategy,
305 				lowLevelResults, null, description);
306 	}
307 
308 	/**
309 	 * Constructor for MergeResult.
310 	 *
311 	 * @param newHead
312 	 *            the object the head points at after the merge
313 	 * @param base
314 	 *            the common base which was used to produce a content-merge. May
315 	 *            be <code>null</code> if the merge-result was produced without
316 	 *            computing a common base
317 	 * @param mergedCommits
318 	 *            all the commits which have been merged together
319 	 * @param mergeStatus
320 	 *            the status the merge resulted in
321 	 * @param mergeStrategy
322 	 *            the used {@link org.eclipse.jgit.merge.MergeStrategy}
323 	 * @param lowLevelResults
324 	 *            merge results as returned by
325 	 *            {@link org.eclipse.jgit.merge.ResolveMerger#getMergeResults()}
326 	 * @param failingPaths
327 	 *            list of paths causing this merge to fail as returned by
328 	 *            {@link org.eclipse.jgit.merge.ResolveMerger#getFailingPaths()}
329 	 * @param description
330 	 *            a user friendly description of the merge result
331 	 */
332 	public MergeResult(ObjectId newHead, ObjectId base,
333 			ObjectId[] mergedCommits, MergeStatus mergeStatus,
334 			MergeStrategy mergeStrategy,
335 			Map<String, org.eclipse.jgit.merge.MergeResult<?>> lowLevelResults,
336 			Map<String, MergeFailureReason> failingPaths, String description) {
337 		this.newHead = newHead;
338 		this.mergedCommits = mergedCommits;
339 		this.base = base;
340 		this.mergeStatus = mergeStatus;
341 		this.mergeStrategy = mergeStrategy;
342 		this.description = description;
343 		this.failingPaths = failingPaths;
344 		if (lowLevelResults != null)
345 			for (Map.Entry<String, org.eclipse.jgit.merge.MergeResult<?>> result : lowLevelResults
346 					.entrySet())
347 				addConflict(result.getKey(), result.getValue());
348 	}
349 
350 	/**
351 	 * Creates a new result that represents a checkout conflict before the
352 	 * operation even started for real.
353 	 *
354 	 * @param checkoutConflicts
355 	 *            the conflicting files
356 	 */
357 	public MergeResult(List<String> checkoutConflicts) {
358 		this.checkoutConflicts = checkoutConflicts;
359 		this.mergeStatus = MergeStatus.CHECKOUT_CONFLICT;
360 	}
361 
362 	/**
363 	 * Get the object the head points at after the merge
364 	 *
365 	 * @return the object the head points at after the merge
366 	 */
367 	public ObjectId getNewHead() {
368 		return newHead;
369 	}
370 
371 	/**
372 	 * Get the merge status
373 	 *
374 	 * @return the status the merge resulted in
375 	 */
376 	public MergeStatus getMergeStatus() {
377 		return mergeStatus;
378 	}
379 
380 	/**
381 	 * Get the commits which have been merged
382 	 *
383 	 * @return all the commits which have been merged together
384 	 */
385 	public ObjectId[] getMergedCommits() {
386 		return mergedCommits;
387 	}
388 
389 	/**
390 	 * Get the common base
391 	 *
392 	 * @return base the common base which was used to produce a content-merge.
393 	 *         May be <code>null</code> if the merge-result was produced without
394 	 *         computing a common base
395 	 */
396 	public ObjectId getBase() {
397 		return base;
398 	}
399 
400 	/** {@inheritDoc} */
401 	@SuppressWarnings("nls")
402 	@Override
403 	public String toString() {
404 		boolean first = true;
405 		StringBuilder commits = new StringBuilder();
406 		for (ObjectId commit : mergedCommits) {
407 			if (!first)
408 				commits.append(", ");
409 			else
410 				first = false;
411 			commits.append(ObjectId.toString(commit));
412 		}
413 		return MessageFormat.format(
414 				JGitText.get().mergeUsingStrategyResultedInDescription,
415 				commits, ObjectId.toString(base), mergeStrategy.getName(),
416 				mergeStatus, (description == null ? "" : ", " + description));
417 	}
418 
419 	/**
420 	 * Set conflicts
421 	 *
422 	 * @param conflicts
423 	 *            the conflicts to set
424 	 */
425 	public void setConflicts(Map<String, int[][]> conflicts) {
426 		this.conflicts = conflicts;
427 	}
428 
429 	/**
430 	 * Add a conflict
431 	 *
432 	 * @param path
433 	 *            path of the file to add a conflict for
434 	 * @param conflictingRanges
435 	 *            the conflicts to set
436 	 */
437 	public void addConflict(String path, int[][] conflictingRanges) {
438 		if (conflicts == null)
439 			conflicts = new HashMap<>();
440 		conflicts.put(path, conflictingRanges);
441 	}
442 
443 	/**
444 	 * Add a conflict
445 	 *
446 	 * @param path
447 	 *            path of the file to add a conflict for
448 	 * @param lowLevelResult
449 	 *            a {@link org.eclipse.jgit.merge.MergeResult}
450 	 */
451 	public void addConflict(String path, org.eclipse.jgit.merge.MergeResult<?> lowLevelResult) {
452 		if (!lowLevelResult.containsConflicts())
453 			return;
454 		if (conflicts == null)
455 			conflicts = new HashMap<>();
456 		int nrOfConflicts = 0;
457 		// just counting
458 		for (MergeChunk mergeChunk : lowLevelResult) {
459 			if (mergeChunk.getConflictState().equals(ConflictState.FIRST_CONFLICTING_RANGE)) {
460 				nrOfConflicts++;
461 			}
462 		}
463 		int currentConflict = -1;
464 		int[][] ret=new int[nrOfConflicts][mergedCommits.length+1];
465 		for (MergeChunk mergeChunk : lowLevelResult) {
466 			// to store the end of this chunk (end of the last conflicting range)
467 			int endOfChunk = 0;
468 			if (mergeChunk.getConflictState().equals(ConflictState.FIRST_CONFLICTING_RANGE)) {
469 				if (currentConflict > -1) {
470 					// there was a previous conflicting range for which the end
471 					// is not set yet - set it!
472 					ret[currentConflict][mergedCommits.length] = endOfChunk;
473 				}
474 				currentConflict++;
475 				endOfChunk = mergeChunk.getEnd();
476 				ret[currentConflict][mergeChunk.getSequenceIndex()] = mergeChunk.getBegin();
477 			}
478 			if (mergeChunk.getConflictState().equals(ConflictState.NEXT_CONFLICTING_RANGE)) {
479 				if (mergeChunk.getEnd() > endOfChunk)
480 					endOfChunk = mergeChunk.getEnd();
481 				ret[currentConflict][mergeChunk.getSequenceIndex()] = mergeChunk.getBegin();
482 			}
483 		}
484 		conflicts.put(path, ret);
485 	}
486 
487 	/**
488 	 * Returns information about the conflicts which occurred during a
489 	 * {@link org.eclipse.jgit.api.MergeCommand}. The returned value maps the
490 	 * path of a conflicting file to a two-dimensional int-array of line-numbers
491 	 * telling where in the file conflict markers for which merged commit can be
492 	 * found.
493 	 * <p>
494 	 * If the returned value contains a mapping "path"-&gt;[x][y]=z then this
495 	 * means
496 	 * <ul>
497 	 * <li>the file with path "path" contains conflicts</li>
498 	 * <li>if y &lt; "number of merged commits": for conflict number x in this
499 	 * file the chunk which was copied from commit number y starts on line
500 	 * number z. All numberings and line numbers start with 0.</li>
501 	 * <li>if y == "number of merged commits": the first non-conflicting line
502 	 * after conflict number x starts at line number z</li>
503 	 * </ul>
504 	 * <p>
505 	 * Example code how to parse this data:
506 	 *
507 	 * <pre>
508 	 * MergeResult m=...;
509 	 * Map&lt;String, int[][]&gt; allConflicts = m.getConflicts();
510 	 * for (String path : allConflicts.keySet()) {
511 	 * 	int[][] c = allConflicts.get(path);
512 	 * 	System.out.println("Conflicts in file " + path);
513 	 * 	for (int i = 0; i &lt; c.length; ++i) {
514 	 * 		System.out.println("  Conflict #" + i);
515 	 * 		for (int j = 0; j &lt; (c[i].length) - 1; ++j) {
516 	 * 			if (c[i][j] &gt;= 0)
517 	 * 				System.out.println("    Chunk for "
518 	 * 						+ m.getMergedCommits()[j] + " starts on line #"
519 	 * 						+ c[i][j]);
520 	 * 		}
521 	 * 	}
522 	 * }
523 	 * </pre>
524 	 *
525 	 * @return the conflicts or <code>null</code> if no conflict occurred
526 	 */
527 	public Map<String, int[][]> getConflicts() {
528 		return conflicts;
529 	}
530 
531 	/**
532 	 * Returns a list of paths causing this merge to fail as returned by
533 	 * {@link org.eclipse.jgit.merge.ResolveMerger#getFailingPaths()}
534 	 *
535 	 * @return the list of paths causing this merge to fail or <code>null</code>
536 	 *         if no failure occurred
537 	 */
538 	public Map<String, MergeFailureReason> getFailingPaths() {
539 		return failingPaths;
540 	}
541 
542 	/**
543 	 * Returns a list of paths that cause a checkout conflict. These paths
544 	 * prevent the operation from even starting.
545 	 *
546 	 * @return the list of files that caused the checkout conflict.
547 	 */
548 	public List<String> getCheckoutConflicts() {
549 		return checkoutConflicts;
550 	}
551 }