code: 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;
    }
  
  }
  