* This will default to apply the latest stashed commit (stash@{0}) if * unspecified * * @param stashRef * @return {@code this} */ public StashApplyCommand setStashRef(final String stashRef) { this.stashRef = stashRef; return this; } private boolean isEqualEntry(AbstractTreeIterator iter1, AbstractTreeIterator iter2) { if (!iter1.getEntryFileMode().equals(iter2.getEntryFileMode())) return false; ObjectId id1 = iter1.getEntryObjectId(); ObjectId id2 = iter2.getEntryObjectId(); return id1 != null ? id1.equals(id2) : id2 == null; } /** * Would unstashing overwrite local changes? * * @param stashIndexIter * @param stashWorkingTreeIter * @param headIter * @param indexIter * @param workingTreeIter * @return true if unstash conflict, false otherwise */ private boolean isConflict(AbstractTreeIterator stashIndexIter, AbstractTreeIterator stashWorkingTreeIter, AbstractTreeIterator headIter, AbstractTreeIterator indexIter, AbstractTreeIterator workingTreeIter) { // Is the current index dirty? boolean indexDirty = indexIter != null && (headIter == null || !isEqualEntry(indexIter, headIter)); // Is the current working tree dirty? boolean workingTreeDirty = workingTreeIter != null && (headIter == null || !isEqualEntry(workingTreeIter, headIter)); // Would unstashing overwrite existing index changes? if (indexDirty && stashIndexIter != null && indexIter != null && !isEqualEntry(stashIndexIter, indexIter)) return true; // Would unstashing overwrite existing working tree changes? if (workingTreeDirty && stashWorkingTreeIter != null && workingTreeIter != null && !isEqualEntry(stashWorkingTreeIter, workingTreeIter)) return true; return false; } private ObjectId getHeadTree() throws GitAPIException { final ObjectId headTree; try { headTree = repo.resolve(Constants.HEAD + "^{tree}"); } catch (IOException e) { throw new JGitInternalException(JGitText.get().cannotReadTree, e); } if (headTree == null) throw new NoHeadException(JGitText.get().cannotReadTree); return headTree; } private ObjectId getStashId() throws GitAPIException { final String revision = stashRef != null ? stashRef : DEFAULT_REF; final ObjectId stashId; try { stashId = repo.resolve(revision); } catch (IOException e) { throw new InvalidRefNameException(MessageFormat.format( JGitText.get().stashResolveFailed, revision), e); } if (stashId == null) throw new InvalidRefNameException(MessageFormat.format( JGitText.get().stashResolveFailed, revision)); return stashId; } private void scanForConflicts(TreeWalk treeWalk) throws IOException { File workingTree = repo.getWorkTree(); while (treeWalk.next()) { // State of the stashed index and working directory AbstractTreeIterator stashIndexIter = treeWalk.getTree(1, AbstractTreeIterator.class); AbstractTreeIterator stashWorkingIter = treeWalk.getTree(2, AbstractTreeIterator.class); // State of the current HEAD, index, and working directory AbstractTreeIterator headIter = treeWalk.getTree(3, AbstractTreeIterator.class); AbstractTreeIterator indexIter = treeWalk.getTree(4, AbstractTreeIterator.class); AbstractTreeIterator workingIter = treeWalk.getTree(5, AbstractTreeIterator.class); if (isConflict(stashIndexIter, stashWorkingIter, headIter, indexIter, workingIter)) { String path = treeWalk.getPathString(); File file = new File(workingTree, path); throw new CheckoutConflictException(file.getAbsolutePath()); } } } private void applyChanges(TreeWalk treeWalk, DirCache cache, DirCacheEditor editor) throws IOException { File workingTree = repo.getWorkTree(); while (treeWalk.next()) { String path = treeWalk.getPathString(); File file = new File(workingTree, path); // State of the stashed HEAD, index, and working directory AbstractTreeIterator stashHeadIter = treeWalk.getTree(0, AbstractTreeIterator.class); AbstractTreeIterator stashIndexIter = treeWalk.getTree(1, AbstractTreeIterator.class); AbstractTreeIterator stashWorkingIter = treeWalk.getTree(2, AbstractTreeIterator.class); if (stashWorkingIter != null && stashIndexIter != null) { // Checkout index change DirCacheEntry entry = cache.getEntry(path); if (entry == null) entry = new DirCacheEntry(treeWalk.getRawPath()); entry.setFileMode(stashIndexIter.getEntryFileMode()); entry.setObjectId(stashIndexIter.getEntryObjectId()); DirCacheCheckout.checkoutEntry(repo, file, entry, treeWalk.getObjectReader()); final DirCacheEntry updatedEntry = entry; editor.add(new PathEdit(path) { public void apply(DirCacheEntry ent) { ent.copyMetaData(updatedEntry); } }); // Checkout working directory change if (!stashWorkingIter.idEqual(stashIndexIter)) { entry = new DirCacheEntry(treeWalk.getRawPath()); entry.setObjectId(stashWorkingIter.getEntryObjectId()); DirCacheCheckout.checkoutEntry(repo, file, entry, treeWalk.getObjectReader()); } } else { if (stashIndexIter == null || (stashHeadIter != null && !stashIndexIter .idEqual(stashHeadIter))) editor.add(new DeletePath(path)); FileUtils .delete(file, FileUtils.RETRY | FileUtils.SKIP_MISSING); } } } /** * Apply the changes in a stashed commit to the working directory and index * * @return id of stashed commit that was applied * @throws GitAPIException * @throws WrongRepositoryStateException */ public ObjectId call() throws GitAPIException, WrongRepositoryStateException { checkCallable(); if (repo.getRepositoryState() != RepositoryState.SAFE) throw new WrongRepositoryStateException(MessageFormat.format( JGitText.get().stashApplyOnUnsafeRepository, repo.getRepositoryState())); final ObjectId headTree = getHeadTree(); final ObjectId stashId = getStashId(); ObjectReader reader = repo.newObjectReader(); try { RevWalk revWalk = new RevWalk(reader); RevCommit stashCommit = revWalk.parseCommit(stashId); if (stashCommit.getParentCount() != 2) throw new JGitInternalException(MessageFormat.format( JGitText.get().stashCommitMissingTwoParents, stashId.name())); RevTree stashWorkingTree = stashCommit.getTree(); RevTree stashIndexTree = revWalk.parseCommit( stashCommit.getParent(1)).getTree(); RevTree stashHeadTree = revWalk.parseCommit( stashCommit.getParent(0)).getTree(); CanonicalTreeParser stashWorkingIter = new CanonicalTreeParser(); stashWorkingIter.reset(reader, stashWorkingTree); CanonicalTreeParser stashIndexIter = new CanonicalTreeParser(); stashIndexIter.reset(reader, stashIndexTree); CanonicalTreeParser stashHeadIter = new CanonicalTreeParser(); stashHeadIter.reset(reader, stashHeadTree); CanonicalTreeParser headIter = new CanonicalTreeParser(); headIter.reset(reader, headTree); DirCache cache = repo.lockDirCache(); DirCacheEditor editor = cache.editor(); try { DirCacheIterator indexIter = new DirCacheIterator(cache); FileTreeIterator workingIter = new FileTreeIterator(repo); TreeWalk treeWalk = new TreeWalk(reader); treeWalk.setRecursive(true); treeWalk.setFilter(new StashDiffFilter()); treeWalk.addTree(stashHeadIter); treeWalk.addTree(stashIndexIter); treeWalk.addTree(stashWorkingIter); treeWalk.addTree(headIter); treeWalk.addTree(indexIter); treeWalk.addTree(workingIter); scanForConflicts(treeWalk); // Reset trees and walk treeWalk.reset(); stashWorkingIter.reset(reader, stashWorkingTree); stashIndexIter.reset(reader, stashIndexTree); stashHeadIter.reset(reader, stashHeadTree); treeWalk.addTree(stashHeadIter); treeWalk.addTree(stashIndexIter); treeWalk.addTree(stashWorkingIter); applyChanges(treeWalk, cache, editor); } finally { editor.commit(); cache.unlock(); } } catch (JGitInternalException e) { throw e; } catch (IOException e) { throw new JGitInternalException(JGitText.get().stashApplyFailed, e); } finally { reader.release(); } return stashId; } }