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 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 public String toString() {
216 return "Checkout Conflict"; //$NON-NLS-1$
217 }
218
219 @Override
220 public boolean isSuccessful() {
221 return false;
222 }
223 };
224
225 /**
226 * @return whether the status indicates a successful result
227 */
228 public abstract boolean isSuccessful();
229 }
230
231 private ObjectId[] mergedCommits;
232
233 private ObjectId base;
234
235 private ObjectId newHead;
236
237 private Map<String, int[][]> conflicts;
238
239 private MergeStatus mergeStatus;
240
241 private String description;
242
243 private MergeStrategy mergeStrategy;
244
245 private Map<String, MergeFailureReason> failingPaths;
246
247 private List<String> checkoutConflicts;
248
249 /**
250 * @param newHead
251 * the object the head points at after the merge
252 * @param base
253 * the common base which was used to produce a content-merge. May
254 * be <code>null</code> if the merge-result was produced without
255 * computing a common base
256 * @param mergedCommits
257 * all the commits which have been merged together
258 * @param mergeStatus
259 * the status the merge resulted in
260 * @param mergeStrategy
261 * the used {@link MergeStrategy}
262 * @param lowLevelResults
263 * merge results as returned by
264 * {@link ResolveMerger#getMergeResults()}
265 * @since 2.0
266 */
267 public MergeResult(ObjectId newHead, ObjectId base,
268 ObjectId[] mergedCommits, MergeStatus mergeStatus,
269 MergeStrategy mergeStrategy,
270 Map<String, org.eclipse.jgit.merge.MergeResult<?>> lowLevelResults) {
271 this(newHead, base, mergedCommits, mergeStatus, mergeStrategy,
272 lowLevelResults, null);
273 }
274
275 /**
276 * @param newHead
277 * the object the head points at after the merge
278 * @param base
279 * the common base which was used to produce a content-merge. May
280 * be <code>null</code> if the merge-result was produced without
281 * computing a common base
282 * @param mergedCommits
283 * all the commits which have been merged together
284 * @param mergeStatus
285 * the status the merge resulted in
286 * @param mergeStrategy
287 * the used {@link MergeStrategy}
288 * @param lowLevelResults
289 * merge results as returned by {@link ResolveMerger#getMergeResults()}
290 * @param description
291 * a user friendly description of the merge result
292 */
293 public MergeResult(ObjectId newHead, ObjectId base,
294 ObjectId[] mergedCommits, MergeStatus mergeStatus,
295 MergeStrategy mergeStrategy,
296 Map<String, org.eclipse.jgit.merge.MergeResult<?>> lowLevelResults,
297 String description) {
298 this(newHead, base, mergedCommits, mergeStatus, mergeStrategy,
299 lowLevelResults, null, description);
300 }
301
302 /**
303 * @param newHead
304 * the object the head points at after the merge
305 * @param base
306 * the common base which was used to produce a content-merge. May
307 * be <code>null</code> if the merge-result was produced without
308 * computing a common base
309 * @param mergedCommits
310 * all the commits which have been merged together
311 * @param mergeStatus
312 * the status the merge resulted in
313 * @param mergeStrategy
314 * the used {@link MergeStrategy}
315 * @param lowLevelResults
316 * merge results as returned by
317 * {@link ResolveMerger#getMergeResults()}
318 * @param failingPaths
319 * list of paths causing this merge to fail as returned by
320 * {@link ResolveMerger#getFailingPaths()}
321 * @param description
322 * a user friendly description of the merge result
323 */
324 public MergeResult(ObjectId newHead, ObjectId base,
325 ObjectId[] mergedCommits, MergeStatus mergeStatus,
326 MergeStrategy mergeStrategy,
327 Map<String, org.eclipse.jgit.merge.MergeResult<?>> lowLevelResults,
328 Map<String, MergeFailureReason> failingPaths, String description) {
329 this.newHead = newHead;
330 this.mergedCommits = mergedCommits;
331 this.base = base;
332 this.mergeStatus = mergeStatus;
333 this.mergeStrategy = mergeStrategy;
334 this.description = description;
335 this.failingPaths = failingPaths;
336 if (lowLevelResults != null)
337 for (Map.Entry<String, org.eclipse.jgit.merge.MergeResult<?>> result : lowLevelResults
338 .entrySet())
339 addConflict(result.getKey(), result.getValue());
340 }
341
342 /**
343 * Creates a new result that represents a checkout conflict before the
344 * operation even started for real.
345 *
346 * @param checkoutConflicts
347 * the conflicting files
348 */
349 public MergeResult(List<String> checkoutConflicts) {
350 this.checkoutConflicts = checkoutConflicts;
351 this.mergeStatus = MergeStatus.CHECKOUT_CONFLICT;
352 }
353
354 /**
355 * @return the object the head points at after the merge
356 */
357 public ObjectId getNewHead() {
358 return newHead;
359 }
360
361 /**
362 * @return the status the merge resulted in
363 */
364 public MergeStatus getMergeStatus() {
365 return mergeStatus;
366 }
367
368 /**
369 * @return all the commits which have been merged together
370 */
371 public ObjectId[] getMergedCommits() {
372 return mergedCommits;
373 }
374
375 /**
376 * @return base the common base which was used to produce a content-merge.
377 * May be <code>null</code> if the merge-result was produced without
378 * computing a common base
379 */
380 public ObjectId getBase() {
381 return base;
382 }
383
384 @SuppressWarnings("nls")
385 @Override
386 public String toString() {
387 boolean first = true;
388 StringBuilder commits = new StringBuilder();
389 for (ObjectId commit : mergedCommits) {
390 if (!first)
391 commits.append(", ");
392 else
393 first = false;
394 commits.append(ObjectId.toString(commit));
395 }
396 return MessageFormat.format(
397 JGitText.get().mergeUsingStrategyResultedInDescription,
398 commits, ObjectId.toString(base), mergeStrategy.getName(),
399 mergeStatus, (description == null ? "" : ", " + description));
400 }
401
402 /**
403 * @param conflicts
404 * the conflicts to set
405 */
406 public void setConflicts(Map<String, int[][]> conflicts) {
407 this.conflicts = conflicts;
408 }
409
410 /**
411 * @param path
412 * @param conflictingRanges
413 * the conflicts to set
414 */
415 public void addConflict(String path, int[][] conflictingRanges) {
416 if (conflicts == null)
417 conflicts = new HashMap<String, int[][]>();
418 conflicts.put(path, conflictingRanges);
419 }
420
421 /**
422 * @param path
423 * @param lowLevelResult
424 */
425 public void addConflict(String path, org.eclipse.jgit.merge.MergeResult<?> lowLevelResult) {
426 if (!lowLevelResult.containsConflicts())
427 return;
428 if (conflicts == null)
429 conflicts = new HashMap<String, int[][]>();
430 int nrOfConflicts = 0;
431 // just counting
432 for (MergeChunk mergeChunk : lowLevelResult) {
433 if (mergeChunk.getConflictState().equals(ConflictState.FIRST_CONFLICTING_RANGE)) {
434 nrOfConflicts++;
435 }
436 }
437 int currentConflict = -1;
438 int[][] ret=new int[nrOfConflicts][mergedCommits.length+1];
439 for (MergeChunk mergeChunk : lowLevelResult) {
440 // to store the end of this chunk (end of the last conflicting range)
441 int endOfChunk = 0;
442 if (mergeChunk.getConflictState().equals(ConflictState.FIRST_CONFLICTING_RANGE)) {
443 if (currentConflict > -1) {
444 // there was a previous conflicting range for which the end
445 // is not set yet - set it!
446 ret[currentConflict][mergedCommits.length] = endOfChunk;
447 }
448 currentConflict++;
449 endOfChunk = mergeChunk.getEnd();
450 ret[currentConflict][mergeChunk.getSequenceIndex()] = mergeChunk.getBegin();
451 }
452 if (mergeChunk.getConflictState().equals(ConflictState.NEXT_CONFLICTING_RANGE)) {
453 if (mergeChunk.getEnd() > endOfChunk)
454 endOfChunk = mergeChunk.getEnd();
455 ret[currentConflict][mergeChunk.getSequenceIndex()] = mergeChunk.getBegin();
456 }
457 }
458 conflicts.put(path, ret);
459 }
460
461 /**
462 * Returns information about the conflicts which occurred during a
463 * {@link MergeCommand}. The returned value maps the path of a conflicting
464 * file to a two-dimensional int-array of line-numbers telling where in the
465 * file conflict markers for which merged commit can be found.
466 * <p>
467 * If the returned value contains a mapping "path"->[x][y]=z then this
468 * means
469 * <ul>
470 * <li>the file with path "path" contains conflicts</li>
471 * <li>if y < "number of merged commits": for conflict number x in this
472 * file the chunk which was copied from commit number y starts on line
473 * number z. All numberings and line numbers start with 0.</li>
474 * <li>if y == "number of merged commits": the first non-conflicting line
475 * after conflict number x starts at line number z</li>
476 * </ul>
477 * <p>
478 * Example code how to parse this data:
479 *
480 * <pre>
481 * MergeResult m=...;
482 * Map<String, int[][]> allConflicts = m.getConflicts();
483 * for (String path : allConflicts.keySet()) {
484 * int[][] c = allConflicts.get(path);
485 * System.out.println("Conflicts in file " + path);
486 * for (int i = 0; i < c.length; ++i) {
487 * System.out.println(" Conflict #" + i);
488 * for (int j = 0; j < (c[i].length) - 1; ++j) {
489 * if (c[i][j] >= 0)
490 * System.out.println(" Chunk for "
491 * + m.getMergedCommits()[j] + " starts on line #"
492 * + c[i][j]);
493 * }
494 * }
495 * }
496 * </pre>
497 *
498 * @return the conflicts or <code>null</code> if no conflict occurred
499 */
500 public Map<String, int[][]> getConflicts() {
501 return conflicts;
502 }
503
504 /**
505 * Returns a list of paths causing this merge to fail as returned by
506 * {@link ResolveMerger#getFailingPaths()}
507 *
508 * @return the list of paths causing this merge to fail or <code>null</code>
509 * if no failure occurred
510 */
511 public Map<String, MergeFailureReason> getFailingPaths() {
512 return failingPaths;
513 }
514
515 /**
516 * Returns a list of paths that cause a checkout conflict. These paths
517 * prevent the operation from even starting.
518 *
519 * @return the list of files that caused the checkout conflict.
520 */
521 public List<String> getCheckoutConflicts() {
522 return checkoutConflicts;
523 }
524 }