1
2
3
4
5
6
7
8
9
10 package org.eclipse.jgit.api;
11
12 import java.io.IOException;
13 import java.text.MessageFormat;
14 import java.util.Collection;
15 import java.util.LinkedList;
16
17 import org.eclipse.jgit.api.errors.CheckoutConflictException;
18 import org.eclipse.jgit.api.errors.GitAPIException;
19 import org.eclipse.jgit.api.errors.JGitInternalException;
20 import org.eclipse.jgit.dircache.DirCache;
21 import org.eclipse.jgit.dircache.DirCacheBuildIterator;
22 import org.eclipse.jgit.dircache.DirCacheBuilder;
23 import org.eclipse.jgit.dircache.DirCacheCheckout;
24 import org.eclipse.jgit.dircache.DirCacheEntry;
25 import org.eclipse.jgit.dircache.DirCacheIterator;
26 import org.eclipse.jgit.internal.JGitText;
27 import org.eclipse.jgit.lib.Constants;
28 import org.eclipse.jgit.lib.NullProgressMonitor;
29 import org.eclipse.jgit.lib.ObjectId;
30 import org.eclipse.jgit.lib.ProgressMonitor;
31 import org.eclipse.jgit.lib.Ref;
32 import org.eclipse.jgit.lib.RefUpdate;
33 import org.eclipse.jgit.lib.Repository;
34 import org.eclipse.jgit.lib.RepositoryState;
35 import org.eclipse.jgit.revwalk.RevCommit;
36 import org.eclipse.jgit.revwalk.RevWalk;
37 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
38 import org.eclipse.jgit.treewalk.CanonicalTreeParser;
39 import org.eclipse.jgit.treewalk.EmptyTreeIterator;
40 import org.eclipse.jgit.treewalk.TreeWalk;
41 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
42
43
44
45
46
47
48
49
50
51
52 public class ResetCommand extends GitCommand<Ref> {
53
54
55
56
57 public enum ResetType {
58
59
60
61 SOFT,
62
63
64
65
66 MIXED,
67
68
69
70
71 HARD,
72
73
74
75
76
77
78 MERGE,
79
80
81
82
83
84 KEEP
85 }
86
87
88
89 private String ref = null;
90
91 private ResetType mode;
92
93 private Collection<String> filepaths = new LinkedList<>();
94
95 private boolean isReflogDisabled;
96
97 private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
98
99
100
101
102
103
104
105
106
107 public ResetCommand(Repository repo) {
108 super(repo);
109 }
110
111
112
113
114
115
116
117
118 @Override
119 public Ref call() throws GitAPIException, CheckoutConflictException {
120 checkCallable();
121
122 try {
123 RepositoryState state = repo.getRepositoryState();
124 final boolean merging = state.equals(RepositoryState.MERGING)
125 || state.equals(RepositoryState.MERGING_RESOLVED);
126 final boolean cherryPicking = state
127 .equals(RepositoryState.CHERRY_PICKING)
128 || state.equals(RepositoryState.CHERRY_PICKING_RESOLVED);
129 final boolean reverting = state.equals(RepositoryState.REVERTING)
130 || state.equals(RepositoryState.REVERTING_RESOLVED);
131
132 final ObjectId commitId = resolveRefToCommitId();
133
134 if (ref != null && commitId == null) {
135
136
137 throw new JGitInternalException(MessageFormat
138 .format(JGitText.get().invalidRefName, ref));
139 }
140
141 final ObjectId commitTree;
142 if (commitId != null)
143 commitTree = parseCommit(commitId).getTree();
144 else
145 commitTree = null;
146
147 if (!filepaths.isEmpty()) {
148
149 resetIndexForPaths(commitTree);
150 setCallable(false);
151 return repo.exactRef(Constants.HEAD);
152 }
153
154 final Ref result;
155 if (commitId != null) {
156
157 final RefUpdate ru = repo.updateRef(Constants.HEAD);
158 ru.setNewObjectId(commitId);
159
160 String refName = Repository.shortenRefName(getRefOrHEAD());
161 if (isReflogDisabled) {
162 ru.disableRefLog();
163 } else {
164 String message = refName + ": updating " + Constants.HEAD;
165 ru.setRefLogMessage(message, false);
166 }
167 if (ru.forceUpdate() == RefUpdate.Result.LOCK_FAILURE)
168 throw new JGitInternalException(MessageFormat.format(
169 JGitText.get().cannotLock, ru.getName()));
170
171 ObjectId origHead = ru.getOldObjectId();
172 if (origHead != null)
173 repo.writeOrigHead(origHead);
174 }
175 result = repo.exactRef(Constants.HEAD);
176
177 if (mode == null)
178 mode = ResetType.MIXED;
179
180 switch (mode) {
181 case HARD:
182 checkoutIndex(commitTree);
183 break;
184 case MIXED:
185 resetIndex(commitTree);
186 break;
187 case SOFT:
188 break;
189 case KEEP:
190 case MERGE:
191 throw new UnsupportedOperationException();
192
193 }
194
195 if (mode != ResetType.SOFT) {
196 if (merging)
197 resetMerge();
198 else if (cherryPicking)
199 resetCherryPick();
200 else if (reverting)
201 resetRevert();
202 else if (repo.readSquashCommitMsg() != null)
203 repo.writeSquashCommitMsg(null );
204 }
205
206 setCallable(false);
207 return result;
208 } catch (IOException e) {
209 throw new JGitInternalException(MessageFormat.format(
210 JGitText.get().exceptionCaughtDuringExecutionOfResetCommand,
211 e.getMessage()), e);
212 }
213 }
214
215 private RevCommit parseCommit(ObjectId commitId) {
216 try (RevWalk rw = new RevWalk(repo)) {
217 return rw.parseCommit(commitId);
218 } catch (IOException e) {
219 throw new JGitInternalException(MessageFormat.format(
220 JGitText.get().cannotReadCommit, commitId.toString()), e);
221 }
222 }
223
224 private ObjectId resolveRefToCommitId() {
225 try {
226 return repo.resolve(getRefOrHEAD() + "^{commit}");
227 } catch (IOException e) {
228 throw new JGitInternalException(
229 MessageFormat.format(JGitText.get().cannotRead, getRefOrHEAD()),
230 e);
231 }
232 }
233
234
235
236
237
238
239
240
241 public ResetCommand setRef(String ref) {
242 this.ref = ref;
243 return this;
244 }
245
246
247
248
249
250
251
252
253 public ResetCommand setMode(ResetType mode) {
254 if (!filepaths.isEmpty())
255 throw new JGitInternalException(MessageFormat.format(
256 JGitText.get().illegalCombinationOfArguments,
257 "[--mixed | --soft | --hard]", "<paths>..."));
258 this.mode = mode;
259 return this;
260 }
261
262
263
264
265
266
267
268
269
270 public ResetCommand addPath(String path) {
271 if (mode != null)
272 throw new JGitInternalException(MessageFormat.format(
273 JGitText.get().illegalCombinationOfArguments, "<paths>...",
274 "[--mixed | --soft | --hard]"));
275 filepaths.add(path);
276 return this;
277 }
278
279
280
281
282
283
284
285
286
287
288 public ResetCommand disableRefLog(boolean disable) {
289 this.isReflogDisabled = disable;
290 return this;
291 }
292
293
294
295
296
297
298
299 public boolean isReflogDisabled() {
300 return this.isReflogDisabled;
301 }
302
303 private String getRefOrHEAD() {
304 if (ref != null) {
305 return ref;
306 }
307 return Constants.HEAD;
308 }
309
310
311
312
313
314
315
316
317
318
319
320 public ResetCommand setProgressMonitor(ProgressMonitor monitor) {
321 if (monitor == null) {
322 monitor = NullProgressMonitor.INSTANCE;
323 }
324 this.monitor = monitor;
325 return this;
326 }
327
328 private void resetIndexForPaths(ObjectId commitTree) {
329 DirCache dc = null;
330 try (TreeWalk tw = new TreeWalk(repo)) {
331 dc = repo.lockDirCache();
332 DirCacheBuilder builder = dc.builder();
333
334 tw.addTree(new DirCacheBuildIterator(builder));
335 if (commitTree != null)
336 tw.addTree(commitTree);
337 else
338 tw.addTree(new EmptyTreeIterator());
339 tw.setFilter(PathFilterGroup.createFromStrings(filepaths));
340 tw.setRecursive(true);
341
342 while (tw.next()) {
343 final CanonicalTreeParser tree = tw.getTree(1,
344 CanonicalTreeParser.class);
345
346 if (tree != null) {
347
348 DirCacheEntry entry = new DirCacheEntry(tw.getRawPath());
349 entry.setFileMode(tree.getEntryFileMode());
350 entry.setObjectId(tree.getEntryObjectId());
351 builder.add(entry);
352 }
353 }
354
355 builder.commit();
356 } catch (IOException e) {
357 throw new RuntimeException(e);
358 } finally {
359 if (dc != null)
360 dc.unlock();
361 }
362 }
363
364 private void resetIndex(ObjectId commitTree) throws IOException {
365 DirCache dc = repo.lockDirCache();
366 try (TreeWalk walk = new TreeWalk(repo)) {
367 DirCacheBuilder builder = dc.builder();
368
369 if (commitTree != null)
370 walk.addTree(commitTree);
371 else
372 walk.addTree(new EmptyTreeIterator());
373 walk.addTree(new DirCacheIterator(dc));
374 walk.setRecursive(true);
375
376 while (walk.next()) {
377 AbstractTreeIterator cIter = walk.getTree(0,
378 AbstractTreeIterator.class);
379 if (cIter == null) {
380
381 continue;
382 }
383
384 final DirCacheEntry entry = new DirCacheEntry(walk.getRawPath());
385 entry.setFileMode(cIter.getEntryFileMode());
386 entry.setObjectIdFromRaw(cIter.idBuffer(), cIter.idOffset());
387
388 DirCacheIterator dcIter = walk.getTree(1,
389 DirCacheIterator.class);
390 if (dcIter != null && dcIter.idEqual(cIter)) {
391 DirCacheEntry indexEntry = dcIter.getDirCacheEntry();
392 entry.setLastModified(indexEntry.getLastModifiedInstant());
393 entry.setLength(indexEntry.getLength());
394 }
395
396 builder.add(entry);
397 }
398
399 builder.commit();
400 } finally {
401 dc.unlock();
402 }
403 }
404
405 private void checkoutIndex(ObjectId commitTree) throws IOException,
406 GitAPIException {
407 DirCache dc = repo.lockDirCache();
408 try {
409 DirCacheCheckout checkout = new DirCacheCheckout(repo, dc,
410 commitTree);
411 checkout.setFailOnConflict(false);
412 checkout.setProgressMonitor(monitor);
413 try {
414 checkout.checkout();
415 } catch (org.eclipse.jgit.errors.CheckoutConflictException cce) {
416 throw new CheckoutConflictException(checkout.getConflicts(),
417 cce);
418 }
419 } finally {
420 dc.unlock();
421 }
422 }
423
424 private void resetMerge() throws IOException {
425 repo.writeMergeHeads(null);
426 repo.writeMergeCommitMsg(null);
427 }
428
429 private void resetCherryPick() throws IOException {
430 repo.writeCherryPickHead(null);
431 repo.writeMergeCommitMsg(null);
432 }
433
434 private void resetRevert() throws IOException {
435 repo.writeRevertHead(null);
436 repo.writeMergeCommitMsg(null);
437 }
438
439
440 @SuppressWarnings("nls")
441 @Override
442 public String toString() {
443 return "ResetCommand [repo=" + repo + ", ref=" + ref + ", mode=" + mode
444 + ", isReflogDisabled=" + isReflogDisabled + ", filepaths="
445 + filepaths + "]";
446 }
447
448 }