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