View Javadoc
1   /*
2    * Copyright (C) 2011, 2014 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.pgm;
12  
13  import java.io.IOException;
14  import java.text.MessageFormat;
15  import java.util.Map;
16  
17  import org.eclipse.jgit.api.Git;
18  import org.eclipse.jgit.api.MergeCommand;
19  import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
20  import org.eclipse.jgit.api.MergeResult;
21  import org.eclipse.jgit.api.errors.CheckoutConflictException;
22  import org.eclipse.jgit.api.errors.GitAPIException;
23  import org.eclipse.jgit.lib.AnyObjectId;
24  import org.eclipse.jgit.lib.Constants;
25  import org.eclipse.jgit.lib.ObjectId;
26  import org.eclipse.jgit.lib.Ref;
27  import org.eclipse.jgit.merge.MergeStrategy;
28  import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
29  import org.eclipse.jgit.pgm.internal.CLIText;
30  import org.eclipse.jgit.revwalk.RevCommit;
31  import org.eclipse.jgit.revwalk.RevWalk;
32  import org.kohsuke.args4j.Argument;
33  import org.kohsuke.args4j.Option;
34  
35  @Command(common = true, usage = "usage_MergesTwoDevelopmentHistories")
36  class Merge extends TextBuiltin {
37  
38  	@Option(name = "--strategy", aliases = { "-s" }, usage = "usage_mergeStrategy")
39  	private String strategyName;
40  
41  	@Option(name = "--squash", usage = "usage_squash")
42  	private boolean squash;
43  
44  	@Option(name = "--no-commit", usage = "usage_noCommit")
45  	private boolean noCommit = false;
46  
47  	private MergeStrategy mergeStrategy = MergeStrategy.RECURSIVE;
48  
49  	@Argument(required = true, metaVar = "metaVar_ref", usage = "usage_mergeRef")
50  	private String ref;
51  
52  	private FastForwardMode ff = FastForwardMode.FF;
53  
54  	@Option(name = "--ff", usage = "usage_mergeFf")
55  	void ff(@SuppressWarnings("unused") final boolean ignored) {
56  		ff = FastForwardMode.FF;
57  	}
58  
59  	@Option(name = "--no-ff", usage = "usage_mergeNoFf")
60  	void noff(@SuppressWarnings("unused") final boolean ignored) {
61  		ff = FastForwardMode.NO_FF;
62  	}
63  
64  	@Option(name = "--ff-only", usage = "usage_mergeFfOnly")
65  	void ffonly(@SuppressWarnings("unused") final boolean ignored) {
66  		ff = FastForwardMode.FF_ONLY;
67  	}
68  
69  	@Option(name = "-m", usage = "usage_message")
70  	private String message;
71  
72  	/** {@inheritDoc} */
73  	@Override
74  	protected void run() {
75  		if (squash && ff == FastForwardMode.NO_FF) {
76  			throw die(CLIText.get().cannotCombineSquashWithNoff);
77  		}
78  		// determine the merge strategy
79  		if (strategyName != null) {
80  			mergeStrategy = MergeStrategy.get(strategyName);
81  			if (mergeStrategy == null) {
82  				throw die(MessageFormat.format(
83  						CLIText.get().unknownMergeStrategy, strategyName));
84  			}
85  		}
86  
87  		try {
88  			// determine the other revision we want to merge with HEAD
89  			final Ref srcRef = db.findRef(ref);
90  			final ObjectId src = db.resolve(ref + "^{commit}"); //$NON-NLS-1$
91  			if (src == null) {
92  				throw die(MessageFormat
93  						.format(CLIText.get().refDoesNotExistOrNoCommit, ref));
94  			}
95  
96  			Ref oldHead = getOldHead();
97  			MergeResult result;
98  			try (Gitit.html#Git">Git git = new Git(db)) {
99  				MergeCommand mergeCmd = git.merge().setStrategy(mergeStrategy)
100 						.setSquash(squash).setFastForward(ff)
101 						.setCommit(!noCommit);
102 				if (srcRef != null) {
103 					mergeCmd.include(srcRef);
104 				} else {
105 					mergeCmd.include(src);
106 				}
107 
108 				if (message != null) {
109 					mergeCmd.setMessage(message);
110 				}
111 
112 				try {
113 					result = mergeCmd.call();
114 				} catch (CheckoutConflictException e) {
115 					result = new MergeResult(e.getConflictingPaths()); // CHECKOUT_CONFLICT
116 				}
117 			}
118 
119 			switch (result.getMergeStatus()) {
120 			case ALREADY_UP_TO_DATE:
121 				if (squash) {
122 					outw.print(CLIText.get().nothingToSquash);
123 				}
124 				outw.println(CLIText.get().alreadyUpToDate);
125 				break;
126 			case FAST_FORWARD:
127 				ObjectId oldHeadId = oldHead.getObjectId();
128 				if (oldHeadId != null) {
129 					String oldId = oldHeadId.abbreviate(7).name();
130 					String newId = result.getNewHead().abbreviate(7).name();
131 					outw.println(MessageFormat.format(CLIText.get().updating,
132 							oldId, newId));
133 				}
134 				outw.println(result.getMergeStatus().toString());
135 				break;
136 			case CHECKOUT_CONFLICT:
137 				outw.println(CLIText.get().mergeCheckoutConflict);
138 				for (String collidingPath : result.getCheckoutConflicts()) {
139 					outw.println("\t" + collidingPath); //$NON-NLS-1$
140 				}
141 				outw.println(CLIText.get().mergeCheckoutFailed);
142 				break;
143 			case CONFLICTING:
144 				for (String collidingPath : result.getConflicts().keySet())
145 					outw.println(MessageFormat.format(
146 							CLIText.get().mergeConflict, collidingPath));
147 				outw.println(CLIText.get().mergeFailed);
148 				break;
149 			case FAILED:
150 				for (Map.Entry<String, MergeFailureReason> entry : result
151 						.getFailingPaths().entrySet())
152 					switch (entry.getValue()) {
153 					case DIRTY_WORKTREE:
154 					case DIRTY_INDEX:
155 						outw.println(CLIText.get().dontOverwriteLocalChanges);
156 						outw.println("        " + entry.getKey()); //$NON-NLS-1$
157 						break;
158 					case COULD_NOT_DELETE:
159 						outw.println(CLIText.get().cannotDeleteFile);
160 						outw.println("        " + entry.getKey()); //$NON-NLS-1$
161 						break;
162 					}
163 				break;
164 			case MERGED:
165 				MergeStrategy strategy = isMergedInto(oldHead, src)
166 						? MergeStrategy.RECURSIVE
167 						: mergeStrategy;
168 				outw.println(MessageFormat.format(CLIText.get().mergeMadeBy,
169 						strategy.getName()));
170 				break;
171 			case MERGED_NOT_COMMITTED:
172 				outw.println(
173 						CLIText.get().mergeWentWellStoppedBeforeCommitting);
174 				break;
175 			case MERGED_SQUASHED:
176 			case FAST_FORWARD_SQUASHED:
177 			case MERGED_SQUASHED_NOT_COMMITTED:
178 				outw.println(CLIText.get().mergedSquashed);
179 				outw.println(
180 						CLIText.get().mergeWentWellStoppedBeforeCommitting);
181 				break;
182 			case ABORTED:
183 				throw die(CLIText.get().ffNotPossibleAborting);
184 			case NOT_SUPPORTED:
185 				outw.println(MessageFormat.format(
186 						CLIText.get().unsupportedOperation, result.toString()));
187 			}
188 		} catch (GitAPIException | IOException e) {
189 			throw die(e.getMessage(), e);
190 		}
191 
192 	}
193 
194 	private Ref getOldHead() throws IOException {
195 		Ref oldHead = db.exactRef(Constants.HEAD);
196 		if (oldHead == null) {
197 			throw die(CLIText.get().onBranchToBeBorn);
198 		}
199 		return oldHead;
200 	}
201 
202 	private boolean isMergedInto(Ref oldHead, AnyObjectId src)
203 			throws IOException {
204 		try (RevWalklk.html#RevWalk">RevWalk revWalk = new RevWalk(db)) {
205 			ObjectId oldHeadObjectId = oldHead.getPeeledObjectId();
206 			if (oldHeadObjectId == null)
207 				oldHeadObjectId = oldHead.getObjectId();
208 			RevCommit oldHeadCommit = revWalk.lookupCommit(oldHeadObjectId);
209 			RevCommit srcCommit = revWalk.lookupCommit(src);
210 			return revWalk.isMergedInto(oldHeadCommit, srcCommit);
211 		}
212 	}
213 }