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;
57 import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
58
59 /**
60 * Encapsulates the result of a {@link MergeCommand}.
61 */
62 public class MergeResult {
63
64 /**
65 * The status the merge resulted in.
66 */
67 public enum MergeStatus {
68 /** */
69 FAST_FORWARD {
70 @Override
71 public String toString() {
72 return "Fast-forward"; //$NON-NLS-1$
73 }
74
75 @Override
76 public boolean isSuccessful() {
77 return true;
78 }
79 },
80 /**
81 * @since 2.0
82 */
83 FAST_FORWARD_SQUASHED {
84 @Override
85 public String toString() {
86 return "Fast-forward-squashed"; //$NON-NLS-1$
87 }
88
89 @Override
90 public boolean isSuccessful() {
91 return true;
92 }
93 },
94 /** */
95 ALREADY_UP_TO_DATE {
96 @Override
97 public String toString() {
98 return "Already-up-to-date"; //$NON-NLS-1$
99 }
100
101 @Override
102 public boolean isSuccessful() {
103 return true;
104 }
105 },
106 /** */
107 FAILED {
108 @Override
109 public String toString() {
110 return "Failed"; //$NON-NLS-1$
111 }
112
113 @Override
114 public boolean isSuccessful() {
115 return false;
116 }
117 },
118 /** */
119 MERGED {
120 @Override
121 public String toString() {
122 return "Merged"; //$NON-NLS-1$
123 }
124
125 @Override
126 public boolean isSuccessful() {
127 return true;
128 }
129 },
130 /**
131 * @since 2.0
132 */
133 MERGED_SQUASHED {
134 @Override
135 public String toString() {
136 return "Merged-squashed"; //$NON-NLS-1$
137 }
138
139 @Override
140 public boolean isSuccessful() {
141 return true;
142 }
143 },
144 /**
145 * @since 3.0
146 */
147 MERGED_SQUASHED_NOT_COMMITTED {
148 @Override
149 public String toString() {
150 return "Merged-squashed-not-committed"; //$NON-NLS-1$
151 }
152
153 @Override
154 public boolean isSuccessful() {
155 return true;
156 }
157 },
158 /** */
159 CONFLICTING {
160 @Override
161 public String toString() {
162 return "Conflicting"; //$NON-NLS-1$
163 }
164
165 @Override
166 public boolean isSuccessful() {
167 return false;
168 }
169 },
170 /**
171 * @since 2.2
172 */
173 ABORTED {
174 @Override
175 public String toString() {
176 return "Aborted"; //$NON-NLS-1$
177 }
178
179 @Override
180 public boolean isSuccessful() {
181 return false;
182 }
183 },
184 /**
185 * @since 3.0
186 **/
187 MERGED_NOT_COMMITTED {
188 @Override
189 public String toString() {
190 return "Merged-not-committed"; //$NON-NLS-1$
191 }
192
193 @Override
194 public boolean isSuccessful() {
195 return true;
196 }
197 },
198 /** */
199 NOT_SUPPORTED {
200 @Override
201 public String toString() {
202 return "Not-yet-supported"; //$NON-NLS-1$
203 }
204
205 @Override
206 public boolean isSuccessful() {
207 return false;
208 }
209 },
210 /**
211 * Status representing a checkout conflict, meaning that nothing could
212 * be merged, as the pre-scan for the trees already failed for certain
213 * files (i.e. local modifications prevent checkout of files).
214 */
215 CHECKOUT_CONFLICT {
216 @Override
217 public String toString() {
218 return "Checkout Conflict"; //$NON-NLS-1$
219 }
220
221 @Override
222 public boolean isSuccessful() {
223 return false;
224 }
225 };
226
227 /**
228 * @return whether the status indicates a successful result
229 */
230 public abstract boolean isSuccessful();
231 }
232
233 private ObjectId[] mergedCommits;
234
235 private ObjectId base;
236
237 private ObjectId newHead;
238
239 private Map<String, int[][]> conflicts;
240
241 private MergeStatus mergeStatus;
242
243 private String description;
244
245 private MergeStrategy mergeStrategy;
246
247 private Map<String, MergeFailureReason> failingPaths;
248
249 private List<String> checkoutConflicts;
250
251 /**
252 * @param newHead
253 * the object the head points at after the merge
254 * @param base
255 * the common base which was used to produce a content-merge. May
256 * be <code>null</code> if the merge-result was produced without
257 * computing a common base
258 * @param mergedCommits
259 * all the commits which have been merged together
260 * @param mergeStatus
261 * the status the merge resulted in
262 * @param mergeStrategy
263 * the used {@link MergeStrategy}
264 * @param lowLevelResults
265 * merge results as returned by
266 * {@link ResolveMerger#getMergeResults()}
267 * @since 2.0
268 */
269 public MergeResult(ObjectId newHead, ObjectId base,
270 ObjectId[] mergedCommits, MergeStatus mergeStatus,
271 MergeStrategy mergeStrategy,
272 Map<String, org.eclipse.jgit.merge.MergeResult<?>> lowLevelResults) {
273 this(newHead, base, mergedCommits, mergeStatus, mergeStrategy,
274 lowLevelResults, null);
275 }
276
277 /**
278 * @param newHead
279 * the object the head points at after the merge
280 * @param base
281 * the common base which was used to produce a content-merge. May
282 * be <code>null</code> if the merge-result was produced without
283 * computing a common base
284 * @param mergedCommits
285 * all the commits which have been merged together
286 * @param mergeStatus
287 * the status the merge resulted in
288 * @param mergeStrategy
289 * the used {@link MergeStrategy}
290 * @param lowLevelResults
291 * merge results as returned by {@link ResolveMerger#getMergeResults()}
292 * @param description
293 * a user friendly description of the merge result
294 */
295 public MergeResult(ObjectId newHead, ObjectId base,
296 ObjectId[] mergedCommits, MergeStatus mergeStatus,
297 MergeStrategy mergeStrategy,
298 Map<String, org.eclipse.jgit.merge.MergeResult<?>> lowLevelResults,
299 String description) {
300 this(newHead, base, mergedCommits, mergeStatus, mergeStrategy,
301 lowLevelResults, null, description);
302 }
303
304 /**
305 * @param newHead
306 * the object the head points at after the merge
307 * @param base
308 * the common base which was used to produce a content-merge. May
309 * be <code>null</code> if the merge-result was produced without
310 * computing a common base
311 * @param mergedCommits
312 * all the commits which have been merged together
313 * @param mergeStatus
314 * the status the merge resulted in
315 * @param mergeStrategy
316 * the used {@link MergeStrategy}
317 * @param lowLevelResults
318 * merge results as returned by
319 * {@link ResolveMerger#getMergeResults()}
320 * @param failingPaths
321 * list of paths causing this merge to fail as returned by
322 * {@link ResolveMerger#getFailingPaths()}
323 * @param description
324 * a user friendly description of the merge result
325 */
326 public MergeResult(ObjectId newHead, ObjectId base,
327 ObjectId[] mergedCommits, MergeStatus mergeStatus,
328 MergeStrategy mergeStrategy,
329 Map<String, org.eclipse.jgit.merge.MergeResult<?>> lowLevelResults,
330 Map<String, MergeFailureReason> failingPaths, String description) {
331 this.newHead = newHead;
332 this.mergedCommits = mergedCommits;
333 this.base = base;
334 this.mergeStatus = mergeStatus;
335 this.mergeStrategy = mergeStrategy;
336 this.description = description;
337 this.failingPaths = failingPaths;
338 if (lowLevelResults != null)
339 for (Map.Entry<String, org.eclipse.jgit.merge.MergeResult<?>> result : lowLevelResults
340 .entrySet())
341 addConflict(result.getKey(), result.getValue());
342 }
343
344 /**
345 * Creates a new result that represents a checkout conflict before the
346 * operation even started for real.
347 *
348 * @param checkoutConflicts
349 * the conflicting files
350 */
351 public MergeResult(List<String> checkoutConflicts) {
352 this.checkoutConflicts = checkoutConflicts;
353 this.mergeStatus = MergeStatus.CHECKOUT_CONFLICT;
354 }
355
356 /**
357 * @return the object the head points at after the merge
358 */
359 public ObjectId getNewHead() {
360 return newHead;
361 }
362
363 /**
364 * @return the status the merge resulted in
365 */
366 public MergeStatus getMergeStatus() {
367 return mergeStatus;
368 }
369
370 /**
371 * @return all the commits which have been merged together
372 */
373 public ObjectId[] getMergedCommits() {
374 return mergedCommits;
375 }
376
377 /**
378 * @return base the common base which was used to produce a content-merge.
379 * May be <code>null</code> if the merge-result was produced without
380 * computing a common base
381 */
382 public ObjectId getBase() {
383 return base;
384 }
385
386 @SuppressWarnings("nls")
387 @Override
388 public String toString() {
389 boolean first = true;
390 StringBuilder commits = new StringBuilder();
391 for (ObjectId commit : mergedCommits) {
392 if (!first)
393 commits.append(", ");
394 else
395 first = false;
396 commits.append(ObjectId.toString(commit));
397 }
398 return MessageFormat.format(
399 JGitText.get().mergeUsingStrategyResultedInDescription,
400 commits, ObjectId.toString(base), mergeStrategy.getName(),
401 mergeStatus, (description == null ? "" : ", " + description));
402 }
403
404 /**
405 * @param conflicts
406 * the conflicts to set
407 */
408 public void setConflicts(Map<String, int[][]> conflicts) {
409 this.conflicts = conflicts;
410 }
411
412 /**
413 * @param path
414 * @param conflictingRanges
415 * the conflicts to set
416 */
417 public void addConflict(String path, int[][] conflictingRanges) {
418 if (conflicts == null)
419 conflicts = new HashMap<>();
420 conflicts.put(path, conflictingRanges);
421 }
422
423 /**
424 * @param path
425 * @param lowLevelResult
426 */
427 public void addConflict(String path, org.eclipse.jgit.merge.MergeResult<?> lowLevelResult) {
428 if (!lowLevelResult.containsConflicts())
429 return;
430 if (conflicts == null)
431 conflicts = new HashMap<>();
432 int nrOfConflicts = 0;
433 // just counting
434 for (MergeChunk mergeChunk : lowLevelResult) {
435 if (mergeChunk.getConflictState().equals(ConflictState.FIRST_CONFLICTING_RANGE)) {
436 nrOfConflicts++;
437 }
438 }
439 int currentConflict = -1;
440 int[][] ret=new int[nrOfConflicts][mergedCommits.length+1];
441 for (MergeChunk mergeChunk : lowLevelResult) {
442 // to store the end of this chunk (end of the last conflicting range)
443 int endOfChunk = 0;
444 if (mergeChunk.getConflictState().equals(ConflictState.FIRST_CONFLICTING_RANGE)) {
445 if (currentConflict > -1) {
446 // there was a previous conflicting range for which the end
447 // is not set yet - set it!
448 ret[currentConflict][mergedCommits.length] = endOfChunk;
449 }
450 currentConflict++;
451 endOfChunk = mergeChunk.getEnd();
452 ret[currentConflict][mergeChunk.getSequenceIndex()] = mergeChunk.getBegin();
453 }
454 if (mergeChunk.getConflictState().equals(ConflictState.NEXT_CONFLICTING_RANGE)) {
455 if (mergeChunk.getEnd() > endOfChunk)
456 endOfChunk = mergeChunk.getEnd();
457 ret[currentConflict][mergeChunk.getSequenceIndex()] = mergeChunk.getBegin();
458 }
459 }
460 conflicts.put(path, ret);
461 }
462
463 /**
464 * Returns information about the conflicts which occurred during a
465 * {@link MergeCommand}. The returned value maps the path of a conflicting
466 * file to a two-dimensional int-array of line-numbers telling where in the
467 * file conflict markers for which merged commit can be found.
468 * <p>
469 * If the returned value contains a mapping "path"->[x][y]=z then this
470 * means
471 * <ul>
472 * <li>the file with path "path" contains conflicts</li>
473 * <li>if y < "number of merged commits": for conflict number x in this
474 * file the chunk which was copied from commit number y starts on line
475 * number z. All numberings and line numbers start with 0.</li>
476 * <li>if y == "number of merged commits": the first non-conflicting line
477 * after conflict number x starts at line number z</li>
478 * </ul>
479 * <p>
480 * Example code how to parse this data:
481 *
482 * <pre>
483 * MergeResult m=...;
484 * Map<String, int[][]> allConflicts = m.getConflicts();
485 * for (String path : allConflicts.keySet()) {
486 * int[][] c = allConflicts.get(path);
487 * System.out.println("Conflicts in file " + path);
488 * for (int i = 0; i < c.length; ++i) {
489 * System.out.println(" Conflict #" + i);
490 * for (int j = 0; j < (c[i].length) - 1; ++j) {
491 * if (c[i][j] >= 0)
492 * System.out.println(" Chunk for "
493 * + m.getMergedCommits()[j] + " starts on line #"
494 * + c[i][j]);
495 * }
496 * }
497 * }
498 * </pre>
499 *
500 * @return the conflicts or <code>null</code> if no conflict occurred
501 */
502 public Map<String, int[][]> getConflicts() {
503 return conflicts;
504 }
505
506 /**
507 * Returns a list of paths causing this merge to fail as returned by
508 * {@link ResolveMerger#getFailingPaths()}
509 *
510 * @return the list of paths causing this merge to fail or <code>null</code>
511 * if no failure occurred
512 */
513 public Map<String, MergeFailureReason> getFailingPaths() {
514 return failingPaths;
515 }
516
517 /**
518 * Returns a list of paths that cause a checkout conflict. These paths
519 * prevent the operation from even starting.
520 *
521 * @return the list of files that caused the checkout conflict.
522 */
523 public List<String> getCheckoutConflicts() {
524 return checkoutConflicts;
525 }
526 }