code: package com.beijunyi.parallelgit.filesystem.commands;
  
  import java.io.IOException;
  import java.util.Collections;
  import java.util.Map;
  import javax.annotation.Nonnull;
  
  import com.beijunyi.parallelgit.filesystem.GfsStatusProvider;
  import com.beijunyi.parallelgit.filesystem.GitFileSystem;
  import com.beijunyi.parallelgit.filesystem.exceptions.NoHeadCommitException;
  import com.beijunyi.parallelgit.filesystem.merge.MergeConflict;
  import org.eclipse.jgit.dircache.DirCache;
  import org.eclipse.jgit.lib.AnyObjectId;
  import org.eclipse.jgit.merge.*;
  import org.eclipse.jgit.revwalk.RevCommit;
  
  import static com.beijunyi.parallelgit.filesystem.commands.GfsApplyStash.Result.*;
  import static com.beijunyi.parallelgit.filesystem.commands.GfsApplyStash.Status.*;
  import static com.beijunyi.parallelgit.filesystem.io.GfsDefaultCheckout.checkout;
  import static com.beijunyi.parallelgit.filesystem.io.GfsTreeIterator.iterateRoot;
  import static com.beijunyi.parallelgit.filesystem.merge.GfsMergeCheckout.handleConflicts;
  import static com.beijunyi.parallelgit.filesystem.merge.MergeConflict.readConflicts;
  import static com.beijunyi.parallelgit.utils.CommitUtils.getCommit;
  import static java.util.Collections.unmodifiableMap;
  import static org.eclipse.jgit.dircache.DirCache.newInCore;
  import static org.eclipse.jgit.lib.Constants.STASH;
  import static org.eclipse.jgit.merge.MergeStrategy.RECURSIVE;
  
  public class GfsApplyStash extends GfsCommand<GfsApplyStash.Result> {
  
    private static final String LAST_STASH = makeStashId(0);
  
    private MergeStrategy strategy = RECURSIVE;
    private MergeFormatter formatter = new MergeFormatter();
    private DirCache cache = newInCore();
  
    private RevCommit head;
    private String stashId;
    private RevCommit stash;
    private ResolveMerger merger;
  
    public GfsApplyStash(GitFileSystem gfs) {
      super(gfs);
    }
  
    @Nonnull
    public GfsApplyStash stash(String id) throws IOException {
      this.stashId = id;
      return this;
    }
  
    @Nonnull
    public GfsApplyStash stash(int id) throws IOException {
      return stash(makeStashId(id));
    }
  
    @Nonnull
    @Override
    protected GfsApplyStash.Result doExecute(GfsStatusProvider.Update update) throws IOException {
      prepareHead();
      prepareStash();
      prepareMerger();
      return mergeStash();
    }
  
    private void prepareHead() throws IOException {
      if(!status.isInitialized()) throw new NoHeadCommitException();
      head = status.commit();
    }
  
    private void prepareStash() throws IOException {
      if(stashId == null) stashId = LAST_STASH;
      stash = getCommit(stashId, repo);
    }
  
    private void prepareMerger() throws IOException {
      merger = (ResolveMerger)strategy.newMerger(repo, true);
      merger.setBase(stash.getParent(0));
      merger.setCommitNames(new String[] {"BASE", "Updated upstream", "Stashed changes"});
      merger.setWorkingTreeIterator(iterateRoot(gfs));
    }
  
    @Nonnull
    private GfsApplyStash.Result mergeStash() throws IOException {
      boolean success = merger.merge(head, stash);
      GfsApplyStash.Result ret;
      if(success) {
        checkoutFiles(merger);
        ret = success();
      } else {
        writeConflicts(merger);
        ret = conflicting(readConflicts(merger));
      }
      return ret;
    }
  
    private void checkoutFiles(Merger merger) throws IOException {
      AnyObjectId treeId = merger.getResultTreeId();
      checkout(gfs, treeId);
    }
  
    private void writeConflicts(ResolveMerger merger) throws IOException {
      Map<String, MergeConflict> conflicts = readConflicts(merger);
      handleConflicts(gfs, conflicts)
        .withFormatter(formatter)
        .checkout(cache);
    }
  
    @Nonnull
    private static String makeStashId(int id) {
      return STASH + "@{" + id + "}";
    }
  
    public enum Status {
      SUCCESS,
      CONFLICTING
    }
  
    public static class Result implements GfsCommandResult {
  
      private final GfsApplyStash.Status status;
      private final Map<String, MergeConflict> conflicts;
  
      private Result(Status status, Map<String, MergeConflict> conflicts) {
        this.status = status;
        this.conflicts = unmodifiableMap(conflicts);
      }
  
      @Nonnull
      public static GfsApplyStash.Result success() {
        return new GfsApplyStash.Result(SUCCESS, Collections.<String, MergeConflict>emptyMap());
      }
  
      @Nonnull
      public static GfsApplyStash.Result conflicting(Map<String, MergeConflict> conflicts) {
        return new GfsApplyStash.Result(CONFLICTING, conflicts);
      }
  
      @Override
      public boolean isSuccessful() {
        return SUCCESS == status;
      }
  
      public boolean hasConflicts() {
        return CONFLICTING == status;
      }
  
      @Nonnull
      public Map<String, MergeConflict> getConflicts() {
        return conflicts;
      }
    }
  
  }
  