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