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
105 @Override
106 protected void run() throws Exception {
107 if (squash && ff == FastForwardMode.NO_FF)
108 throw die(CLIText.get().cannotCombineSquashWithNoff);
109
110 if (strategyName != null) {
111 mergeStrategy = MergeStrategy.get(strategyName);
112 if (mergeStrategy == null)
113 throw die(MessageFormat.format(
114 CLIText.get().unknownMergeStrategy, strategyName));
115 }
116
117
118 final Ref srcRef = db.findRef(ref);
119 final ObjectId src = db.resolve(ref + "^{commit}");
120 if (src == null)
121 throw die(MessageFormat.format(
122 CLIText.get().refDoesNotExistOrNoCommit, ref));
123
124 Ref oldHead = getOldHead();
125 MergeResult result;
126 try (Git git = new Git(db)) {
127 MergeCommand mergeCmd = git.merge().setStrategy(mergeStrategy)
128 .setSquash(squash).setFastForward(ff).setCommit(!noCommit);
129 if (srcRef != null)
130 mergeCmd.include(srcRef);
131 else
132 mergeCmd.include(src);
133
134 if (message != null)
135 mergeCmd.setMessage(message);
136
137 try {
138 result = mergeCmd.call();
139 } catch (CheckoutConflictException e) {
140 result = new MergeResult(e.getConflictingPaths());
141 }
142 }
143
144 switch (result.getMergeStatus()) {
145 case ALREADY_UP_TO_DATE:
146 if (squash)
147 outw.print(CLIText.get().nothingToSquash);
148 outw.println(CLIText.get().alreadyUpToDate);
149 break;
150 case FAST_FORWARD:
151 ObjectId oldHeadId = oldHead.getObjectId();
152 if (oldHeadId != null) {
153 String oldId = oldHeadId.abbreviate(7).name();
154 String newId = result.getNewHead().abbreviate(7).name();
155 outw.println(MessageFormat.format(CLIText.get().updating, oldId,
156 newId));
157 }
158 outw.println(result.getMergeStatus().toString());
159 break;
160 case CHECKOUT_CONFLICT:
161 outw.println(CLIText.get().mergeCheckoutConflict);
162 for (String collidingPath : result.getCheckoutConflicts())
163 outw.println("\t" + collidingPath);
164 outw.println(CLIText.get().mergeCheckoutFailed);
165 break;
166 case CONFLICTING:
167 for (String collidingPath : result.getConflicts().keySet())
168 outw.println(MessageFormat.format(CLIText.get().mergeConflict,
169 collidingPath));
170 outw.println(CLIText.get().mergeFailed);
171 break;
172 case FAILED:
173 for (Map.Entry<String, MergeFailureReason> entry : result
174 .getFailingPaths().entrySet())
175 switch (entry.getValue()) {
176 case DIRTY_WORKTREE:
177 case DIRTY_INDEX:
178 outw.println(CLIText.get().dontOverwriteLocalChanges);
179 outw.println(" " + entry.getKey());
180 break;
181 case COULD_NOT_DELETE:
182 outw.println(CLIText.get().cannotDeleteFile);
183 outw.println(" " + entry.getKey());
184 break;
185 }
186 break;
187 case MERGED:
188 String name;
189 if (!isMergedInto(oldHead, src))
190 name = mergeStrategy.getName();
191 else
192 name = "recursive";
193 outw.println(MessageFormat.format(CLIText.get().mergeMadeBy, name));
194 break;
195 case MERGED_NOT_COMMITTED:
196 outw.println(CLIText.get().mergeWentWellStoppedBeforeCommitting);
197 break;
198 case MERGED_SQUASHED:
199 case FAST_FORWARD_SQUASHED:
200 case MERGED_SQUASHED_NOT_COMMITTED:
201 outw.println(CLIText.get().mergedSquashed);
202 outw.println(CLIText.get().mergeWentWellStoppedBeforeCommitting);
203 break;
204 case ABORTED:
205 throw die(CLIText.get().ffNotPossibleAborting);
206 case NOT_SUPPORTED:
207 outw.println(MessageFormat.format(
208 CLIText.get().unsupportedOperation, result.toString()));
209 }
210 }
211
212 private Ref getOldHead() throws IOException {
213 Ref oldHead = db.exactRef(Constants.HEAD);
214 if (oldHead == null) {
215 throw die(CLIText.get().onBranchToBeBorn);
216 }
217 return oldHead;
218 }
219
220 private boolean isMergedInto(Ref oldHead, AnyObjectId src)
221 throws IOException {
222 try (RevWalk revWalk = new RevWalk(db)) {
223 ObjectId oldHeadObjectId = oldHead.getPeeledObjectId();
224 if (oldHeadObjectId == null)
225 oldHeadObjectId = oldHead.getObjectId();
226 RevCommit oldHeadCommit = revWalk.lookupCommit(oldHeadObjectId);
227 RevCommit srcCommit = revWalk.lookupCommit(src);
228 return revWalk.isMergedInto(oldHeadCommit, srcCommit);
229 }
230 }
231 }