code: package com.beijunyi.parallelgit;
  
  import java.io.File;
  import java.io.IOException;
  import javax.annotation.Nonnull;
  import javax.annotation.Nullable;
  
  import com.beijunyi.parallelgit.utils.*;
  import org.eclipse.jgit.dircache.DirCache;
  import org.eclipse.jgit.dircache.DirCacheBuilder;
  import org.eclipse.jgit.dircache.DirCacheEntry;
  import org.eclipse.jgit.errors.NoWorkTreeException;
  import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
  import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
  import org.eclipse.jgit.lib.*;
  import org.eclipse.jgit.revwalk.RevCommit;
  import org.eclipse.jgit.util.FileUtils;
  import org.junit.After;
  import org.junit.Before;
  
  import static com.beijunyi.parallelgit.utils.CacheUtils.*;
  import static java.util.UUID.randomUUID;
  import static org.eclipse.jgit.lib.Constants.*;
  import static org.eclipse.jgit.lib.FileMode.REGULAR_FILE;
  import static org.junit.Assert.*;
  
  public abstract class AbstractParallelGitTest {
  
    protected File repoDir;
    protected Repository repo;
    protected DirCache cache;
  
    @Before
    public void setUpCache() {
      cache = DirCache.newInCore();
    }
  
    @After
    public void closeRepository() throws IOException {
      if(repo != null)
        repo.close();
      if(repoDir != null && repoDir.exists())
        FileUtils.delete(repoDir, FileUtils.RECURSIVE);
    }
  
    @Nonnull
    protected ObjectId writeToCache(String path, byte[] content, FileMode mode) throws IOException {
      ObjectId blobId = repo != null ? BlobUtils.insertBlob(content, repo) : calculateBlobId(content);
      if(findEntry(path, cache) >= 0) {
        updateFile(path, content, mode);
      } else {
        addFile(path, mode, blobId, cache);
      }
      return blobId;
    }
  
    @Nonnull
    protected ObjectId writeToCache(String path, byte[] content) throws IOException {
      return writeToCache(path, content, REGULAR_FILE);
    }
  
    @Nonnull
    protected ObjectId writeToCache(String path, String content) throws IOException {
      return writeToCache(path, encode(content));
    }
  
    @Nonnull
    protected ObjectId writeToCache(String path) throws IOException {
      return writeToCache(path, someBytes());
    }
  
    @Nonnull
    protected ObjectId writeSomethingToCache() throws IOException {
      return writeToCache(randomUUID().toString() + ".txt");
    }
  
    protected void writeMultipleToCache(String... paths) throws IOException {
      DirCacheBuilder builder = CacheUtils.keepEverything(cache);
      for(String path : paths) {
        AnyObjectId blobId = repo != null ? BlobUtils.insertBlob(someBytes(), repo) : someObjectId();
        addFile(path, REGULAR_FILE, blobId, builder);
      }
      builder.finish();
    }
  
    @Nonnull
    protected ObjectId updateFile(String path, byte[] content, FileMode mode) throws IOException {
      ObjectId blobId = BlobUtils.insertBlob(content, repo);
      CacheUtils.updateFileBlob(path, blobId, mode, cache);
      return blobId;
    }
  
    @Nonnull
    protected ObjectId updateFile(String path, byte[] content) throws IOException {
      ObjectId blobId = BlobUtils.insertBlob(content, repo);
      CacheUtils.updateFileBlob(path, blobId, cache);
      return blobId;
    }
  
    @Nonnull
    protected ObjectId updateFile(String path, String content) throws IOException {
      return updateFile(path, encode(content));
    }
  
    @Nonnull
    protected static String someText() {
      return randomUUID().toString();
    }
  
    @Nonnull
    protected static String someFilename() {
      return someText() + ".txt";
    }
  
    @Nonnull
    protected static byte[] someBytes() {
      return encodeASCII(someText());
    }
  
    @Nonnull
    protected static ObjectId someObjectId() {
      return calculateBlobId(someBytes());
    }
  
    @Nonnull
    protected static String someCommitMessage() {
      return someText() + " commit: " + someText();
    }
  
    @Nonnull
    protected static PersonIdent somePersonIdent() {
      String name = someText();
      return new PersonIdent(name, name + "@test.com");
    }
  
    @Nonnull
    protected RevCommit commit(String message, @Nullable AnyObjectId parent) throws IOException {
      return CommitUtils.createCommit(message, cache, somePersonIdent(), parent, repo);
    }
  
    @Nonnull
    protected RevCommit commit(@Nullable AnyObjectId parent) throws IOException {
      return commit(someCommitMessage(), parent);
    }
  
    @Nonnull
    protected RevCommit commit() throws IOException {
      return commit(null);
    }
  
    protected void updateBranchHead(String branch, AnyObjectId commit, boolean init) throws IOException {
      if(init)
        BranchUtils.initBranch(branch, commit, repo);
      else
        BranchUtils.newCommit(branch, commit, repo);
    }
  
    @Nonnull
    protected RevCommit commitToBranch(String branch, String message, @Nullable AnyObjectId parent) throws IOException {
      if(parent == null && BranchUtils.branchExists(branch, repo))
        parent = BranchUtils.getHeadCommit(branch, repo);
      RevCommit commitId = commit(message, parent);
      updateBranchHead(branch, commitId, parent == null);
      return commitId;
    }
  
    @Nonnull
    protected RevCommit commitToBranch(String branch, @Nullable AnyObjectId parent) throws IOException {
      return commitToBranch(branch, someCommitMessage(), parent);
    }
  
    @Nonnull
    protected RevCommit commitToBranch(String branch) throws IOException {
      return commitToBranch(branch, someCommitMessage(), null);
    }
  
    @Nonnull
    protected RevCommit commitToMaster(String message, @Nullable AnyObjectId parent) throws IOException {
      return commitToBranch(MASTER, message, parent);
    }
  
    @Nonnull
    protected RevCommit commitToMaster(String message) throws IOException {
      return commitToBranch(MASTER, message, null);
    }
  
    @Nonnull
    protected RevCommit commitToMaster() throws IOException {
      return commitToBranch(MASTER);
    }
  
    protected void clearCache() {
      cache = DirCache.newInCore();
    }
  
    protected void initRepositoryDir() throws IOException {
      if(repoDir == null)
        repoDir = FileUtils.createTempDir(getClass().getSimpleName(), null, null);
    }
  
    @Nonnull
    protected RevCommit initContent() throws IOException {
      RevCommit head = commitToMaster();
      clearCache();
      return head;
    }
  
    @Nonnull
    protected RevCommit initRepository(boolean memory, boolean bare) throws IOException {
      if(!memory)
        initRepositoryDir();
      repo = memory ? new TestRepository(bare) : RepositoryUtils.createRepository(repoDir, bare);
      return initContent();
    }
  
    @Nonnull
    protected RevCommit initFileRepository(boolean bare) throws IOException {
      return initRepository(false, bare);
    }
  
    @Nonnull
    protected RevCommit initMemoryRepository(boolean bare) throws IOException {
      return initRepository(true, bare);
    }
  
    @Nonnull
    protected RevCommit initRepository() throws IOException {
      return initRepository(true, true);
    }
  
    @Nonnull
    public static ObjectId calculateBlobId(byte[] data) {
      return new ObjectInserter.Formatter().idFor(OBJ_BLOB, data);
    }
  
    public static void assertCacheEquals(@Nullable String message, DirCache expected, DirCache actual) {
      if(expected != actual) {
        String header = message == null ? "" : message + ": ";
        int cacheSize = assertCacheSameSize(expected, actual, header);
        DirCacheEntry[] expectedEntries = expected.getEntriesWithin("");
        DirCacheEntry[] actualEntries = actual.getEntriesWithin("");
        for(int i = 0; i < cacheSize; ++i) {
          DirCacheEntry expectedEntry = expectedEntries[i];
          DirCacheEntry actualEntry = actualEntries[i];
          assertCacheEntryEquals(expectedEntry, actualEntry, header, i);
        }
      }
    }
  
    public static void assertCacheEquals(DirCache expected, DirCache actual) {
      assertCacheEquals(null, expected, actual);
    }
  
    private static int assertCacheSameSize(DirCache expected, DirCache actual, String header) {
      int actualSize = actual.getEntryCount();
      int expectedSize = expected.getEntryCount();
      if(actualSize != expectedSize)
        fail(header + "cache sizes differed, expected.size=" + expectedSize + " actual.size=" + actualSize);
      return expectedSize;
    }
  
    private static void assertCacheEntryEquals(DirCacheEntry expected, DirCacheEntry actual, String header, int index) {
      try {
        assertEquals("fileMode", expected.getFileMode(), actual.getFileMode());
        assertEquals("length", expected.getLength(), actual.getLength());
        assertEquals("objectId", expected.getObjectId(), actual.getObjectId());
        assertEquals("stage", expected.getStage(), actual.getStage());
        assertEquals("path", expected.getPathString(), actual.getPathString());
      } catch(AssertionError e) {
        fail(header + "caches first differed at entry [" + index + "]; " + e.getMessage());
      }
    }
  
    protected static class TestRepository extends InMemoryRepository {
  
      private final File directory;
      private final File workTree;
  
      public TestRepository(boolean bare) {
        super(new DfsRepositoryDescription());
        File mockLocation = new File(System.getProperty("java.io.tmpdir"));
        directory = bare ? mockLocation : new File(mockLocation, DOT_GIT);
        workTree = bare ? null : mockLocation;
      }
  
      public TestRepository() {
        this(true);
      }
  
      @Override
      public boolean isBare() {
        return workTree == null;
      }
  
      @Nonnull
      @Override
      public File getWorkTree() throws NoWorkTreeException {
        if(workTree == null)
          throw new NoWorkTreeException();
        return workTree;
      }
  
      @Nonnull
      @Override
      public File getDirectory() {
        return directory;
      }
  
    }
  
  }
  