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