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.MergeResult;
53 import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
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.getRef(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 = db.getRef(Constants.HEAD);
124 Git git = new Git(db);
125 MergeCommand mergeCmd = git.merge().setStrategy(mergeStrategy)
126 .setSquash(squash).setFastForward(ff).setCommit(!noCommit);
127 if (srcRef != null)
128 mergeCmd.include(srcRef);
129 else
130 mergeCmd.include(src);
131
132 if (message != null)
133 mergeCmd.setMessage(message);
134
135 MergeResult result;
136 try {
137 result = mergeCmd.call();
138 } catch (CheckoutConflictException e) {
139 result = new MergeResult(e.getConflictingPaths());
140 }
141
142 switch (result.getMergeStatus()) {
143 case ALREADY_UP_TO_DATE:
144 if (squash)
145 outw.print(CLIText.get().nothingToSquash);
146 outw.println(CLIText.get().alreadyUpToDate);
147 break;
148 case FAST_FORWARD:
149 ObjectId oldHeadId = oldHead.getObjectId();
150 outw.println(MessageFormat.format(CLIText.get().updating, oldHeadId
151 .abbreviate(7).name(), result.getNewHead().abbreviate(7)
152 .name()));
153 outw.println(result.getMergeStatus().toString());
154 break;
155 case CHECKOUT_CONFLICT:
156 outw.println(CLIText.get().mergeCheckoutConflict);
157 for (String collidingPath : result.getCheckoutConflicts())
158 outw.println("\t" + collidingPath);
159 outw.println(CLIText.get().mergeCheckoutFailed);
160 break;
161 case CONFLICTING:
162 for (String collidingPath : result.getConflicts().keySet())
163 outw.println(MessageFormat.format(CLIText.get().mergeConflict,
164 collidingPath));
165 outw.println(CLIText.get().mergeFailed);
166 break;
167 case FAILED:
168 for (Map.Entry<String, MergeFailureReason> entry : result
169 .getFailingPaths().entrySet())
170 switch (entry.getValue()) {
171 case DIRTY_WORKTREE:
172 case DIRTY_INDEX:
173 outw.println(CLIText.get().dontOverwriteLocalChanges);
174 outw.println(" " + entry.getKey());
175 break;
176 case COULD_NOT_DELETE:
177 outw.println(CLIText.get().cannotDeleteFile);
178 outw.println(" " + entry.getKey());
179 break;
180 }
181 break;
182 case MERGED:
183 String name;
184 if (!isMergedInto(oldHead, src))
185 name = mergeStrategy.getName();
186 else
187 name = "recursive";
188 outw.println(MessageFormat.format(CLIText.get().mergeMadeBy, name));
189 break;
190 case MERGED_NOT_COMMITTED:
191 outw.println(CLIText.get().mergeWentWellStoppedBeforeCommitting);
192 break;
193 case MERGED_SQUASHED:
194 case FAST_FORWARD_SQUASHED:
195 case MERGED_SQUASHED_NOT_COMMITTED:
196 outw.println(CLIText.get().mergedSquashed);
197 outw.println(CLIText.get().mergeWentWellStoppedBeforeCommitting);
198 break;
199 case ABORTED:
200 throw die(CLIText.get().ffNotPossibleAborting);
201 case NOT_SUPPORTED:
202 outw.println(MessageFormat.format(
203 CLIText.get().unsupportedOperation, result.toString()));
204 }
205 }
206
207 private boolean isMergedInto(Ref oldHead, AnyObjectId src)
208 throws IOException {
209 RevWalk revWalk = new RevWalk(db);
210 ObjectId oldHeadObjectId = oldHead.getPeeledObjectId();
211 if (oldHeadObjectId == null)
212 oldHeadObjectId = oldHead.getObjectId();
213 RevCommit oldHeadCommit = revWalk.lookupCommit(oldHeadObjectId);
214 RevCommit srcCommit = revWalk.lookupCommit(src);
215 return revWalk.isMergedInto(oldHeadCommit, srcCommit);
216 }
217 }