blob: 490c45b5582c89325aedb9f2d45750cae97ceb45 [file] [log] [blame]
/*
* Copyright (C) 2023 Thomas Wolf <twolf@apache.org> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.symlinks;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ResetCommand.ResetType;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.patch.Patch;
import org.eclipse.jgit.patch.PatchApplier;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class DirectoryTest extends RepositoryTestCase {
@BeforeClass
public static void checkPrecondition() throws Exception {
Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
Path tempDir = Files.createTempDirectory("jgit");
try {
Path a = tempDir.resolve("a");
Files.writeString(a, "test");
Path b = tempDir.resolve("A");
Assume.assumeTrue(Files.exists(b));
} finally {
FileUtils.delete(tempDir.toFile(),
FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS);
}
}
@Parameters(name = "core.symlinks={0}")
public static Boolean[] parameters() {
return new Boolean[] { Boolean.TRUE, Boolean.FALSE };
}
@Parameter(0)
public boolean useSymlinks;
private void checkFiles() throws Exception {
File a = new File(trash, "a");
assertTrue("a should be a directory",
Files.isDirectory(a.toPath(), LinkOption.NOFOLLOW_LINKS));
File b = new File(a, "b");
assertTrue("a/b should exist", b.isFile());
File x = new File(trash, "x");
assertTrue("x should be a directory",
Files.isDirectory(x.toPath(), LinkOption.NOFOLLOW_LINKS));
File y = new File(x, "y");
assertTrue("x/y should exist", y.isFile());
}
@Test
public void testCheckout() throws Exception {
StoredConfig config = db.getConfig();
config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
ConfigConstants.CONFIG_KEY_SYMLINKS, useSymlinks);
config.save();
try (TestRepository<Repository> repo = new TestRepository<>(db)) {
db.incrementOpen();
// Create links directly in the git repo, then use a hard reset
// to get them into the workspace.
RevCommit base = repo.commit(
repo.tree(
repo.link("A", repo.blob(".git")),
repo.file("a/b", repo.blob("test")),
repo.file("x/y", repo.blob("test2"))));
try (Git git = new Git(db)) {
git.reset().setMode(ResetType.HARD).setRef(base.name()).call();
File b = new File(new File(trash, ".git"), "b");
assertFalse(".git/b should not exist", b.exists());
checkFiles();
}
}
}
@Test
public void testCheckout2() throws Exception {
StoredConfig config = db.getConfig();
config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
ConfigConstants.CONFIG_KEY_SYMLINKS, useSymlinks);
config.save();
try (TestRepository<Repository> repo = new TestRepository<>(db)) {
db.incrementOpen();
RevCommit base = repo.commit(
repo.tree(
repo.link("A/B", repo.blob("../.git")),
repo.file("a/b/a/b", repo.blob("test")),
repo.file("x/y", repo.blob("test2"))));
try (Git git = new Git(db)) {
boolean testFiles = true;
try {
git.reset().setMode(ResetType.HARD).setRef(base.name())
.call();
} catch (Exception e) {
if (!useSymlinks) {
// There is a file in the middle of the path where we'd
// expect a directory. This case is not handled
// anywhere. What would be a better reply than an IOE?
testFiles = false;
} else {
throw e;
}
}
File a = new File(new File(trash, ".git"), "a");
assertFalse(".git/a should not exist", a.exists());
if (testFiles) {
a = new File(trash, "a");
assertTrue("a should be a directory", Files.isDirectory(
a.toPath(), LinkOption.NOFOLLOW_LINKS));
File b = new File(a, "b");
assertTrue("a/b should be a directory", Files.isDirectory(
a.toPath(), LinkOption.NOFOLLOW_LINKS));
a = new File(b, "a");
assertTrue("a/b/a should be a directory", Files.isDirectory(
a.toPath(), LinkOption.NOFOLLOW_LINKS));
b = new File(a, "b");
assertTrue("a/b/a/b should exist", b.isFile());
File x = new File(trash, "x");
assertTrue("x should be a directory", Files.isDirectory(
x.toPath(), LinkOption.NOFOLLOW_LINKS));
File y = new File(x, "y");
assertTrue("x/y should exist", y.isFile());
}
}
}
}
@Test
public void testMerge() throws Exception {
StoredConfig config = db.getConfig();
config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
ConfigConstants.CONFIG_KEY_SYMLINKS, useSymlinks);
config.save();
try (TestRepository<Repository> repo = new TestRepository<>(db)) {
db.incrementOpen();
RevCommit base = repo.commit(
repo.tree(repo.file("q", repo.blob("test"))));
RevCommit side = repo.commit(
repo.tree(
repo.link("A", repo.blob(".git")),
repo.file("a/b", repo.blob("test")),
repo.file("x/y", repo.blob("test2"))));
try (Git git = new Git(db)) {
git.reset().setMode(ResetType.HARD).setRef(base.name()).call();
git.merge().include(side)
.setMessage("merged").call();
File b = new File(new File(trash, ".git"), "b");
assertFalse(".git/b should not exist", b.exists());
checkFiles();
}
}
}
@Test
public void testMerge2() throws Exception {
StoredConfig config = db.getConfig();
config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
ConfigConstants.CONFIG_KEY_SYMLINKS, useSymlinks);
config.save();
try (TestRepository<Repository> repo = new TestRepository<>(db)) {
db.incrementOpen();
RevCommit base = repo.commit(
repo.tree(
repo.file("q", repo.blob("test")),
repo.link("A", repo.blob(".git"))));
RevCommit side = repo.commit(
repo.tree(
repo.file("a/b", repo.blob("test")),
repo.file("x/y", repo.blob("test2"))));
try (Git git = new Git(db)) {
git.reset().setMode(ResetType.HARD).setRef(base.name()).call();
git.merge().include(side)
.setMessage("merged").call();
File b = new File(new File(trash, ".git"), "b");
assertFalse(".git/b should not exist", b.exists());
checkFiles();
}
}
}
@Test
public void testApply() throws Exception {
StoredConfig config = db.getConfig();
config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
ConfigConstants.CONFIG_KEY_SYMLINKS, useSymlinks);
config.save();
// PatchApplier doesn't do symlinks yet.
try (TestRepository<Repository> repo = new TestRepository<>(db)) {
db.incrementOpen();
RevCommit base = repo.commit(
repo.tree(
repo.file("x", repo.blob("test")),
repo.link("A", repo.blob(".git"))));
try (Git git = new Git(db)) {
git.reset().setMode(ResetType.HARD).setRef(base.name()).call();
Patch patch = new Patch();
try (InputStream patchStream = this.getClass()
.getResourceAsStream("dirtest.patch")) {
patch.parse(patchStream);
}
boolean testFiles = true;
try {
PatchApplier.Result result = new PatchApplier(db)
.applyPatch(patch);
assertNotNull(result);
} catch (IOException e) {
if (!useSymlinks) {
// There is a file there, so the patch won't apply.
// Unclear whether an IOE is the correct response,
// though. Probably some negative PatchApplier.Result is
// more appropriate.
testFiles = false;
} else {
throw e;
}
}
File b = new File(new File(trash, ".git"), "b");
assertFalse(".git/b should not exist", b.exists());
if (testFiles) {
File a = new File(trash, "a");
assertTrue("a should be a directory",
Files.isDirectory(a.toPath(), LinkOption.NOFOLLOW_LINKS));
b = new File(a, "b");
assertTrue("a/b should exist", b.isFile());
}
}
}
}
}