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 public ObjectId call() throws GitAPIException,
161 WrongRepositoryStateException, NoHeadException,
162 StashApplyFailureException {
163 checkCallable();
164
165 if (!ignoreRepositoryState
166 && repo.getRepositoryState() != RepositoryState.SAFE)
167 throw new WrongRepositoryStateException(MessageFormat.format(
168 JGitText.get().stashApplyOnUnsafeRepository,
169 repo.getRepositoryState()));
170
171 try (ObjectReader reader = repo.newObjectReader();
172 RevWalk revWalk = new RevWalk(reader)) {
173
174 ObjectId headCommit = repo.resolve(Constants.HEAD);
175 if (headCommit == null)
176 throw new NoHeadException(JGitText.get().stashApplyWithoutHead);
177
178 final ObjectId stashId = getStashId();
179 RevCommit stashCommit = revWalk.parseCommit(stashId);
180 if (stashCommit.getParentCount() < 2
181 || stashCommit.getParentCount() > 3)
182 throw new JGitInternalException(MessageFormat.format(
183 JGitText.get().stashCommitIncorrectNumberOfParents,
184 stashId.name(),
185 Integer.valueOf(stashCommit.getParentCount())));
186
187 ObjectId headTree = repo.resolve(Constants.HEAD + "^{tree}");
188 ObjectId stashIndexCommit = revWalk.parseCommit(stashCommit
189 .getParent(1));
190 ObjectId stashHeadCommit = stashCommit.getParent(0);
191 ObjectId untrackedCommit = null;
192 if (applyUntracked && stashCommit.getParentCount() == 3)
193 untrackedCommit = revWalk.parseCommit(stashCommit.getParent(2));
194
195 ResolveMerger merger = (ResolveMerger) strategy.newMerger(repo);
196 merger.setCommitNames(new String[] { "stashed HEAD", "HEAD",
197 "stash" });
198 merger.setBase(stashHeadCommit);
199 merger.setWorkingTreeIterator(new FileTreeIterator(repo));
200 if (merger.merge(headCommit, stashCommit)) {
201 DirCache dc = repo.lockDirCache();
202 DirCacheCheckout dco = new DirCacheCheckout(repo, headTree,
203 dc, merger.getResultTreeId());
204 dco.setFailOnConflict(true);
205 dco.checkout();
206 if (applyIndex) {
207 ResolveMerger ixMerger = (ResolveMerger) strategy
208 .newMerger(repo, true);
209 ixMerger.setCommitNames(new String[] { "stashed HEAD",
210 "HEAD", "stashed index" });
211 ixMerger.setBase(stashHeadCommit);
212 boolean ok = ixMerger.merge(headCommit, stashIndexCommit);
213 if (ok) {
214 resetIndex(revWalk
215 .parseTree(ixMerger.getResultTreeId()));
216 } else {
217 throw new StashApplyFailureException(
218 JGitText.get().stashApplyConflict);
219 }
220 }
221
222 if (untrackedCommit != null) {
223 ResolveMerger untrackedMerger = (ResolveMerger) strategy
224 .newMerger(repo, true);
225 untrackedMerger.setCommitNames(new String[] {
226 "stashed HEAD", "HEAD", "untracked files" });
227 untrackedMerger.setBase(stashHeadCommit);
228 boolean ok = untrackedMerger.merge(headCommit,
229 untrackedCommit);
230 if (ok)
231 try {
232 RevTree untrackedTree = revWalk
233 .parseTree(untrackedMerger
234 .getResultTreeId());
235 resetUntracked(untrackedTree);
236 } catch (CheckoutConflictException e) {
237 throw new StashApplyFailureException(
238 JGitText.get().stashApplyConflict);
239 }
240 else
241 throw new StashApplyFailureException(
242 JGitText.get().stashApplyConflict);
243 }
244 } else {
245 throw new StashApplyFailureException(
246 JGitText.get().stashApplyConflict);
247 }
248 return stashId;
249
250 } catch (JGitInternalException e) {
251 throw e;
252 } catch (IOException e) {
253 throw new JGitInternalException(JGitText.get().stashApplyFailed, e);
254 }
255 }
256
257
258
259
260
261 public void setApplyIndex(boolean applyIndex) {
262 this.applyIndex = applyIndex;
263 }
264
265
266
267
268
269
270
271
272 public StashApplyCommand setStrategy(MergeStrategy strategy) {
273 this.strategy = strategy;
274 return this;
275 }
276
277
278
279
280
281
282 public void setApplyUntracked(boolean applyUntracked) {
283 this.applyUntracked = applyUntracked;
284 }
285
286 private void resetIndex(RevTree tree) throws IOException {
287 DirCache dc = repo.lockDirCache();
288 try (TreeWalk walk = new TreeWalk(repo)) {
289 DirCacheBuilder builder = dc.builder();
290
291 walk.addTree(tree);
292 walk.addTree(new DirCacheIterator(dc));
293 walk.setRecursive(true);
294
295 while (walk.next()) {
296 AbstractTreeIterator cIter = walk.getTree(0,
297 AbstractTreeIterator.class);
298 if (cIter == null) {
299
300 continue;
301 }
302
303 final DirCacheEntry entry = new DirCacheEntry(walk.getRawPath());
304 entry.setFileMode(cIter.getEntryFileMode());
305 entry.setObjectIdFromRaw(cIter.idBuffer(), cIter.idOffset());
306
307 DirCacheIterator dcIter = walk.getTree(1,
308 DirCacheIterator.class);
309 if (dcIter != null && dcIter.idEqual(cIter)) {
310 DirCacheEntry indexEntry = dcIter.getDirCacheEntry();
311 entry.setLastModified(indexEntry.getLastModified());
312 entry.setLength(indexEntry.getLength());
313 }
314
315 builder.add(entry);
316 }
317
318 builder.commit();
319 } finally {
320 dc.unlock();
321 }
322 }
323
324 private void resetUntracked(RevTree tree) throws CheckoutConflictException,
325 IOException {
326
327 try (TreeWalk walk = new TreeWalk(repo)) {
328 walk.addTree(tree);
329 walk.addTree(new FileTreeIterator(repo));
330 walk.setRecursive(true);
331
332 final ObjectReader reader = walk.getObjectReader();
333
334 while (walk.next()) {
335 final AbstractTreeIterator cIter = walk.getTree(0,
336 AbstractTreeIterator.class);
337 if (cIter == null)
338
339 continue;
340
341 final EolStreamType eolStreamType = walk.getEolStreamType();
342 final DirCacheEntry entry = new DirCacheEntry(walk.getRawPath());
343 entry.setFileMode(cIter.getEntryFileMode());
344 entry.setObjectIdFromRaw(cIter.idBuffer(), cIter.idOffset());
345
346 FileTreeIterator fIter = walk
347 .getTree(1, FileTreeIterator.class);
348 if (fIter != null) {
349 if (fIter.isModified(entry, true, reader)) {
350
351 throw new CheckoutConflictException(
352 entry.getPathString());
353 }
354 }
355
356 checkoutPath(entry, reader,
357 new CheckoutMetadata(eolStreamType, null));
358 }
359 }
360 }
361
362 private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
363 CheckoutMetadata checkoutMetadata) {
364 try {
365 DirCacheCheckout.checkoutEntry(repo, entry, reader, true,
366 checkoutMetadata);
367 } catch (IOException e) {
368 throw new JGitInternalException(MessageFormat.format(
369 JGitText.get().checkoutConflictWithFile,
370 entry.getPathString()), e);
371 }
372 }
373 }