Merge pull request #27 from winstonli/empty_file_in_dotgit

Empty file in dotgit: direct response to Git Bridge Fails to Clone With IllegalStateException (#3705)
This commit is contained in:
Winston Li
2017-08-03 16:21:09 +01:00
committed by GitHub
9 changed files with 144 additions and 33 deletions
@@ -76,13 +76,13 @@ public class FSGitRepoStore implements RepoStore {
String projectName, String projectName,
long[] sizePtr long[] sizePtr
) throws IOException { ) throws IOException {
Preconditions.checkArgument(Project.isValidProjectName(projectName)); Project.checkValidProjectName(projectName);
return Tar.bz2.zip(getDotGitForProject(projectName), sizePtr); return Tar.bz2.zip(getDotGitForProject(projectName), sizePtr);
} }
@Override @Override
public void remove(String projectName) throws IOException { public void remove(String projectName) throws IOException {
Preconditions.checkArgument(Project.isValidProjectName(projectName)); Project.checkValidProjectName(projectName);
FileUtils.deleteDirectory(new File(rootDirectory, projectName)); FileUtils.deleteDirectory(new File(rootDirectory, projectName));
} }
@@ -91,13 +91,22 @@ public class FSGitRepoStore implements RepoStore {
String projectName, String projectName,
InputStream dataStream InputStream dataStream
) throws IOException { ) throws IOException {
Preconditions.checkArgument(Project.isValidProjectName(projectName)); Preconditions.checkArgument(
Preconditions.checkState(getDirForProject(projectName).mkdirs()); Project.isValidProjectName(projectName),
"[%s] invalid project name: ",
projectName
);
Preconditions.checkState(
getDirForProject(projectName).mkdirs(),
"[%s] directories for " +
"evicted project already exist",
projectName
);
Tar.bz2.unzip(dataStream, getDirForProject(projectName)); Tar.bz2.unzip(dataStream, getDirForProject(projectName));
} }
private File getDirForProject(String projectName) { private File getDirForProject(String projectName) {
Preconditions.checkArgument(Project.isValidProjectName(projectName)); Project.checkValidProjectName(projectName);
return Paths.get( return Paths.get(
rootDirectory.getAbsolutePath() rootDirectory.getAbsolutePath()
).resolve( ).resolve(
@@ -106,7 +115,7 @@ public class FSGitRepoStore implements RepoStore {
} }
private File getDotGitForProject(String projectName) { private File getDotGitForProject(String projectName) {
Preconditions.checkArgument(Project.isValidProjectName(projectName)); Project.checkValidProjectName(projectName);
return Paths.get( return Paths.get(
rootDirectory.getAbsolutePath() rootDirectory.getAbsolutePath()
).resolve( ).resolve(
@@ -119,7 +128,12 @@ public class FSGitRepoStore implements RepoStore {
private File initRootGitDirectory(String rootGitDirectoryPath) { private File initRootGitDirectory(String rootGitDirectoryPath) {
File rootGitDirectory = new File(rootGitDirectoryPath); File rootGitDirectory = new File(rootGitDirectoryPath);
rootGitDirectory.mkdirs(); rootGitDirectory.mkdirs();
Preconditions.checkArgument(rootGitDirectory.isDirectory()); Preconditions.checkArgument(
rootGitDirectory.isDirectory(),
"given root git directory " +
"is not a directory: %s",
rootGitDirectory.getAbsolutePath()
);
return rootGitDirectory; return rootGitDirectory;
} }
@@ -158,7 +158,7 @@ public class SwapJobImpl implements SwapJob {
*/ */
@Override @Override
public void evict(String projName) throws IOException { public void evict(String projName) throws IOException {
Preconditions.checkNotNull(projName); Preconditions.checkNotNull(projName, "projName was null");
Log.info("Evicting project: {}", projName); Log.info("Evicting project: {}", projName);
try (LockGuard __ = lock.lockGuard(projName)) { try (LockGuard __ = lock.lockGuard(projName)) {
long[] sizePtr = new long[1]; long[] sizePtr = new long[1];
@@ -0,0 +1,11 @@
package uk.ac.ic.wlgitbridge.util;
/**
* BiConsumer interface that allows checked exceptions.
*/
@FunctionalInterface
public interface BiConsumerT<T, U, E extends Throwable> {
void accept(T t, U u) throws E;
}
@@ -0,0 +1,14 @@
package uk.ac.ic.wlgitbridge.util;
/**
* Function interface that allows checked exceptions.
* @param <T>
* @param <R>
* @param <E>
*/
@FunctionalInterface
public interface FunctionT<T, R, E extends Throwable> {
R apply(T t) throws E;
}
@@ -1,5 +1,7 @@
package uk.ac.ic.wlgitbridge.util; package uk.ac.ic.wlgitbridge.util;
import com.google.common.base.Preconditions;
/** /**
* Created by winston on 23/08/2016. * Created by winston on 23/08/2016.
*/ */
@@ -10,4 +12,9 @@ public class Project {
&& !projectName.startsWith("."); && !projectName.startsWith(".");
} }
public static void checkValidProjectName(String projectName) {
Preconditions.checkArgument(isValidProjectName(projectName),
"[%s] invalid project name", projectName);
}
} }
@@ -0,0 +1,32 @@
package uk.ac.ic.wlgitbridge.util;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
public class ResourceUtil {
/**
* Creates a copy of a resource folder. Mainly used for testing to prevent
* the original folder from being mangled.
*
* It will have the same name as the original.
* @param resource the resource name, e.g. "/uk/ac/ic/wlgitbridge/file.txt"
* @param folderProvider function used to create the folder.
* E.g. TemporaryFolder from junit
* @return
* @throws IOException
*/
public static File copyOfFolderResource(
String resource,
FunctionT<String, File, IOException> folderProvider
) throws IOException {
File original
= new File(ResourceUtil.class.getResource(resource).getFile());
File tmp = folderProvider.apply(original.getName());
FileUtils.copyDirectory(original, tmp);
return tmp;
}
}
@@ -100,10 +100,7 @@ public class Tar {
continue; continue;
} }
long size = e.getSize(); long size = e.getSize();
Preconditions.checkArgument( checkFileSize(size);
size > 0 && size < Integer.MAX_VALUE,
"file too big: tarTo should have thrown an IOException"
);
try (OutputStream out = new FileOutputStream(f)) { try (OutputStream out = new FileOutputStream(f)) {
/* TarInputStream pretends each /* TarInputStream pretends each
entry's EOF is the stream's EOF */ entry's EOF is the stream's EOF */
@@ -112,6 +109,14 @@ public class Tar {
} }
} }
private static void checkFileSize(long size) {
Preconditions.checkArgument(
size >= 0 && size <= Integer.MAX_VALUE,
"file too big (" + size + " B): " +
"tarTo should have thrown an IOException"
);
}
private static void addTarEntry( private static void addTarEntry(
TarArchiveOutputStream tout, TarArchiveOutputStream tout,
Path base, Path base,
@@ -150,13 +155,19 @@ public class Tar {
Path base, Path base,
File file File file
) throws IOException { ) throws IOException {
Preconditions.checkArgument(file.isFile()); Preconditions.checkArgument(
file.isFile(),
"given file" +
" is not file: %s", file);
checkFileSize(file.length());
String name = base.relativize( String name = base.relativize(
Paths.get(file.getAbsolutePath()) Paths.get(file.getAbsolutePath())
).toString(); ).toString();
ArchiveEntry entry = tout.createArchiveEntry(file, name); ArchiveEntry entry = tout.createArchiveEntry(file, name);
tout.putArchiveEntry(entry); tout.putArchiveEntry(entry);
tout.write(FileUtils.readFileToByteArray(file)); try (InputStream in = new FileInputStream(file)) {
IOUtils.copy(in, tout);
}
tout.closeArchiveEntry(); tout.closeArchiveEntry();
} }
@@ -1,6 +1,5 @@
package uk.ac.ic.wlgitbridge.util; package uk.ac.ic.wlgitbridge.util;
import org.apache.commons.io.FileUtils;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
@@ -8,8 +7,6 @@ import org.junit.rules.TemporaryFolder;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -18,37 +15,62 @@ import static org.junit.Assert.assertTrue;
*/ */
public class TarTest { public class TarTest {
private static final String RESOURCE_DIR
= "/uk/ac/ic/wlgitbridge/util/TarTest";
private File testDir; private File testDir;
private File dirWithEmptyFile;
private File tmpDir; private File tmpDir;
@Before @Before
public void setup() throws IOException { public void setup() throws IOException {
TemporaryFolder tmpFolder = new TemporaryFolder(); TemporaryFolder tmpFolder = new TemporaryFolder();
tmpFolder.create(); tmpFolder.create();
testDir = tmpFolder.newFolder("testdir"); testDir = ResourceUtil.copyOfFolderResource(
Path resdir = Paths.get( RESOURCE_DIR + "/testdir",
"src/test/resources/uk/ac/ic/wlgitbridge/util/TarTest/testdir" tmpFolder::newFolder);
); dirWithEmptyFile = ResourceUtil.copyOfFolderResource(
FileUtils.copyDirectory(resdir.toFile(), testDir); RESOURCE_DIR + "/dir_with_empty_file",
tmpFolder::newFolder);
tmpDir = tmpFolder.newFolder(); tmpDir = tmpFolder.newFolder();
} }
/**
* Compresses inputDir and decompresses to outputDir. Checks equality
* between outputDir and inputDir.
* @param inputDir the directory to compress
* @param outputDir the output directory. Must be empty.
* @param compressFunction compression function
* @param decompressFunction decompression function
* @throws IOException
*/
private static void assertCompDecompEqual(
File inputDir,
File outputDir,
FunctionT<File, InputStream, IOException> compressFunction,
BiConsumerT<InputStream, File, IOException> decompressFunction
) throws IOException {
try (InputStream tarbz2 = compressFunction.apply(inputDir)) {
decompressFunction.accept(tarbz2, outputDir);
File unzipped = new File(outputDir, inputDir.getName());
assertTrue(Files.contentsAreEqual(inputDir, unzipped));
}
}
@Test @Test
public void tarAndUntarProducesTheSameResult() throws IOException { public void tarAndUntarProducesTheSameResult() throws IOException {
try (InputStream tar = Tar.tar(testDir)) { assertCompDecompEqual(testDir, tmpDir, Tar::tar, Tar::untar);
Tar.untar(tar, tmpDir);
File untarred = new File(tmpDir, "testdir");
assertTrue(Files.contentsAreEqual(testDir, untarred));
}
} }
@Test @Test
public void tarbz2AndUntarbz2ProducesTheSameResult() throws IOException { public void tarbz2AndUntarbz2ProducesTheSameResult() throws IOException {
try (InputStream tarbz2 = Tar.bz2.zip(testDir)) { assertCompDecompEqual(testDir, tmpDir, Tar.bz2::zip, Tar.bz2::unzip);
Tar.bz2.unzip(tarbz2, tmpDir);
File unzipped = new File(tmpDir, "testdir");
assertTrue(Files.contentsAreEqual(testDir, unzipped));
}
} }
} @Test
public void tarbz2WorksOnDirectoriesWithAnEmptyFile() throws IOException {
assertCompDecompEqual(
dirWithEmptyFile, tmpDir, Tar.bz2::zip, Tar.bz2::unzip);
}
}