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 "null", "HEAD", "untracked files" });
227
228
229
230
231
232 untrackedMerger.setBase(null);
233 boolean ok = untrackedMerger.merge(headCommit,
234 untrackedCommit);
235 if (ok)
236 try {
237 RevTree untrackedTree = revWalk
238 .parseTree(untrackedMerger
239 .getResultTreeId());
240 resetUntracked(untrackedTree);
241 } catch (CheckoutConflictException e) {
242 throw new StashApplyFailureException(
243 JGitText.get().stashApplyConflict);
244 }
245 else
246 throw new StashApplyFailureException(
247 JGitText.get().stashApplyConflict);
248 }
249 } else {
250 throw new StashApplyFailureException(
251 JGitText.get().stashApplyConflict);
252 }
253 return stashId;
254
255 } catch (JGitInternalException e) {
256 throw e;
257 } catch (IOException e) {
258 throw new JGitInternalException(JGitText.get().stashApplyFailed, e);
259 }
260 }
261
262
263
264
265
266 public void setApplyIndex(boolean applyIndex) {
267 this.applyIndex = applyIndex;
268 }
269
270
271
272
273
274
275
276
277 public StashApplyCommand setStrategy(MergeStrategy strategy) {
278 this.strategy = strategy;
279 return this;
280 }
281
282
283
284
285
286
287 public void setApplyUntracked(boolean applyUntracked) {
288 this.applyUntracked = applyUntracked;
289 }
290
291 private void resetIndex(RevTree tree) throws IOException {
292 DirCache dc = repo.lockDirCache();
293 try (TreeWalk walk = new TreeWalk(repo)) {
294 DirCacheBuilder builder = dc.builder();
295
296 walk.addTree(tree);
297 walk.addTree(new DirCacheIterator(dc));
298 walk.setRecursive(true);
299
300 while (walk.next()) {
301 AbstractTreeIterator cIter = walk.getTree(0,
302 AbstractTreeIterator.class);
303 if (cIter == null) {
304
305 continue;
306 }
307
308 final DirCacheEntry entry = new DirCacheEntry(walk.getRawPath());
309 entry.setFileMode(cIter.getEntryFileMode());
310 entry.setObjectIdFromRaw(cIter.idBuffer(), cIter.idOffset());
311
312 DirCacheIterator dcIter = walk.getTree(1,
313 DirCacheIterator.class);
314 if (dcIter != null && dcIter.idEqual(cIter)) {
315 DirCacheEntry indexEntry = dcIter.getDirCacheEntry();
316 entry.setLastModified(indexEntry.getLastModified());
317 entry.setLength(indexEntry.getLength());
318 }
319
320 builder.add(entry);
321 }
322
323 builder.commit();
324 } finally {
325 dc.unlock();
326 }
327 }
328
329 private void resetUntracked(RevTree tree) throws CheckoutConflictException,
330 IOException {
331
332 try (TreeWalk walk = new TreeWalk(repo)) {
333 walk.addTree(tree);
334 walk.addTree(new FileTreeIterator(repo));
335 walk.setRecursive(true);
336
337 final ObjectReader reader = walk.getObjectReader();
338
339 while (walk.next()) {
340 final AbstractTreeIterator cIter = walk.getTree(0,
341 AbstractTreeIterator.class);
342 if (cIter == null)
343
344 continue;
345
346 final EolStreamType eolStreamType = walk.getEolStreamType();
347 final DirCacheEntry entry = new DirCacheEntry(walk.getRawPath());
348 entry.setFileMode(cIter.getEntryFileMode());
349 entry.setObjectIdFromRaw(cIter.idBuffer(), cIter.idOffset());
350
351 FileTreeIterator fIter = walk
352 .getTree(1, FileTreeIterator.class);
353 if (fIter != null) {
354 if (fIter.isModified(entry, true, reader)) {
355
356 throw new CheckoutConflictException(
357 entry.getPathString());
358 }
359 }
360
361 checkoutPath(entry, reader,
362 new CheckoutMetadata(eolStreamType, null));
363 }
364 }
365 }
366
367 private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
368 CheckoutMetadata checkoutMetadata) {
369 try {
370 DirCacheCheckout.checkoutEntry(repo, entry, reader, true,
371 checkoutMetadata);
372 } catch (IOException e) {
373 throw new JGitInternalException(MessageFormat.format(
374 JGitText.get().checkoutConflictWithFile,
375 entry.getPathString()), e);
376 }
377 }
378 }