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 applyIndex = true;
100
101 private boolean applyUntracked = 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 (applyUntracked && 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 (applyIndex) {
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 public void setApplyIndex(boolean applyIndex) {
282 this.applyIndex = applyIndex;
283 }
284
285
286
287
288
289
290
291
292
293
294 public StashApplyCommand setStrategy(MergeStrategy strategy) {
295 this.strategy = strategy;
296 return this;
297 }
298
299
300
301
302
303
304
305
306 public void setApplyUntracked(boolean applyUntracked) {
307 this.applyUntracked = applyUntracked;
308 }
309
310 private void resetIndex(RevTree tree) throws IOException {
311 DirCache dc = repo.lockDirCache();
312 try (TreeWalk walk = new TreeWalk(repo)) {
313 DirCacheBuilder builder = dc.builder();
314
315 walk.addTree(tree);
316 walk.addTree(new DirCacheIterator(dc));
317 walk.setRecursive(true);
318
319 while (walk.next()) {
320 AbstractTreeIterator cIter = walk.getTree(0,
321 AbstractTreeIterator.class);
322 if (cIter == null) {
323
324 continue;
325 }
326
327 final DirCacheEntry entry = new DirCacheEntry(walk.getRawPath());
328 entry.setFileMode(cIter.getEntryFileMode());
329 entry.setObjectIdFromRaw(cIter.idBuffer(), cIter.idOffset());
330
331 DirCacheIterator dcIter = walk.getTree(1,
332 DirCacheIterator.class);
333 if (dcIter != null && dcIter.idEqual(cIter)) {
334 DirCacheEntry indexEntry = dcIter.getDirCacheEntry();
335 entry.setLastModified(indexEntry.getLastModifiedInstant());
336 entry.setLength(indexEntry.getLength());
337 }
338
339 builder.add(entry);
340 }
341
342 builder.commit();
343 } finally {
344 dc.unlock();
345 }
346 }
347
348 private void resetUntracked(RevTree tree) throws CheckoutConflictException,
349 IOException {
350 Set<String> actuallyModifiedPaths = new HashSet<>();
351
352 try (TreeWalk walk = new TreeWalk(repo)) {
353 walk.addTree(tree);
354 walk.addTree(new FileTreeIterator(repo));
355 walk.setRecursive(true);
356
357 final ObjectReader reader = walk.getObjectReader();
358
359 while (walk.next()) {
360 final AbstractTreeIterator cIter = walk.getTree(0,
361 AbstractTreeIterator.class);
362 if (cIter == null)
363
364 continue;
365
366 final EolStreamType eolStreamType = walk
367 .getEolStreamType(CHECKOUT_OP);
368 final DirCacheEntry entry = new DirCacheEntry(walk.getRawPath());
369 entry.setFileMode(cIter.getEntryFileMode());
370 entry.setObjectIdFromRaw(cIter.idBuffer(), cIter.idOffset());
371
372 FileTreeIterator fIter = walk
373 .getTree(1, FileTreeIterator.class);
374 if (fIter != null) {
375 if (fIter.isModified(entry, true, reader)) {
376
377 throw new CheckoutConflictException(
378 entry.getPathString());
379 }
380 }
381
382 checkoutPath(entry, reader,
383 new CheckoutMetadata(eolStreamType, null));
384 actuallyModifiedPaths.add(entry.getPathString());
385 }
386 } finally {
387 if (!actuallyModifiedPaths.isEmpty()) {
388 repo.fireEvent(new WorkingTreeModifiedEvent(
389 actuallyModifiedPaths, null));
390 }
391 }
392 }
393
394 private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
395 CheckoutMetadata checkoutMetadata) {
396 try {
397 DirCacheCheckout.checkoutEntry(repo, entry, reader, true,
398 checkoutMetadata);
399 } catch (IOException e) {
400 throw new JGitInternalException(MessageFormat.format(
401 JGitText.get().checkoutConflictWithFile,
402 entry.getPathString()), e);
403 }
404 }
405 }