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 static org.eclipse.jgit.treewalk.TreeWalk.OperationType.CHECKOUT_OP;
46
47 import java.io.IOException;
48 import java.text.MessageFormat;
49 import java.util.HashSet;
50 import java.util.List;
51 import java.util.Set;
52
53 import org.eclipse.jgit.api.errors.GitAPIException;
54 import org.eclipse.jgit.api.errors.InvalidRefNameException;
55 import org.eclipse.jgit.api.errors.JGitInternalException;
56 import org.eclipse.jgit.api.errors.NoHeadException;
57 import org.eclipse.jgit.api.errors.StashApplyFailureException;
58 import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
59 import org.eclipse.jgit.dircache.DirCache;
60 import org.eclipse.jgit.dircache.DirCacheBuilder;
61 import org.eclipse.jgit.dircache.DirCacheCheckout;
62 import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
63 import org.eclipse.jgit.dircache.DirCacheEntry;
64 import org.eclipse.jgit.dircache.DirCacheIterator;
65 import org.eclipse.jgit.errors.CheckoutConflictException;
66 import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
67 import org.eclipse.jgit.internal.JGitText;
68 import org.eclipse.jgit.lib.Constants;
69 import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
70 import org.eclipse.jgit.lib.ObjectId;
71 import org.eclipse.jgit.lib.ObjectReader;
72 import org.eclipse.jgit.lib.Repository;
73 import org.eclipse.jgit.lib.RepositoryState;
74 import org.eclipse.jgit.merge.MergeStrategy;
75 import org.eclipse.jgit.merge.ResolveMerger;
76 import org.eclipse.jgit.revwalk.RevCommit;
77 import org.eclipse.jgit.revwalk.RevTree;
78 import org.eclipse.jgit.revwalk.RevWalk;
79 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
80 import org.eclipse.jgit.treewalk.FileTreeIterator;
81 import org.eclipse.jgit.treewalk.TreeWalk;
82
83
84
85
86
87
88
89
90
91
92
93 public class StashApplyCommand extends GitCommand<ObjectId> {
94
95 private static final String DEFAULT_REF = Constants.STASH + "@{0}";
96
97 private String stashRef;
98
99 private boolean restoreIndex = true;
100
101 private boolean restoreUntracked = true;
102
103 private boolean ignoreRepositoryState;
104
105 private MergeStrategy strategy = MergeStrategy.RECURSIVE;
106
107
108
109
110
111
112
113
114 public StashApplyCommand(Repository repo) {
115 super(repo);
116 }
117
118
119
120
121
122
123
124
125
126
127
128 public StashApplyCommand setStashRef(String stashRef) {
129 this.stashRef = stashRef;
130 return this;
131 }
132
133
134
135
136
137
138
139
140
141 public StashApplyCommand ignoreRepositoryState(boolean willIgnoreRepositoryState) {
142 this.ignoreRepositoryState = willIgnoreRepositoryState;
143 return this;
144 }
145
146 private ObjectId getStashId() throws GitAPIException {
147 final String revision = stashRef != null ? stashRef : DEFAULT_REF;
148 final ObjectId stashId;
149 try {
150 stashId = repo.resolve(revision);
151 } catch (IOException e) {
152 throw new InvalidRefNameException(MessageFormat.format(
153 JGitText.get().stashResolveFailed, revision), e);
154 }
155 if (stashId == null)
156 throw new InvalidRefNameException(MessageFormat.format(
157 JGitText.get().stashResolveFailed, revision));
158 return stashId;
159 }
160
161
162
163
164
165
166 @Override
167 public ObjectId call() throws GitAPIException,
168 WrongRepositoryStateException, NoHeadException,
169 StashApplyFailureException {
170 checkCallable();
171
172 if (!ignoreRepositoryState
173 && repo.getRepositoryState() != RepositoryState.SAFE)
174 throw new WrongRepositoryStateException(MessageFormat.format(
175 JGitText.get().stashApplyOnUnsafeRepository,
176 repo.getRepositoryState()));
177
178 try (ObjectReader reader = repo.newObjectReader();
179 RevWalk revWalk = new RevWalk(reader)) {
180
181 ObjectId headCommit = repo.resolve(Constants.HEAD);
182 if (headCommit == null)
183 throw new NoHeadException(JGitText.get().stashApplyWithoutHead);
184
185 final ObjectId stashId = getStashId();
186 RevCommit stashCommit = revWalk.parseCommit(stashId);
187 if (stashCommit.getParentCount() < 2
188 || stashCommit.getParentCount() > 3)
189 throw new JGitInternalException(MessageFormat.format(
190 JGitText.get().stashCommitIncorrectNumberOfParents,
191 stashId.name(),
192 Integer.valueOf(stashCommit.getParentCount())));
193
194 ObjectId headTree = repo.resolve(Constants.HEAD + "^{tree}");
195 ObjectId stashIndexCommit = revWalk.parseCommit(stashCommit
196 .getParent(1));
197 ObjectId stashHeadCommit = stashCommit.getParent(0);
198 ObjectId untrackedCommit = null;
199 if (restoreUntracked && stashCommit.getParentCount() == 3)
200 untrackedCommit = revWalk.parseCommit(stashCommit.getParent(2));
201
202 ResolveMerger merger = (ResolveMerger) strategy.newMerger(repo);
203 merger.setCommitNames(new String[] { "stashed HEAD", "HEAD",
204 "stash" });
205 merger.setBase(stashHeadCommit);
206 merger.setWorkingTreeIterator(new FileTreeIterator(repo));
207 boolean mergeSucceeded = merger.merge(headCommit, stashCommit);
208 List<String> modifiedByMerge = merger.getModifiedFiles();
209 if (!modifiedByMerge.isEmpty()) {
210 repo.fireEvent(
211 new WorkingTreeModifiedEvent(modifiedByMerge, null));
212 }
213 if (mergeSucceeded) {
214 DirCache dc = repo.lockDirCache();
215 DirCacheCheckout dco = new DirCacheCheckout(repo, headTree,
216 dc, merger.getResultTreeId());
217 dco.setFailOnConflict(true);
218 dco.checkout();
219 if (restoreIndex) {
220 ResolveMerger ixMerger = (ResolveMerger) strategy
221 .newMerger(repo, true);
222 ixMerger.setCommitNames(new String[] { "stashed HEAD",
223 "HEAD", "stashed index" });
224 ixMerger.setBase(stashHeadCommit);
225 boolean ok = ixMerger.merge(headCommit, stashIndexCommit);
226 if (ok) {
227 resetIndex(revWalk
228 .parseTree(ixMerger.getResultTreeId()));
229 } else {
230 throw new StashApplyFailureException(
231 JGitText.get().stashApplyConflict);
232 }
233 }
234
235 if (untrackedCommit != null) {
236 ResolveMerger untrackedMerger = (ResolveMerger) strategy
237 .newMerger(repo, true);
238 untrackedMerger.setCommitNames(new String[] {
239 "null", "HEAD", "untracked files" });
240
241
242
243
244
245 untrackedMerger.setBase(null);
246 boolean ok = untrackedMerger.merge(headCommit,
247 untrackedCommit);
248 if (ok) {
249 try {
250 RevTree untrackedTree = revWalk
251 .parseTree(untrackedCommit);
252 resetUntracked(untrackedTree);
253 } catch (CheckoutConflictException e) {
254 throw new StashApplyFailureException(
255 JGitText.get().stashApplyConflict, e);
256 }
257 } else {
258 throw new StashApplyFailureException(
259 JGitText.get().stashApplyConflict);
260 }
261 }
262 } else {
263 throw new StashApplyFailureException(
264 JGitText.get().stashApplyConflict);
265 }
266 return stashId;
267
268 } catch (JGitInternalException e) {
269 throw e;
270 } catch (IOException e) {
271 throw new JGitInternalException(JGitText.get().stashApplyFailed, e);
272 }
273 }
274
275
276
277
278
279
280
281
282 @Deprecated
283 public void setApplyIndex(boolean applyIndex) {
284 this.restoreIndex = applyIndex;
285 }
286
287
288
289
290
291
292
293
294
295 public StashApplyCommand setRestoreIndex(boolean restoreIndex) {
296 this.restoreIndex = restoreIndex;
297 return this;
298 }
299
300
301
302
303
304
305
306
307
308
309 public StashApplyCommand setStrategy(MergeStrategy strategy) {
310 this.strategy = strategy;
311 return this;
312 }
313
314
315
316
317
318
319
320
321
322 @Deprecated
323 public void setApplyUntracked(boolean applyUntracked) {
324 this.restoreUntracked = applyUntracked;
325 }
326
327
328
329
330
331
332
333
334
335 public StashApplyCommand setRestoreUntracked(boolean restoreUntracked) {
336 this.restoreUntracked = restoreUntracked;
337 return this;
338 }
339
340 private void resetIndex(RevTree tree) throws IOException {
341 DirCache dc = repo.lockDirCache();
342 try (TreeWalkeeWalk.html#TreeWalk">TreeWalk walk = new TreeWalk(repo)) {
343 DirCacheBuilder builder = dc.builder();
344
345 walk.addTree(tree);
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 DirCacheEntryEntry.html#DirCacheEntry">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.getLastModifiedInstant());
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 resetUntracked(RevTree tree) throws CheckoutConflictException,
379 IOException {
380 Set<String> actuallyModifiedPaths = new HashSet<>();
381
382 try (TreeWalkeeWalk.html#TreeWalk">TreeWalk walk = new TreeWalk(repo)) {
383 walk.addTree(tree);
384 walk.addTree(new FileTreeIterator(repo));
385 walk.setRecursive(true);
386
387 final ObjectReader reader = walk.getObjectReader();
388
389 while (walk.next()) {
390 final AbstractTreeIterator cIter = walk.getTree(0,
391 AbstractTreeIterator.class);
392 if (cIter == null)
393
394 continue;
395
396 final EolStreamType eolStreamType = walk
397 .getEolStreamType(CHECKOUT_OP);
398 final DirCacheEntryEntry.html#DirCacheEntry">DirCacheEntry entry = new DirCacheEntry(walk.getRawPath());
399 entry.setFileMode(cIter.getEntryFileMode());
400 entry.setObjectIdFromRaw(cIter.idBuffer(), cIter.idOffset());
401
402 FileTreeIterator fIter = walk
403 .getTree(1, FileTreeIterator.class);
404 if (fIter != null) {
405 if (fIter.isModified(entry, true, reader)) {
406
407 throw new CheckoutConflictException(
408 entry.getPathString());
409 }
410 }
411
412 checkoutPath(entry, reader,
413 new CheckoutMetadata(eolStreamType, null));
414 actuallyModifiedPaths.add(entry.getPathString());
415 }
416 } finally {
417 if (!actuallyModifiedPaths.isEmpty()) {
418 repo.fireEvent(new WorkingTreeModifiedEvent(
419 actuallyModifiedPaths, null));
420 }
421 }
422 }
423
424 private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
425 CheckoutMetadata checkoutMetadata) {
426 try {
427 DirCacheCheckout.checkoutEntry(repo, entry, reader, true,
428 checkoutMetadata);
429 } catch (IOException e) {
430 throw new JGitInternalException(MessageFormat.format(
431 JGitText.get().checkoutConflictWithFile,
432 entry.getPathString()), e);
433 }
434 }
435 }