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