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