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