package com.beijunyi.parallelgit.filesystem.io; import java.io.IOException; import java.util.*; import javax.annotation.Nonnull; import com.beijunyi.parallelgit.filesystem.GfsStatusProvider; import com.beijunyi.parallelgit.filesystem.GitFileSystem; import com.beijunyi.parallelgit.utils.io.GitFileEntry; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.treewalk.*; import static com.beijunyi.parallelgit.filesystem.io.GfsCheckoutConflict.threeWayConflict; import static com.beijunyi.parallelgit.filesystem.io.GfsTreeIterator.iterateRoot; import static com.beijunyi.parallelgit.filesystem.utils.GfsPathUtils.toAbsolutePath; public class GfsDefaultCheckout { private static final int HEAD = 0; private static final int TARGET = 1; private static final int WORKTREE = 2; private final GitFileSystem gfs; private final GfsStatusProvider status; private final ObjectReader reader; protected final GfsCheckoutChangesCollector changes; private Set<String> ignoredFiles; public GfsDefaultCheckout(GitFileSystem gfs, boolean failOnConflict) { this.gfs = gfs; this.status = gfs.getStatusProvider(); this.reader = gfs.getRepository().newObjectReader(); changes = new GfsCheckoutChangesCollector(failOnConflict); } public GfsDefaultCheckout(GitFileSystem gfs) { this(gfs, true); } public static void checkout(GitFileSystem gfs, AnyObjectId tree) throws IOException { new GfsDefaultCheckout(gfs).checkout(tree); } @Nonnull public GfsDefaultCheckout ignoredFiles(Collection<String> ignoredFiles) { this.ignoredFiles = new HashSet<>(ignoredFiles); return this; } public void checkout(AbstractTreeIterator iterator) throws IOException { TreeWalk tw = prepareTreeWalk(iterator); collectChanges(tw); if(!hasConflicts()) applyChanges(); } public void checkout(AnyObjectId tree) throws IOException { checkout(new CanonicalTreeParser(null, reader, tree)); } public void checkout(DirCache cache) throws IOException { checkout(new DirCacheIterator(cache)); } public boolean hasConflicts() { return changes.hasConflicts(); } @Nonnull public Map<String, GfsCheckoutConflict> getConflicts() { return changes.getConflicts(); } protected boolean skips(String path) { return ignoredFiles != null && ignoredFiles.contains(path); } protected void applyChanges() throws IOException { if(!changes.isEmpty()) changes.applyTo(gfs); } @Nonnull private TreeWalk prepareTreeWalk(AbstractTreeIterator iterator) throws IOException { TreeWalk ret = new NameConflictTreeWalk(gfs.getRepository()); ret.addTree(new CanonicalTreeParser(null, reader, status.commit().getTree())); ret.addTree(iterator); ret.addTree(iterateRoot(gfs)); return ret; } private void collectChanges(TreeWalk tw) throws IOException { while(tw.next()) { String path = toAbsolutePath(tw.getPathString()); if(skips(path)) continue; GitFileEntry head = GitFileEntry.newEntry(tw, HEAD); GitFileEntry target = GitFileEntry.newEntry(tw, TARGET); GitFileEntry worktree = GitFileEntry.newEntry(tw, WORKTREE); if(mergeEntries(path, head, target, worktree)) tw.enterSubtree(); } } private boolean mergeEntries(String path, GitFileEntry head, GitFileEntry target, GitFileEntry worktree) throws IOException { if(target.equals(worktree) || target.equals(head)) return false; if(head.equals(worktree)) { changes.addChange(path, target); return target.isVirtualSubtree(); } if(target.isSubtree() && worktree.isSubtree()) return true; changes.addConflict(threeWayConflict(path, head, target, worktree)); return false; } }