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