Merge branch 'stable-6.6' into stable-6.7
* stable-6.6: Prepare 6.6.2-SNAPSHOT builds JGit v6.6.1.202309021850-r Checkout: better directory handling Change-Id: Ice82d68b2d343a5fac214807cdb369e486481aab
This commit is contained in:
commit
7a6e852745
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
*.patch -crlf
|
Binary file not shown.
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2009, 2020 Google Inc. and others
|
* Copyright (C) 2009, 2023 Google Inc. and others
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available under the
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||||
|
@ -46,6 +46,16 @@ public void testIsValidPath() {
|
||||||
assertFalse(isValidPath("a/"));
|
assertFalse(isValidPath("a/"));
|
||||||
assertFalse(isValidPath("ab/cd/ef/"));
|
assertFalse(isValidPath("ab/cd/ef/"));
|
||||||
assertFalse(isValidPath("a\u0000b"));
|
assertFalse(isValidPath("a\u0000b"));
|
||||||
|
assertFalse(isValidPath(".git"));
|
||||||
|
assertFalse(isValidPath(".GIT"));
|
||||||
|
assertFalse(isValidPath(".Git"));
|
||||||
|
assertFalse(isValidPath(".git/b"));
|
||||||
|
assertFalse(isValidPath(".GIT/b"));
|
||||||
|
assertFalse(isValidPath(".Git/b"));
|
||||||
|
assertFalse(isValidPath("x/y/.git/z/b"));
|
||||||
|
assertFalse(isValidPath("x/y/.GIT/z/b"));
|
||||||
|
assertFalse(isValidPath("x/y/.Git/z/b"));
|
||||||
|
assertTrue(isValidPath("git/b"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* 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.dircache;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
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.FileMode;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.revwalk.RevBlob;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for checking out with invalid paths.
|
||||||
|
*/
|
||||||
|
public class InvalidPathCheckoutTest extends RepositoryTestCase {
|
||||||
|
|
||||||
|
private DirCacheEntry brokenEntry(String fileName, RevBlob blob) {
|
||||||
|
DirCacheEntry entry = new DirCacheEntry("XXXX/" + fileName);
|
||||||
|
entry.path[0] = '.';
|
||||||
|
entry.path[1] = 'g';
|
||||||
|
entry.path[2] = 'i';
|
||||||
|
entry.path[3] = 't';
|
||||||
|
entry.setFileMode(FileMode.REGULAR_FILE);
|
||||||
|
entry.setObjectId(blob);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckoutIntoDotGit() throws Exception {
|
||||||
|
try (TestRepository<Repository> repo = new TestRepository<>(db)) {
|
||||||
|
db.incrementOpen();
|
||||||
|
// DirCacheEntry does not allow any path component to contain
|
||||||
|
// ".git". C git also forbids this. But what if somebody creates
|
||||||
|
// such an entry explicitly?
|
||||||
|
RevCommit base = repo
|
||||||
|
.commit(repo.tree(brokenEntry("b", repo.blob("test"))));
|
||||||
|
try (Git git = new Git(db)) {
|
||||||
|
assertThrows(InvalidPathException.class, () -> 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -24,6 +24,7 @@
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
|
||||||
import org.eclipse.jgit.annotations.Nullable;
|
import org.eclipse.jgit.annotations.Nullable;
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.attributes.FilterCommand;
|
import org.eclipse.jgit.attributes.FilterCommand;
|
||||||
|
@ -892,5 +893,30 @@ public void testFiltering() throws Exception {
|
||||||
FilterCommandRegistry.unregister("jgit://builtin/a2e/smudge");
|
FilterCommandRegistry.unregister("jgit://builtin/a2e/smudge");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void dotGitTest(String fileName) throws Exception {
|
||||||
|
init(fileName, false, false);
|
||||||
|
Result result = null;
|
||||||
|
IOException ex = null;
|
||||||
|
try {
|
||||||
|
result = applyPatch();
|
||||||
|
} catch (IOException e) {
|
||||||
|
ex = e;
|
||||||
|
}
|
||||||
|
assertTrue(ex != null
|
||||||
|
|| (result != null && !result.getErrors().isEmpty()));
|
||||||
|
File b = new File(new File(trash, ".git"), "b");
|
||||||
|
assertFalse(".git/b should not exist", b.exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDotGit() throws Exception {
|
||||||
|
dotGitTest("dotgit");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDotGit2() throws Exception {
|
||||||
|
dotGitTest("dotgit2");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,259 @@
|
||||||
|
/*
|
||||||
|
* 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,13 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<component id="org.eclipse.jgit" version="2">
|
<component id="org.eclipse.jgit" version="2">
|
||||||
|
<resource path="src/org/eclipse/jgit/dircache/Checkout.java" type="org.eclipse.jgit.dircache.Checkout">
|
||||||
|
<filter id="1109393411">
|
||||||
|
<message_arguments>
|
||||||
|
<message_argument value="6.6.1"/>
|
||||||
|
<message_argument value="org.eclipse.jgit.dircache.Checkout"/>
|
||||||
|
</message_arguments>
|
||||||
|
</filter>
|
||||||
|
</resource>
|
||||||
<resource path="src/org/eclipse/jgit/lib/Constants.java" type="org.eclipse.jgit.lib.Constants">
|
<resource path="src/org/eclipse/jgit/lib/Constants.java" type="org.eclipse.jgit.lib.Constants">
|
||||||
<filter id="388100214">
|
<filter id="388100214">
|
||||||
<message_arguments>
|
<message_arguments>
|
||||||
|
@ -14,4 +22,12 @@
|
||||||
</message_arguments>
|
</message_arguments>
|
||||||
</filter>
|
</filter>
|
||||||
</resource>
|
</resource>
|
||||||
|
<resource path="src/org/eclipse/jgit/lib/FileModeCache.java" type="org.eclipse.jgit.lib.FileModeCache">
|
||||||
|
<filter id="1109393411">
|
||||||
|
<message_arguments>
|
||||||
|
<message_argument value="6.6.1"/>
|
||||||
|
<message_argument value="org.eclipse.jgit.lib.FileModeCache"/>
|
||||||
|
</message_arguments>
|
||||||
|
</filter>
|
||||||
|
</resource>
|
||||||
</component>
|
</component>
|
||||||
|
|
|
@ -20,6 +20,8 @@ applyBinaryPatchTypeNotSupported=Couldn't apply binary patch of type {0}
|
||||||
applyTextPatchCannotApplyHunk=Hunk cannot be applied
|
applyTextPatchCannotApplyHunk=Hunk cannot be applied
|
||||||
applyTextPatchSingleClearingHunk=Expected a single hunk for clearing all content
|
applyTextPatchSingleClearingHunk=Expected a single hunk for clearing all content
|
||||||
applyBinaryResultOidWrong=Result of binary patch for file {0} has wrong OID
|
applyBinaryResultOidWrong=Result of binary patch for file {0} has wrong OID
|
||||||
|
applyPatchDestInvalid=Destination path in patch is invalid
|
||||||
|
applyPatchSourceInvalid==Source path in patch is invalid
|
||||||
applyPatchWithoutSourceOnAlreadyExistingSource=Cannot perform {0} action on an existing file
|
applyPatchWithoutSourceOnAlreadyExistingSource=Cannot perform {0} action on an existing file
|
||||||
applyPatchWithCreationOverAlreadyExistingDestination=Cannot perform {0} action which overrides an existing file
|
applyPatchWithCreationOverAlreadyExistingDestination=Cannot perform {0} action which overrides an existing file
|
||||||
applyPatchWithSourceOnNonExistentSource=Cannot perform {0} action on a non-existent file
|
applyPatchWithSourceOnNonExistentSource=Cannot perform {0} action on a non-existent file
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com>
|
* Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com>
|
||||||
* Copyright (C) 2011, 2020 Matthias Sohn <matthias.sohn@sap.com> and others
|
* Copyright (C) 2011, 2023 Matthias Sohn <matthias.sohn@sap.com> and others
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available under the
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||||
|
@ -28,6 +28,7 @@
|
||||||
import org.eclipse.jgit.api.errors.JGitInternalException;
|
import org.eclipse.jgit.api.errors.JGitInternalException;
|
||||||
import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
|
import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
|
||||||
import org.eclipse.jgit.api.errors.RefNotFoundException;
|
import org.eclipse.jgit.api.errors.RefNotFoundException;
|
||||||
|
import org.eclipse.jgit.dircache.Checkout;
|
||||||
import org.eclipse.jgit.dircache.DirCache;
|
import org.eclipse.jgit.dircache.DirCache;
|
||||||
import org.eclipse.jgit.dircache.DirCacheCheckout;
|
import org.eclipse.jgit.dircache.DirCacheCheckout;
|
||||||
import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
|
import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
|
||||||
|
@ -55,7 +56,6 @@
|
||||||
import org.eclipse.jgit.revwalk.RevTree;
|
import org.eclipse.jgit.revwalk.RevTree;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
|
|
||||||
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
|
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -413,8 +413,7 @@ public CheckoutCommand setAllPaths(boolean all) {
|
||||||
protected CheckoutCommand checkoutPaths() throws IOException,
|
protected CheckoutCommand checkoutPaths() throws IOException,
|
||||||
RefNotFoundException {
|
RefNotFoundException {
|
||||||
actuallyModifiedPaths = new HashSet<>();
|
actuallyModifiedPaths = new HashSet<>();
|
||||||
WorkingTreeOptions options = repo.getConfig()
|
Checkout checkout = new Checkout(repo).setRecursiveDeletion(true);
|
||||||
.get(WorkingTreeOptions.KEY);
|
|
||||||
DirCache dc = repo.lockDirCache();
|
DirCache dc = repo.lockDirCache();
|
||||||
try (RevWalk revWalk = new RevWalk(repo);
|
try (RevWalk revWalk = new RevWalk(repo);
|
||||||
TreeWalk treeWalk = new TreeWalk(repo,
|
TreeWalk treeWalk = new TreeWalk(repo,
|
||||||
|
@ -423,10 +422,10 @@ protected CheckoutCommand checkoutPaths() throws IOException,
|
||||||
if (!checkoutAllPaths)
|
if (!checkoutAllPaths)
|
||||||
treeWalk.setFilter(PathFilterGroup.createFromStrings(paths));
|
treeWalk.setFilter(PathFilterGroup.createFromStrings(paths));
|
||||||
if (isCheckoutIndex())
|
if (isCheckoutIndex())
|
||||||
checkoutPathsFromIndex(treeWalk, dc, options);
|
checkoutPathsFromIndex(treeWalk, dc, checkout);
|
||||||
else {
|
else {
|
||||||
RevCommit commit = revWalk.parseCommit(getStartPointObjectId());
|
RevCommit commit = revWalk.parseCommit(getStartPointObjectId());
|
||||||
checkoutPathsFromCommit(treeWalk, dc, commit, options);
|
checkoutPathsFromCommit(treeWalk, dc, commit, checkout);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
|
@ -444,7 +443,7 @@ protected CheckoutCommand checkoutPaths() throws IOException,
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkoutPathsFromIndex(TreeWalk treeWalk, DirCache dc,
|
private void checkoutPathsFromIndex(TreeWalk treeWalk, DirCache dc,
|
||||||
WorkingTreeOptions options)
|
Checkout checkout)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
DirCacheIterator dci = new DirCacheIterator(dc);
|
DirCacheIterator dci = new DirCacheIterator(dc);
|
||||||
treeWalk.addTree(dci);
|
treeWalk.addTree(dci);
|
||||||
|
@ -470,7 +469,7 @@ public void apply(DirCacheEntry ent) {
|
||||||
if (stage > DirCacheEntry.STAGE_0) {
|
if (stage > DirCacheEntry.STAGE_0) {
|
||||||
if (checkoutStage != null) {
|
if (checkoutStage != null) {
|
||||||
if (stage == checkoutStage.number) {
|
if (stage == checkoutStage.number) {
|
||||||
checkoutPath(ent, r, options,
|
checkoutPath(ent, r, checkout, path,
|
||||||
new CheckoutMetadata(eolStreamType,
|
new CheckoutMetadata(eolStreamType,
|
||||||
filterCommand));
|
filterCommand));
|
||||||
actuallyModifiedPaths.add(path);
|
actuallyModifiedPaths.add(path);
|
||||||
|
@ -481,7 +480,7 @@ public void apply(DirCacheEntry ent) {
|
||||||
throw new JGitInternalException(e.getMessage(), e);
|
throw new JGitInternalException(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
checkoutPath(ent, r, options,
|
checkoutPath(ent, r, checkout, path,
|
||||||
new CheckoutMetadata(eolStreamType,
|
new CheckoutMetadata(eolStreamType,
|
||||||
filterCommand));
|
filterCommand));
|
||||||
actuallyModifiedPaths.add(path);
|
actuallyModifiedPaths.add(path);
|
||||||
|
@ -495,7 +494,7 @@ public void apply(DirCacheEntry ent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkoutPathsFromCommit(TreeWalk treeWalk, DirCache dc,
|
private void checkoutPathsFromCommit(TreeWalk treeWalk, DirCache dc,
|
||||||
RevCommit commit, WorkingTreeOptions options) throws IOException {
|
RevCommit commit, Checkout checkout) throws IOException {
|
||||||
treeWalk.addTree(commit.getTree());
|
treeWalk.addTree(commit.getTree());
|
||||||
final ObjectReader r = treeWalk.getObjectReader();
|
final ObjectReader r = treeWalk.getObjectReader();
|
||||||
DirCacheEditor editor = dc.editor();
|
DirCacheEditor editor = dc.editor();
|
||||||
|
@ -517,7 +516,7 @@ public void apply(DirCacheEntry ent) {
|
||||||
}
|
}
|
||||||
ent.setObjectId(blobId);
|
ent.setObjectId(blobId);
|
||||||
ent.setFileMode(mode);
|
ent.setFileMode(mode);
|
||||||
checkoutPath(ent, r, options,
|
checkoutPath(ent, r, checkout, path,
|
||||||
new CheckoutMetadata(eolStreamType, filterCommand));
|
new CheckoutMetadata(eolStreamType, filterCommand));
|
||||||
actuallyModifiedPaths.add(path);
|
actuallyModifiedPaths.add(path);
|
||||||
}
|
}
|
||||||
|
@ -527,10 +526,9 @@ public void apply(DirCacheEntry ent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
|
private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
|
||||||
WorkingTreeOptions options, CheckoutMetadata checkoutMetadata) {
|
Checkout checkout, String path, CheckoutMetadata checkoutMetadata) {
|
||||||
try {
|
try {
|
||||||
DirCacheCheckout.checkoutEntry(repo, entry, reader, true,
|
checkout.checkout(entry, checkoutMetadata, reader, path);
|
||||||
checkoutMetadata, options);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new JGitInternalException(MessageFormat.format(
|
throw new JGitInternalException(MessageFormat.format(
|
||||||
JGitText.get().checkoutConflictWithFile,
|
JGitText.get().checkoutConflictWithFile,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2012, 2021 GitHub Inc. and others
|
* Copyright (C) 2012, 2023 GitHub Inc. and others
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available under the
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||||
|
@ -23,6 +23,7 @@
|
||||||
import org.eclipse.jgit.api.errors.NoHeadException;
|
import org.eclipse.jgit.api.errors.NoHeadException;
|
||||||
import org.eclipse.jgit.api.errors.StashApplyFailureException;
|
import org.eclipse.jgit.api.errors.StashApplyFailureException;
|
||||||
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
|
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
|
||||||
|
import org.eclipse.jgit.dircache.Checkout;
|
||||||
import org.eclipse.jgit.dircache.DirCache;
|
import org.eclipse.jgit.dircache.DirCache;
|
||||||
import org.eclipse.jgit.dircache.DirCacheBuilder;
|
import org.eclipse.jgit.dircache.DirCacheBuilder;
|
||||||
import org.eclipse.jgit.dircache.DirCacheCheckout;
|
import org.eclipse.jgit.dircache.DirCacheCheckout;
|
||||||
|
@ -48,7 +49,6 @@
|
||||||
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
|
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
|
||||||
import org.eclipse.jgit.treewalk.FileTreeIterator;
|
import org.eclipse.jgit.treewalk.FileTreeIterator;
|
||||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command class to apply a stashed commit.
|
* Command class to apply a stashed commit.
|
||||||
|
@ -383,8 +383,7 @@ private void resetIndex(RevTree tree) throws IOException {
|
||||||
private void resetUntracked(RevTree tree) throws CheckoutConflictException,
|
private void resetUntracked(RevTree tree) throws CheckoutConflictException,
|
||||||
IOException {
|
IOException {
|
||||||
Set<String> actuallyModifiedPaths = new HashSet<>();
|
Set<String> actuallyModifiedPaths = new HashSet<>();
|
||||||
WorkingTreeOptions options = repo.getConfig()
|
Checkout checkout = new Checkout(repo).setRecursiveDeletion(true);
|
||||||
.get(WorkingTreeOptions.KEY);
|
|
||||||
// TODO maybe NameConflictTreeWalk ?
|
// TODO maybe NameConflictTreeWalk ?
|
||||||
try (TreeWalk walk = new TreeWalk(repo)) {
|
try (TreeWalk walk = new TreeWalk(repo)) {
|
||||||
walk.addTree(tree);
|
walk.addTree(tree);
|
||||||
|
@ -408,17 +407,17 @@ private void resetUntracked(RevTree tree) throws CheckoutConflictException,
|
||||||
|
|
||||||
FileTreeIterator fIter = walk
|
FileTreeIterator fIter = walk
|
||||||
.getTree(1, FileTreeIterator.class);
|
.getTree(1, FileTreeIterator.class);
|
||||||
|
String gitPath = entry.getPathString();
|
||||||
if (fIter != null) {
|
if (fIter != null) {
|
||||||
if (fIter.isModified(entry, true, reader)) {
|
if (fIter.isModified(entry, true, reader)) {
|
||||||
// file exists and is dirty
|
// file exists and is dirty
|
||||||
throw new CheckoutConflictException(
|
throw new CheckoutConflictException(gitPath);
|
||||||
entry.getPathString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkoutPath(entry, reader, options,
|
checkoutPath(entry, gitPath, reader, checkout,
|
||||||
new CheckoutMetadata(eolStreamType, null));
|
new CheckoutMetadata(eolStreamType, null));
|
||||||
actuallyModifiedPaths.add(entry.getPathString());
|
actuallyModifiedPaths.add(gitPath);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (!actuallyModifiedPaths.isEmpty()) {
|
if (!actuallyModifiedPaths.isEmpty()) {
|
||||||
|
@ -428,11 +427,11 @@ private void resetUntracked(RevTree tree) throws CheckoutConflictException,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
|
private void checkoutPath(DirCacheEntry entry, String gitPath,
|
||||||
WorkingTreeOptions options, CheckoutMetadata checkoutMetadata) {
|
ObjectReader reader,
|
||||||
|
Checkout checkout, CheckoutMetadata checkoutMetadata) {
|
||||||
try {
|
try {
|
||||||
DirCacheCheckout.checkoutEntry(repo, entry, reader, true,
|
checkout.checkout(entry, checkoutMetadata, reader, gitPath);
|
||||||
checkoutMetadata, options);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new JGitInternalException(MessageFormat.format(
|
throw new JGitInternalException(MessageFormat.format(
|
||||||
JGitText.get().checkoutConflictWithFile,
|
JGitText.get().checkoutConflictWithFile,
|
||||||
|
|
|
@ -0,0 +1,238 @@
|
||||||
|
/*
|
||||||
|
* 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.dircache;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.LinkOption;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.annotations.NonNull;
|
||||||
|
import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
|
||||||
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
|
import org.eclipse.jgit.lib.FileMode;
|
||||||
|
import org.eclipse.jgit.lib.FileModeCache;
|
||||||
|
import org.eclipse.jgit.lib.ObjectLoader;
|
||||||
|
import org.eclipse.jgit.lib.ObjectReader;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
|
||||||
|
import org.eclipse.jgit.lib.CoreConfig.SymLinks;
|
||||||
|
import org.eclipse.jgit.lib.FileModeCache.CacheItem;
|
||||||
|
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
|
||||||
|
import org.eclipse.jgit.util.FS;
|
||||||
|
import org.eclipse.jgit.util.FileUtils;
|
||||||
|
import org.eclipse.jgit.util.RawParseUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object that can be used to check out many files.
|
||||||
|
*
|
||||||
|
* @since 6.6.1
|
||||||
|
*/
|
||||||
|
public class Checkout {
|
||||||
|
|
||||||
|
private final FileModeCache cache;
|
||||||
|
|
||||||
|
private final WorkingTreeOptions options;
|
||||||
|
|
||||||
|
private boolean recursiveDelete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link Checkout} for checking out from the given
|
||||||
|
* repository.
|
||||||
|
*
|
||||||
|
* @param repo
|
||||||
|
* the {@link Repository} to check out from
|
||||||
|
*/
|
||||||
|
public Checkout(@NonNull Repository repo) {
|
||||||
|
this(repo, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link Checkout} for checking out from the given
|
||||||
|
* repository.
|
||||||
|
*
|
||||||
|
* @param repo
|
||||||
|
* the {@link Repository} to check out from
|
||||||
|
* @param options
|
||||||
|
* the {@link WorkingTreeOptions} to use; if {@code null},
|
||||||
|
* read from the {@code repo} config when this object is
|
||||||
|
* created
|
||||||
|
*/
|
||||||
|
public Checkout(@NonNull Repository repo, WorkingTreeOptions options) {
|
||||||
|
this.cache = new FileModeCache(repo);
|
||||||
|
this.options = options != null ? options
|
||||||
|
: repo.getConfig().get(WorkingTreeOptions.KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the {@link WorkingTreeOptions} of the repository that are
|
||||||
|
* used.
|
||||||
|
*
|
||||||
|
* @return the {@link WorkingTreeOptions}
|
||||||
|
*/
|
||||||
|
public WorkingTreeOptions getWorkingTreeOptions() {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines whether directories that are in the way of the file to be checked
|
||||||
|
* out shall be deleted recursively.
|
||||||
|
*
|
||||||
|
* @param recursive
|
||||||
|
* whether to delete such directories recursively
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public Checkout setRecursiveDeletion(boolean recursive) {
|
||||||
|
this.recursiveDelete = recursive;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the given parent directory exists, and cache the information
|
||||||
|
* that gitPath refers to a file.
|
||||||
|
*
|
||||||
|
* @param gitPath
|
||||||
|
* of the file to be written
|
||||||
|
* @param parentDir
|
||||||
|
* directory in which the file shall be placed, assumed to be the
|
||||||
|
* parent of the {@code gitPath}
|
||||||
|
* @param makeSpace
|
||||||
|
* whether to delete a possibly existing file at
|
||||||
|
* {@code parentDir}
|
||||||
|
* @throws IOException
|
||||||
|
* if the directory cannot be created, if necessary
|
||||||
|
*/
|
||||||
|
public void safeCreateParentDirectory(String gitPath, File parentDir,
|
||||||
|
boolean makeSpace) throws IOException {
|
||||||
|
cache.safeCreateParentDirectory(gitPath, parentDir, makeSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks out the gitlink given by the {@link DirCacheEntry}.
|
||||||
|
*
|
||||||
|
* @param entry
|
||||||
|
* {@link DirCacheEntry} to check out
|
||||||
|
* @param gitPath
|
||||||
|
* the git path of the entry, if known already; otherwise
|
||||||
|
* {@code null} and it's read from the entry itself
|
||||||
|
* @throws IOException
|
||||||
|
* if the gitlink cannot be checked out
|
||||||
|
*/
|
||||||
|
public void checkoutGitlink(DirCacheEntry entry, String gitPath)
|
||||||
|
throws IOException {
|
||||||
|
FS fs = cache.getRepository().getFS();
|
||||||
|
File workingTree = cache.getRepository().getWorkTree();
|
||||||
|
String path = gitPath != null ? gitPath : entry.getPathString();
|
||||||
|
File gitlinkDir = new File(workingTree, path);
|
||||||
|
File parentDir = gitlinkDir.getParentFile();
|
||||||
|
CacheItem cachedParent = cache.safeCreateDirectory(path, parentDir,
|
||||||
|
false);
|
||||||
|
FileUtils.mkdirs(gitlinkDir, true);
|
||||||
|
cachedParent.insert(path.substring(path.lastIndexOf('/') + 1),
|
||||||
|
FileMode.GITLINK);
|
||||||
|
entry.setLastModified(fs.lastModifiedInstant(gitlinkDir));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks out the file given by the {@link DirCacheEntry}.
|
||||||
|
*
|
||||||
|
* @param entry
|
||||||
|
* {@link DirCacheEntry} to check out
|
||||||
|
* @param metadata
|
||||||
|
* {@link CheckoutMetadata} to use for CR/LF handling and
|
||||||
|
* smudge filtering
|
||||||
|
* @param reader
|
||||||
|
* {@link ObjectReader} to use
|
||||||
|
* @param gitPath
|
||||||
|
* the git path of the entry, if known already; otherwise
|
||||||
|
* {@code null} and it's read from the entry itself
|
||||||
|
* @throws IOException
|
||||||
|
* if the file cannot be checked out
|
||||||
|
*/
|
||||||
|
public void checkout(DirCacheEntry entry, CheckoutMetadata metadata,
|
||||||
|
ObjectReader reader, String gitPath) throws IOException {
|
||||||
|
if (metadata == null) {
|
||||||
|
metadata = CheckoutMetadata.EMPTY;
|
||||||
|
}
|
||||||
|
FS fs = cache.getRepository().getFS();
|
||||||
|
ObjectLoader ol = reader.open(entry.getObjectId());
|
||||||
|
String path = gitPath != null ? gitPath : entry.getPathString();
|
||||||
|
File f = new File(cache.getRepository().getWorkTree(), path);
|
||||||
|
File parentDir = f.getParentFile();
|
||||||
|
CacheItem cachedParent = cache.safeCreateDirectory(path, parentDir,
|
||||||
|
true);
|
||||||
|
if (entry.getFileMode() == FileMode.SYMLINK
|
||||||
|
&& options.getSymLinks() == SymLinks.TRUE) {
|
||||||
|
byte[] bytes = ol.getBytes();
|
||||||
|
String target = RawParseUtils.decode(bytes);
|
||||||
|
if (recursiveDelete && Files.isDirectory(f.toPath(),
|
||||||
|
LinkOption.NOFOLLOW_LINKS)) {
|
||||||
|
FileUtils.delete(f, FileUtils.RECURSIVE);
|
||||||
|
}
|
||||||
|
fs.createSymLink(f, target);
|
||||||
|
cachedParent.insert(f.getName(), FileMode.SYMLINK);
|
||||||
|
entry.setLength(bytes.length);
|
||||||
|
entry.setLastModified(fs.lastModifiedInstant(f));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String name = f.getName();
|
||||||
|
if (name.length() > 200) {
|
||||||
|
name = name.substring(0, 200);
|
||||||
|
}
|
||||||
|
File tmpFile = File.createTempFile("._" + name, null, parentDir); //$NON-NLS-1$
|
||||||
|
|
||||||
|
DirCacheCheckout.getContent(cache.getRepository(), path, metadata, ol,
|
||||||
|
options,
|
||||||
|
new FileOutputStream(tmpFile));
|
||||||
|
|
||||||
|
// The entry needs to correspond to the on-disk file size. If the
|
||||||
|
// content was filtered (either by autocrlf handling or smudge
|
||||||
|
// filters) ask the file system again for the length. Otherwise the
|
||||||
|
// object loader knows the size
|
||||||
|
if (metadata.eolStreamType == EolStreamType.DIRECT
|
||||||
|
&& metadata.smudgeFilterCommand == null) {
|
||||||
|
entry.setLength(ol.getSize());
|
||||||
|
} else {
|
||||||
|
entry.setLength(tmpFile.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.isFileMode() && fs.supportsExecute()) {
|
||||||
|
if (FileMode.EXECUTABLE_FILE.equals(entry.getRawMode())) {
|
||||||
|
if (!fs.canExecute(tmpFile))
|
||||||
|
fs.setExecute(tmpFile, true);
|
||||||
|
} else {
|
||||||
|
if (fs.canExecute(tmpFile))
|
||||||
|
fs.setExecute(tmpFile, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (recursiveDelete && Files.isDirectory(f.toPath(),
|
||||||
|
LinkOption.NOFOLLOW_LINKS)) {
|
||||||
|
FileUtils.delete(f, FileUtils.RECURSIVE);
|
||||||
|
}
|
||||||
|
FileUtils.rename(tmpFile, f, StandardCopyOption.ATOMIC_MOVE);
|
||||||
|
cachedParent.remove(f.getName());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOException(
|
||||||
|
MessageFormat.format(JGitText.get().renameFileFailed,
|
||||||
|
tmpFile.getPath(), f.getPath()),
|
||||||
|
e);
|
||||||
|
} finally {
|
||||||
|
if (tmpFile.exists()) {
|
||||||
|
FileUtils.delete(tmpFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entry.setLastModified(fs.lastModifiedInstant(f));
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@
|
||||||
* Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
|
* Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
|
||||||
* Copyright (C) 2010, Chrisian Halstrick <christian.halstrick@sap.com>
|
* Copyright (C) 2010, Chrisian Halstrick <christian.halstrick@sap.com>
|
||||||
* Copyright (C) 2019, 2020, Andre Bossert <andre.bossert@siemens.com>
|
* Copyright (C) 2019, 2020, Andre Bossert <andre.bossert@siemens.com>
|
||||||
* Copyright (C) 2017, 2022, Thomas Wolf <thomas.wolf@paranor.ch> and others
|
* Copyright (C) 2017, 2023, Thomas Wolf <twolf@apache.org> and others
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available under the
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||||
|
@ -19,11 +19,9 @@
|
||||||
import static org.eclipse.jgit.treewalk.TreeWalk.OperationType.CHECKOUT_OP;
|
import static org.eclipse.jgit.treewalk.TreeWalk.OperationType.CHECKOUT_OP;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.file.StandardCopyOption;
|
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -49,7 +47,6 @@
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
|
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
|
||||||
import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
|
import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
|
||||||
import org.eclipse.jgit.lib.CoreConfig.SymLinks;
|
|
||||||
import org.eclipse.jgit.lib.FileMode;
|
import org.eclipse.jgit.lib.FileMode;
|
||||||
import org.eclipse.jgit.lib.NullProgressMonitor;
|
import org.eclipse.jgit.lib.NullProgressMonitor;
|
||||||
import org.eclipse.jgit.lib.ObjectChecker;
|
import org.eclipse.jgit.lib.ObjectChecker;
|
||||||
|
@ -69,9 +66,7 @@
|
||||||
import org.eclipse.jgit.treewalk.filter.PathFilter;
|
import org.eclipse.jgit.treewalk.filter.PathFilter;
|
||||||
import org.eclipse.jgit.util.FS;
|
import org.eclipse.jgit.util.FS;
|
||||||
import org.eclipse.jgit.util.FS.ExecutionResult;
|
import org.eclipse.jgit.util.FS.ExecutionResult;
|
||||||
import org.eclipse.jgit.util.FileUtils;
|
|
||||||
import org.eclipse.jgit.util.IntList;
|
import org.eclipse.jgit.util.IntList;
|
||||||
import org.eclipse.jgit.util.RawParseUtils;
|
|
||||||
import org.eclipse.jgit.util.SystemReader;
|
import org.eclipse.jgit.util.SystemReader;
|
||||||
import org.eclipse.jgit.util.io.EolStreamTypeUtil;
|
import org.eclipse.jgit.util.io.EolStreamTypeUtil;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -146,7 +141,7 @@ public CheckoutMetadata(EolStreamType eolStreamType,
|
||||||
|
|
||||||
private boolean performingCheckout;
|
private boolean performingCheckout;
|
||||||
|
|
||||||
private WorkingTreeOptions options;
|
private Checkout checkout;
|
||||||
|
|
||||||
private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
|
private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
|
||||||
|
|
||||||
|
@ -509,9 +504,8 @@ private boolean doCheckout() throws CorruptObjectException, IOException,
|
||||||
MissingObjectException, IncorrectObjectTypeException,
|
MissingObjectException, IncorrectObjectTypeException,
|
||||||
CheckoutConflictException, IndexWriteException, CanceledException {
|
CheckoutConflictException, IndexWriteException, CanceledException {
|
||||||
toBeDeleted.clear();
|
toBeDeleted.clear();
|
||||||
options = repo.getConfig()
|
|
||||||
.get(WorkingTreeOptions.KEY);
|
|
||||||
try (ObjectReader objectReader = repo.getObjectDatabase().newReader()) {
|
try (ObjectReader objectReader = repo.getObjectDatabase().newReader()) {
|
||||||
|
checkout = new Checkout(repo, null);
|
||||||
if (headCommitTree != null)
|
if (headCommitTree != null)
|
||||||
preScanTwoTrees();
|
preScanTwoTrees();
|
||||||
else
|
else
|
||||||
|
@ -578,10 +572,9 @@ private boolean doCheckout() throws CorruptObjectException, IOException,
|
||||||
CheckoutMetadata meta = e.getValue();
|
CheckoutMetadata meta = e.getValue();
|
||||||
DirCacheEntry entry = dc.getEntry(path);
|
DirCacheEntry entry = dc.getEntry(path);
|
||||||
if (FileMode.GITLINK.equals(entry.getRawMode())) {
|
if (FileMode.GITLINK.equals(entry.getRawMode())) {
|
||||||
checkoutGitlink(path, entry);
|
checkout.checkoutGitlink(entry, path);
|
||||||
} else {
|
} else {
|
||||||
checkoutEntry(repo, entry, objectReader, false, meta,
|
checkout.checkout(entry, meta, objectReader, path);
|
||||||
options);
|
|
||||||
}
|
}
|
||||||
e = null;
|
e = null;
|
||||||
|
|
||||||
|
@ -616,8 +609,8 @@ private boolean doCheckout() throws CorruptObjectException, IOException,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (entry.getStage() == DirCacheEntry.STAGE_3) {
|
if (entry.getStage() == DirCacheEntry.STAGE_3) {
|
||||||
checkoutEntry(repo, entry, objectReader, false,
|
checkout.checkout(entry, null, objectReader,
|
||||||
null, options);
|
conflict);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
++entryIdx;
|
++entryIdx;
|
||||||
|
@ -640,14 +633,6 @@ private boolean doCheckout() throws CorruptObjectException, IOException,
|
||||||
return toBeDeleted.isEmpty();
|
return toBeDeleted.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkoutGitlink(String path, DirCacheEntry entry)
|
|
||||||
throws IOException {
|
|
||||||
File gitlinkDir = new File(repo.getWorkTree(), path);
|
|
||||||
FileUtils.mkdirs(gitlinkDir, true);
|
|
||||||
FS fs = repo.getFS();
|
|
||||||
entry.setLastModified(fs.lastModifiedInstant(gitlinkDir));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ArrayList<String> filterOut(ArrayList<String> strings,
|
private static ArrayList<String> filterOut(ArrayList<String> strings,
|
||||||
IntList indicesToRemove) {
|
IntList indicesToRemove) {
|
||||||
int n = indicesToRemove.size();
|
int n = indicesToRemove.size();
|
||||||
|
@ -1251,10 +1236,11 @@ private void keep(String path, DirCacheEntry e, WorkingTreeIterator f)
|
||||||
if (force) {
|
if (force) {
|
||||||
if (f == null || f.isModified(e, true, walk.getObjectReader())) {
|
if (f == null || f.isModified(e, true, walk.getObjectReader())) {
|
||||||
kept.add(path);
|
kept.add(path);
|
||||||
checkoutEntry(repo, e, walk.getObjectReader(), false,
|
checkout.checkout(e,
|
||||||
new CheckoutMetadata(walk.getEolStreamType(CHECKOUT_OP),
|
new CheckoutMetadata(walk.getEolStreamType(CHECKOUT_OP),
|
||||||
walk.getFilterCommand(
|
walk.getFilterCommand(
|
||||||
Constants.ATTR_FILTER_TYPE_SMUDGE)), options);
|
Constants.ATTR_FILTER_TYPE_SMUDGE)),
|
||||||
|
walk.getObjectReader(), path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1523,83 +1509,16 @@ public static void checkoutEntry(Repository repo, DirCacheEntry entry,
|
||||||
* @throws java.io.IOException
|
* @throws java.io.IOException
|
||||||
* if an IO error occurred
|
* if an IO error occurred
|
||||||
* @since 6.3
|
* @since 6.3
|
||||||
|
* @deprecated since 6.6.1; use {@link Checkout} instead
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static void checkoutEntry(Repository repo, DirCacheEntry entry,
|
public static void checkoutEntry(Repository repo, DirCacheEntry entry,
|
||||||
ObjectReader or, boolean deleteRecursive,
|
ObjectReader or, boolean deleteRecursive,
|
||||||
CheckoutMetadata checkoutMetadata, WorkingTreeOptions options)
|
CheckoutMetadata checkoutMetadata, WorkingTreeOptions options)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (checkoutMetadata == null) {
|
Checkout checkout = new Checkout(repo, options)
|
||||||
checkoutMetadata = CheckoutMetadata.EMPTY;
|
.setRecursiveDeletion(deleteRecursive);
|
||||||
}
|
checkout.checkout(entry, checkoutMetadata, or, null);
|
||||||
ObjectLoader ol = or.open(entry.getObjectId());
|
|
||||||
File f = new File(repo.getWorkTree(), entry.getPathString());
|
|
||||||
File parentDir = f.getParentFile();
|
|
||||||
if (parentDir.isFile()) {
|
|
||||||
FileUtils.delete(parentDir);
|
|
||||||
}
|
|
||||||
FileUtils.mkdirs(parentDir, true);
|
|
||||||
FS fs = repo.getFS();
|
|
||||||
WorkingTreeOptions opt = options != null ? options
|
|
||||||
: repo.getConfig().get(WorkingTreeOptions.KEY);
|
|
||||||
if (entry.getFileMode() == FileMode.SYMLINK
|
|
||||||
&& opt.getSymLinks() == SymLinks.TRUE) {
|
|
||||||
byte[] bytes = ol.getBytes();
|
|
||||||
String target = RawParseUtils.decode(bytes);
|
|
||||||
if (deleteRecursive && f.isDirectory()) {
|
|
||||||
FileUtils.delete(f, FileUtils.RECURSIVE);
|
|
||||||
}
|
|
||||||
fs.createSymLink(f, target);
|
|
||||||
entry.setLength(bytes.length);
|
|
||||||
entry.setLastModified(fs.lastModifiedInstant(f));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String name = f.getName();
|
|
||||||
if (name.length() > 200) {
|
|
||||||
name = name.substring(0, 200);
|
|
||||||
}
|
|
||||||
File tmpFile = File.createTempFile(
|
|
||||||
"._" + name, null, parentDir); //$NON-NLS-1$
|
|
||||||
|
|
||||||
getContent(repo, entry.getPathString(), checkoutMetadata, ol, opt,
|
|
||||||
new FileOutputStream(tmpFile));
|
|
||||||
|
|
||||||
// The entry needs to correspond to the on-disk filesize. If the content
|
|
||||||
// was filtered (either by autocrlf handling or smudge filters) ask the
|
|
||||||
// filesystem again for the length. Otherwise the objectloader knows the
|
|
||||||
// size
|
|
||||||
if (checkoutMetadata.eolStreamType == EolStreamType.DIRECT
|
|
||||||
&& checkoutMetadata.smudgeFilterCommand == null) {
|
|
||||||
entry.setLength(ol.getSize());
|
|
||||||
} else {
|
|
||||||
entry.setLength(tmpFile.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opt.isFileMode() && fs.supportsExecute()) {
|
|
||||||
if (FileMode.EXECUTABLE_FILE.equals(entry.getRawMode())) {
|
|
||||||
if (!fs.canExecute(tmpFile))
|
|
||||||
fs.setExecute(tmpFile, true);
|
|
||||||
} else {
|
|
||||||
if (fs.canExecute(tmpFile))
|
|
||||||
fs.setExecute(tmpFile, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (deleteRecursive && f.isDirectory()) {
|
|
||||||
FileUtils.delete(f, FileUtils.RECURSIVE);
|
|
||||||
}
|
|
||||||
FileUtils.rename(tmpFile, f, StandardCopyOption.ATOMIC_MOVE);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IOException(
|
|
||||||
MessageFormat.format(JGitText.get().renameFileFailed,
|
|
||||||
tmpFile.getPath(), f.getPath()),
|
|
||||||
e);
|
|
||||||
} finally {
|
|
||||||
if (tmpFile.exists()) {
|
|
||||||
FileUtils.delete(tmpFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
entry.setLastModified(fs.lastModifiedInstant(f));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -46,6 +46,8 @@ public static JGitText get() {
|
||||||
/***/ public String applyBinaryOidTooShort;
|
/***/ public String applyBinaryOidTooShort;
|
||||||
/***/ public String applyBinaryPatchTypeNotSupported;
|
/***/ public String applyBinaryPatchTypeNotSupported;
|
||||||
/***/ public String applyBinaryResultOidWrong;
|
/***/ public String applyBinaryResultOidWrong;
|
||||||
|
/***/ public String applyPatchDestInvalid;
|
||||||
|
/***/ public String applyPatchSourceInvalid;
|
||||||
/***/ public String applyPatchWithoutSourceOnAlreadyExistingSource;
|
/***/ public String applyPatchWithoutSourceOnAlreadyExistingSource;
|
||||||
/***/ public String applyPatchWithCreationOverAlreadyExistingDestination;
|
/***/ public String applyPatchWithCreationOverAlreadyExistingDestination;
|
||||||
/***/ public String applyPatchWithSourceOnNonExistentSource;
|
/***/ public String applyPatchWithSourceOnNonExistentSource;
|
||||||
|
|
|
@ -0,0 +1,309 @@
|
||||||
|
/*
|
||||||
|
* 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.lib;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.InvalidPathException;
|
||||||
|
import java.nio.file.LinkOption;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributeView;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.annotations.NonNull;
|
||||||
|
import org.eclipse.jgit.util.FS;
|
||||||
|
import org.eclipse.jgit.util.FileUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hierarchical cache of {@link FileMode}s per git path.
|
||||||
|
*
|
||||||
|
* @since 6.6.1
|
||||||
|
*/
|
||||||
|
public class FileModeCache {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final CacheItem root = new CacheItem(FileMode.TREE);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final Repository repo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link FileModeCache} for a {@link Repository}.
|
||||||
|
*
|
||||||
|
* @param repo
|
||||||
|
* {@link Repository} this cache is for
|
||||||
|
*/
|
||||||
|
public FileModeCache(@NonNull Repository repo) {
|
||||||
|
this.repo = repo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the {@link Repository}.
|
||||||
|
*
|
||||||
|
* @return the {@link Repository} this {@link FileModeCache} was created for
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public Repository getRepository() {
|
||||||
|
return repo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the {@link CacheItem} for the working tree root.
|
||||||
|
*
|
||||||
|
* @return the {@link CacheItem}
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public CacheItem getRoot() {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the given parent directory exists, and cache the information
|
||||||
|
* that gitPath refers to a file.
|
||||||
|
*
|
||||||
|
* @param gitPath
|
||||||
|
* of the file to be written
|
||||||
|
* @param parentDir
|
||||||
|
* directory in which the file shall be placed, assumed to be the
|
||||||
|
* parent of the {@code gitPath}
|
||||||
|
* @param makeSpace
|
||||||
|
* whether to delete a possibly existing file at
|
||||||
|
* {@code parentDir}
|
||||||
|
* @throws IOException
|
||||||
|
* if the directory cannot be created, if necessary
|
||||||
|
*/
|
||||||
|
public void safeCreateParentDirectory(String gitPath, File parentDir,
|
||||||
|
boolean makeSpace) throws IOException {
|
||||||
|
CacheItem cachedParent = safeCreateDirectory(gitPath, parentDir,
|
||||||
|
makeSpace);
|
||||||
|
cachedParent.remove(gitPath.substring(gitPath.lastIndexOf('/') + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures the given directory {@code dir} with the given git path exists.
|
||||||
|
*
|
||||||
|
* @param gitPath
|
||||||
|
* of a file to be written
|
||||||
|
* @param dir
|
||||||
|
* directory in which the file shall be placed, assumed to be the
|
||||||
|
* parent of the {@code gitPath}
|
||||||
|
* @param makeSpace
|
||||||
|
* whether to remove a file that already at that name
|
||||||
|
* @return A {@link CacheItem} describing the directory, which is guaranteed
|
||||||
|
* to exist
|
||||||
|
* @throws IOException
|
||||||
|
* if the directory cannot be made to exist at the given
|
||||||
|
* location
|
||||||
|
*/
|
||||||
|
public CacheItem safeCreateDirectory(String gitPath, File dir,
|
||||||
|
boolean makeSpace) throws IOException {
|
||||||
|
FS fs = repo.getFS();
|
||||||
|
int i = gitPath.lastIndexOf('/');
|
||||||
|
String parentPath = null;
|
||||||
|
if (i >= 0) {
|
||||||
|
if ((makeSpace && dir.isFile()) || fs.isSymLink(dir)) {
|
||||||
|
FileUtils.delete(dir);
|
||||||
|
}
|
||||||
|
parentPath = gitPath.substring(0, i);
|
||||||
|
deleteSymlinkParent(fs, parentPath, repo.getWorkTree());
|
||||||
|
}
|
||||||
|
FileUtils.mkdirs(dir, true);
|
||||||
|
CacheItem cachedParent = getRoot();
|
||||||
|
if (parentPath != null) {
|
||||||
|
cachedParent = add(parentPath, FileMode.TREE);
|
||||||
|
}
|
||||||
|
return cachedParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteSymlinkParent(FS fs, String gitPath, File workingTree)
|
||||||
|
throws IOException {
|
||||||
|
if (!fs.supportsSymlinks()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String[] parts = gitPath.split("/"); //$NON-NLS-1$
|
||||||
|
int n = parts.length;
|
||||||
|
CacheItem cached = getRoot();
|
||||||
|
File p = workingTree;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
p = new File(p, parts[i]);
|
||||||
|
CacheItem cachedChild = cached != null ? cached.child(parts[i])
|
||||||
|
: null;
|
||||||
|
boolean delete = false;
|
||||||
|
if (cachedChild != null) {
|
||||||
|
if (FileMode.SYMLINK.equals(cachedChild.getMode())) {
|
||||||
|
delete = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Path nioPath = FileUtils.toPath(p);
|
||||||
|
BasicFileAttributes attributes = nioPath.getFileSystem()
|
||||||
|
.provider()
|
||||||
|
.getFileAttributeView(nioPath,
|
||||||
|
BasicFileAttributeView.class,
|
||||||
|
LinkOption.NOFOLLOW_LINKS)
|
||||||
|
.readAttributes();
|
||||||
|
if (attributes.isSymbolicLink()) {
|
||||||
|
delete = p.isDirectory();
|
||||||
|
} else if (attributes.isRegularFile()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (InvalidPathException | IOException e) {
|
||||||
|
// If we can't get the attributes the path does not exist,
|
||||||
|
// or if it does a subsequent mkdirs() will also throw an
|
||||||
|
// exception.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (delete) {
|
||||||
|
// Deletes the symlink
|
||||||
|
FileUtils.delete(p, FileUtils.SKIP_MISSING);
|
||||||
|
if (cached != null) {
|
||||||
|
cached.remove(parts[i]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cached = cachedChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records the given {@link FileMode} for the given git path in the cache.
|
||||||
|
* If an entry already exists for the given path, the previously cached file
|
||||||
|
* mode is overwritten.
|
||||||
|
*
|
||||||
|
* @param gitPath
|
||||||
|
* to cache the {@link FileMode} for
|
||||||
|
* @param finalMode
|
||||||
|
* {@link FileMode} to cache
|
||||||
|
* @return the {@link CacheItem} for the path
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private CacheItem add(String gitPath, FileMode finalMode) {
|
||||||
|
if (gitPath.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
String[] parts = gitPath.split("/"); //$NON-NLS-1$
|
||||||
|
int n = parts.length;
|
||||||
|
int i = 0;
|
||||||
|
CacheItem curr = getRoot();
|
||||||
|
while (i < n) {
|
||||||
|
CacheItem next = curr.child(parts[i]);
|
||||||
|
if (next == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
curr = next;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (i == n) {
|
||||||
|
curr.setMode(finalMode);
|
||||||
|
} else {
|
||||||
|
while (i < n) {
|
||||||
|
curr = curr.insert(parts[i],
|
||||||
|
i + 1 == n ? finalMode : FileMode.TREE);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return curr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An item from a {@link FileModeCache}, recording information about a git
|
||||||
|
* path (known from context).
|
||||||
|
*/
|
||||||
|
public static class CacheItem {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private FileMode mode;
|
||||||
|
|
||||||
|
private Map<String, CacheItem> children;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link CacheItem}.
|
||||||
|
*
|
||||||
|
* @param mode
|
||||||
|
* {@link FileMode} to cache
|
||||||
|
*/
|
||||||
|
public CacheItem(@NonNull FileMode mode) {
|
||||||
|
this.mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the cached {@link FileMode}.
|
||||||
|
*
|
||||||
|
* @return the {@link FileMode}
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public FileMode getMode() {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves an immediate child of this {@link CacheItem} by name.
|
||||||
|
*
|
||||||
|
* @param childName
|
||||||
|
* name of the child to get
|
||||||
|
* @return the {@link CacheItem}, or {@code null} if no such child is
|
||||||
|
* known
|
||||||
|
*/
|
||||||
|
public CacheItem child(String childName) {
|
||||||
|
if (children == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return children.get(childName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a new cached {@link FileMode} as an immediate child of this
|
||||||
|
* {@link CacheItem}. If there is already a child with the same name, it
|
||||||
|
* is overwritten.
|
||||||
|
*
|
||||||
|
* @param childName
|
||||||
|
* name of the child to create
|
||||||
|
* @param childMode
|
||||||
|
* {@link FileMode} to cache
|
||||||
|
* @return the new {@link CacheItem} created for the child
|
||||||
|
*/
|
||||||
|
public CacheItem insert(String childName, @NonNull FileMode childMode) {
|
||||||
|
if (!FileMode.TREE.equals(mode)) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
if (children == null) {
|
||||||
|
children = new HashMap<>();
|
||||||
|
}
|
||||||
|
CacheItem newItem = new CacheItem(childMode);
|
||||||
|
children.put(childName, newItem);
|
||||||
|
return newItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the immediate child with the given name.
|
||||||
|
*
|
||||||
|
* @param childName
|
||||||
|
* name of the child to remove
|
||||||
|
* @return the previously cached {@link CacheItem}, if any
|
||||||
|
*/
|
||||||
|
public CacheItem remove(String childName) {
|
||||||
|
if (children == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return children.remove(childName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMode(@NonNull FileMode mode) {
|
||||||
|
this.mode = mode;
|
||||||
|
if (!FileMode.TREE.equals(mode)) {
|
||||||
|
children = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -3,8 +3,8 @@
|
||||||
* Copyright (C) 2010-2012, Matthias Sohn <matthias.sohn@sap.com>
|
* Copyright (C) 2010-2012, Matthias Sohn <matthias.sohn@sap.com>
|
||||||
* Copyright (C) 2012, Research In Motion Limited
|
* Copyright (C) 2012, Research In Motion Limited
|
||||||
* Copyright (C) 2017, Obeo (mathieu.cartaud@obeo.fr)
|
* Copyright (C) 2017, Obeo (mathieu.cartaud@obeo.fr)
|
||||||
* Copyright (C) 2018, 2022 Thomas Wolf <twolf@apache.org>
|
* Copyright (C) 2018, 2023 Thomas Wolf <twolf@apache.org>
|
||||||
* Copyright (C) 2022, Google Inc. and others
|
* Copyright (C) 2023, Google Inc. and others
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available under the
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||||
|
@ -47,6 +47,7 @@
|
||||||
import org.eclipse.jgit.diff.RawText;
|
import org.eclipse.jgit.diff.RawText;
|
||||||
import org.eclipse.jgit.diff.RawTextComparator;
|
import org.eclipse.jgit.diff.RawTextComparator;
|
||||||
import org.eclipse.jgit.diff.Sequence;
|
import org.eclipse.jgit.diff.Sequence;
|
||||||
|
import org.eclipse.jgit.dircache.Checkout;
|
||||||
import org.eclipse.jgit.dircache.DirCache;
|
import org.eclipse.jgit.dircache.DirCache;
|
||||||
import org.eclipse.jgit.dircache.DirCacheBuildIterator;
|
import org.eclipse.jgit.dircache.DirCacheBuildIterator;
|
||||||
import org.eclipse.jgit.dircache.DirCacheBuilder;
|
import org.eclipse.jgit.dircache.DirCacheBuilder;
|
||||||
|
@ -79,7 +80,6 @@
|
||||||
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
|
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
|
||||||
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
|
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
|
||||||
import org.eclipse.jgit.treewalk.filter.TreeFilter;
|
import org.eclipse.jgit.treewalk.filter.TreeFilter;
|
||||||
import org.eclipse.jgit.util.FS;
|
|
||||||
import org.eclipse.jgit.util.LfsFactory;
|
import org.eclipse.jgit.util.LfsFactory;
|
||||||
import org.eclipse.jgit.util.LfsFactory.LfsInputStream;
|
import org.eclipse.jgit.util.LfsFactory.LfsInputStream;
|
||||||
import org.eclipse.jgit.util.TemporaryBuffer;
|
import org.eclipse.jgit.util.TemporaryBuffer;
|
||||||
|
@ -204,6 +204,12 @@ public List<String> getModifiedFiles() {
|
||||||
*/
|
*/
|
||||||
private boolean indexChangesWritten;
|
private boolean indexChangesWritten;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Checkout} to use for actually checking out files if
|
||||||
|
* {@link #inCore} is {@code false}.
|
||||||
|
*/
|
||||||
|
private Checkout checkout;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param repo
|
* @param repo
|
||||||
* the {@link Repository}.
|
* the {@link Repository}.
|
||||||
|
@ -223,6 +229,7 @@ private WorkTreeUpdater(Repository repo, DirCache dirCache) {
|
||||||
this.inCoreFileSizeLimit = getInCoreFileSizeLimit(config);
|
this.inCoreFileSizeLimit = getInCoreFileSizeLimit(config);
|
||||||
this.checkoutMetadataByPath = new HashMap<>();
|
this.checkoutMetadataByPath = new HashMap<>();
|
||||||
this.cleanupMetadataByPath = new HashMap<>();
|
this.cleanupMetadataByPath = new HashMap<>();
|
||||||
|
this.checkout = new Checkout(nonNullRepo(), workingTreeOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -350,9 +357,8 @@ public void writeWorkTreeChanges(boolean shouldCheckoutTheirs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// All content operations are successfully done. If we can now write
|
// All content operations are successfully done. If we can now write
|
||||||
// the
|
// the new index we are on quite safe ground. Even if the checkout
|
||||||
// new index we are on quite safe ground. Even if the checkout of
|
// of files coming from "theirs" fails the user can work around such
|
||||||
// files coming from "theirs" fails the user can work around such
|
|
||||||
// failures by checking out the index again.
|
// failures by checking out the index again.
|
||||||
if (!builder.commit()) {
|
if (!builder.commit()) {
|
||||||
revertModifiedFiles();
|
revertModifiedFiles();
|
||||||
|
@ -517,14 +523,14 @@ private void checkout() throws NoWorkTreeException, IOException {
|
||||||
for (Map.Entry<String, DirCacheEntry> entry : toBeCheckedOut
|
for (Map.Entry<String, DirCacheEntry> entry : toBeCheckedOut
|
||||||
.entrySet()) {
|
.entrySet()) {
|
||||||
DirCacheEntry dirCacheEntry = entry.getValue();
|
DirCacheEntry dirCacheEntry = entry.getValue();
|
||||||
|
String gitPath = entry.getKey();
|
||||||
if (dirCacheEntry.getFileMode() == FileMode.GITLINK) {
|
if (dirCacheEntry.getFileMode() == FileMode.GITLINK) {
|
||||||
new File(nonNullRepo().getWorkTree(), entry.getKey())
|
checkout.checkoutGitlink(dirCacheEntry, gitPath);
|
||||||
.mkdirs();
|
|
||||||
} else {
|
} else {
|
||||||
DirCacheCheckout.checkoutEntry(repo, dirCacheEntry, reader,
|
checkout.checkout(dirCacheEntry,
|
||||||
false, checkoutMetadataByPath.get(entry.getKey()),
|
checkoutMetadataByPath.get(gitPath), reader,
|
||||||
workingTreeOptions);
|
gitPath);
|
||||||
result.modifiedFiles.add(entry.getKey());
|
result.modifiedFiles.add(gitPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -549,9 +555,8 @@ public void revertModifiedFiles() throws IOException {
|
||||||
for (String path : result.modifiedFiles) {
|
for (String path : result.modifiedFiles) {
|
||||||
DirCacheEntry entry = dirCache.getEntry(path);
|
DirCacheEntry entry = dirCache.getEntry(path);
|
||||||
if (entry != null) {
|
if (entry != null) {
|
||||||
DirCacheCheckout.checkoutEntry(repo, entry, reader, false,
|
checkout.checkout(entry, cleanupMetadataByPath.get(path),
|
||||||
cleanupMetadataByPath.get(path),
|
reader, path);
|
||||||
workingTreeOptions);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -585,6 +590,8 @@ public void updateFileWithContent(StreamSupplier inputStream,
|
||||||
if (inCore) {
|
if (inCore) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
checkout.safeCreateParentDirectory(path, file.getParentFile(),
|
||||||
|
false);
|
||||||
CheckoutMetadata metadata = new CheckoutMetadata(streamType,
|
CheckoutMetadata metadata = new CheckoutMetadata(streamType,
|
||||||
smudgeCommand);
|
smudgeCommand);
|
||||||
|
|
||||||
|
@ -1593,15 +1600,11 @@ private File writeMergedFile(TemporaryBuffer rawMerged,
|
||||||
Attributes attributes)
|
Attributes attributes)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
File workTree = nonNullRepo().getWorkTree();
|
File workTree = nonNullRepo().getWorkTree();
|
||||||
FS fs = nonNullRepo().getFS();
|
String gitPath = tw.getPathString();
|
||||||
File of = new File(workTree, tw.getPathString());
|
File of = new File(workTree, gitPath);
|
||||||
File parentFolder = of.getParentFile();
|
|
||||||
EolStreamType eol = workTreeUpdater.detectCheckoutStreamType(attributes);
|
EolStreamType eol = workTreeUpdater.detectCheckoutStreamType(attributes);
|
||||||
if (!fs.exists(parentFolder)) {
|
|
||||||
parentFolder.mkdirs();
|
|
||||||
}
|
|
||||||
workTreeUpdater.updateFileWithContent(rawMerged::openInputStream,
|
workTreeUpdater.updateFileWithContent(rawMerged::openInputStream,
|
||||||
eol, tw.getSmudgeCommand(attributes), of.getPath(), of);
|
eol, tw.getSmudgeCommand(attributes), gitPath, of);
|
||||||
return of;
|
return of;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022, Google Inc. and others
|
* Copyright (C) 2023, Google Inc. and others
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available under the
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||||
|
@ -52,6 +52,7 @@
|
||||||
import org.eclipse.jgit.dircache.DirCacheCheckout.StreamSupplier;
|
import org.eclipse.jgit.dircache.DirCacheCheckout.StreamSupplier;
|
||||||
import org.eclipse.jgit.dircache.DirCacheEntry;
|
import org.eclipse.jgit.dircache.DirCacheEntry;
|
||||||
import org.eclipse.jgit.dircache.DirCacheIterator;
|
import org.eclipse.jgit.dircache.DirCacheIterator;
|
||||||
|
import org.eclipse.jgit.errors.CorruptObjectException;
|
||||||
import org.eclipse.jgit.errors.IndexWriteException;
|
import org.eclipse.jgit.errors.IndexWriteException;
|
||||||
import org.eclipse.jgit.internal.JGitText;
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
import org.eclipse.jgit.lib.Config;
|
import org.eclipse.jgit.lib.Config;
|
||||||
|
@ -59,6 +60,7 @@
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
|
import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
|
||||||
import org.eclipse.jgit.lib.FileMode;
|
import org.eclipse.jgit.lib.FileMode;
|
||||||
|
import org.eclipse.jgit.lib.FileModeCache;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.ObjectInserter;
|
import org.eclipse.jgit.lib.ObjectInserter;
|
||||||
import org.eclipse.jgit.lib.ObjectLoader;
|
import org.eclipse.jgit.lib.ObjectLoader;
|
||||||
|
@ -81,6 +83,7 @@
|
||||||
import org.eclipse.jgit.util.LfsFactory.LfsInputStream;
|
import org.eclipse.jgit.util.LfsFactory.LfsInputStream;
|
||||||
import org.eclipse.jgit.util.RawParseUtils;
|
import org.eclipse.jgit.util.RawParseUtils;
|
||||||
import org.eclipse.jgit.util.StringUtils;
|
import org.eclipse.jgit.util.StringUtils;
|
||||||
|
import org.eclipse.jgit.util.SystemReader;
|
||||||
import org.eclipse.jgit.util.TemporaryBuffer;
|
import org.eclipse.jgit.util.TemporaryBuffer;
|
||||||
import org.eclipse.jgit.util.TemporaryBuffer.LocalFile;
|
import org.eclipse.jgit.util.TemporaryBuffer.LocalFile;
|
||||||
import org.eclipse.jgit.util.io.BinaryDeltaInputStream;
|
import org.eclipse.jgit.util.io.BinaryDeltaInputStream;
|
||||||
|
@ -259,6 +262,7 @@ public Result applyPatch(Patch p) throws IOException {
|
||||||
DirCache dirCache = inCore() ? DirCache.read(reader, beforeTree)
|
DirCache dirCache = inCore() ? DirCache.read(reader, beforeTree)
|
||||||
: repo.lockDirCache();
|
: repo.lockDirCache();
|
||||||
|
|
||||||
|
FileModeCache directoryCache = new FileModeCache(repo);
|
||||||
DirCacheBuilder dirCacheBuilder = dirCache.builder();
|
DirCacheBuilder dirCacheBuilder = dirCache.builder();
|
||||||
Set<String> modifiedPaths = new HashSet<>();
|
Set<String> modifiedPaths = new HashSet<>();
|
||||||
for (FileHeader fh : p.getFiles()) {
|
for (FileHeader fh : p.getFiles()) {
|
||||||
|
@ -271,7 +275,8 @@ public Result applyPatch(Patch p) throws IOException {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ADD: {
|
case ADD: {
|
||||||
if (dest != null) {
|
if (dest != null) {
|
||||||
FileUtils.mkdirs(dest.getParentFile(), true);
|
directoryCache.safeCreateParentDirectory(fh.getNewPath(),
|
||||||
|
dest.getParentFile(), false);
|
||||||
FileUtils.createNewFile(dest);
|
FileUtils.createNewFile(dest);
|
||||||
}
|
}
|
||||||
apply(fh.getNewPath(), dirCache, dirCacheBuilder, dest, fh, result);
|
apply(fh.getNewPath(), dirCache, dirCacheBuilder, dest, fh, result);
|
||||||
|
@ -296,7 +301,8 @@ public Result applyPatch(Patch p) throws IOException {
|
||||||
* apply() will write a fresh stream anyway, which will
|
* apply() will write a fresh stream anyway, which will
|
||||||
* overwrite if there were hunks in the patch.
|
* overwrite if there were hunks in the patch.
|
||||||
*/
|
*/
|
||||||
FileUtils.mkdirs(dest.getParentFile(), true);
|
directoryCache.safeCreateParentDirectory(fh.getNewPath(),
|
||||||
|
dest.getParentFile(), false);
|
||||||
FileUtils.rename(src, dest,
|
FileUtils.rename(src, dest,
|
||||||
StandardCopyOption.ATOMIC_MOVE);
|
StandardCopyOption.ATOMIC_MOVE);
|
||||||
}
|
}
|
||||||
|
@ -307,7 +313,8 @@ public Result applyPatch(Patch p) throws IOException {
|
||||||
}
|
}
|
||||||
case COPY: {
|
case COPY: {
|
||||||
if (!inCore()) {
|
if (!inCore()) {
|
||||||
FileUtils.mkdirs(dest.getParentFile(), true);
|
directoryCache.safeCreateParentDirectory(fh.getNewPath(),
|
||||||
|
dest.getParentFile(), false);
|
||||||
Files.copy(src.toPath(), dest.toPath());
|
Files.copy(src.toPath(), dest.toPath());
|
||||||
}
|
}
|
||||||
apply(fh.getOldPath(), dirCache, dirCacheBuilder, dest, fh, result);
|
apply(fh.getOldPath(), dirCache, dirCacheBuilder, dest, fh, result);
|
||||||
|
@ -402,9 +409,27 @@ private boolean verifyExistence(FileHeader fh, File src, File dest,
|
||||||
fh.getPatchType()), fh.getNewPath(), null);
|
fh.getPatchType()), fh.getNewPath(), null);
|
||||||
isValid = false;
|
isValid = false;
|
||||||
}
|
}
|
||||||
|
if (srcShouldExist && !validGitPath(fh.getOldPath())) {
|
||||||
|
result.addError(JGitText.get().applyPatchSourceInvalid,
|
||||||
|
fh.getOldPath(), null);
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
if (destShouldNotExist && !validGitPath(fh.getNewPath())) {
|
||||||
|
result.addError(JGitText.get().applyPatchDestInvalid,
|
||||||
|
fh.getNewPath(), null);
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
return isValid;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean validGitPath(String path) {
|
||||||
|
try {
|
||||||
|
SystemReader.getInstance().checkPath(path);
|
||||||
|
return true;
|
||||||
|
} catch (CorruptObjectException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
private static final int FILE_TREE_INDEX = 1;
|
private static final int FILE_TREE_INDEX = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue