Add symlink support to JGit
The change includes comparing symbolic links between disk and index, adding symbolic links to the index, creating/modifying links on checkout. The behavior is controlled by the core.symlinks setting, just as C Git does. When a new repository is created core.symlinks will be set depending on the capabilities of the operating system and Java runtime. If core.symlinks is set to true, the assumption is that symlinks are supported, which may result in runtime errors if this turns out not to be the case. Measuring the cost of jgit status on a repository with ~70000 files, of which ~30000 are tracked reveals a penalty of about 10% for using the Java7 (really NIO2) support module. Bug: 354367 Change-Id: I12f0fdd9d26212324a586896ef7eb1f6ff89c39c Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
This commit is contained in:
parent
5ef6d69532
commit
078a9f6066
|
@ -46,6 +46,8 @@
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.lib.CLIRepositoryTestCase;
|
import org.eclipse.jgit.lib.CLIRepositoryTestCase;
|
||||||
|
@ -75,8 +77,28 @@ public void testListConfig() throws Exception {
|
||||||
if (isMac)
|
if (isMac)
|
||||||
expect.add("core.precomposeunicode=true");
|
expect.add("core.precomposeunicode=true");
|
||||||
expect.add("core.repositoryformatversion=0");
|
expect.add("core.repositoryformatversion=0");
|
||||||
|
if (SystemReader.getInstance().isWindows() && osVersion() < 6
|
||||||
|
|| javaVersion() < 1.7) {
|
||||||
|
expect.add("core.symlinks=false");
|
||||||
|
}
|
||||||
expect.add(""); // ends with LF (last line empty)
|
expect.add(""); // ends with LF (last line empty)
|
||||||
assertArrayEquals("expected default configuration", expect.toArray(),
|
assertArrayEquals("expected default configuration", expect.toArray(),
|
||||||
output);
|
output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static float javaVersion() {
|
||||||
|
String versionString = System.getProperty("java.version");
|
||||||
|
Matcher matcher = Pattern.compile("(\\d+\\.\\d+).*").matcher(
|
||||||
|
versionString);
|
||||||
|
matcher.matches();
|
||||||
|
return Float.parseFloat(matcher.group(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float osVersion() {
|
||||||
|
String versionString = System.getProperty("os.version");
|
||||||
|
Matcher matcher = Pattern.compile("(\\d+\\.\\d+).*").matcher(
|
||||||
|
versionString);
|
||||||
|
matcher.matches();
|
||||||
|
return Float.parseFloat(matcher.group(1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,7 @@ protected void run() throws Exception {
|
||||||
generator.push(null, dc.getEntry(entry).getObjectId());
|
generator.push(null, dc.getEntry(entry).getObjectId());
|
||||||
|
|
||||||
File inTree = new File(db.getWorkTree(), file);
|
File inTree = new File(db.getWorkTree(), file);
|
||||||
if (inTree.isFile())
|
if (db.getFS().isFile(inTree))
|
||||||
generator.push(null, new RawText(inTree));
|
generator.push(null, new RawText(inTree));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,7 @@
|
||||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||||
import org.eclipse.jgit.errors.MissingObjectException;
|
import org.eclipse.jgit.errors.MissingObjectException;
|
||||||
import org.eclipse.jgit.junit.RepositoryTestCase;
|
import org.eclipse.jgit.junit.RepositoryTestCase;
|
||||||
|
import org.eclipse.jgit.lib.ConfigConstants;
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.FileMode;
|
import org.eclipse.jgit.lib.FileMode;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
@ -242,9 +243,11 @@ public void testDirCacheMatchingId() throws Exception {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIsModifiedSymlink() throws Exception {
|
public void testIsModifiedSymlinkAsFile() throws Exception {
|
||||||
File f = writeTrashFile("symlink", "content");
|
File f = writeTrashFile("symlink", "content");
|
||||||
Git git = new Git(db);
|
Git git = new Git(db);
|
||||||
|
db.getConfig().setString(ConfigConstants.CONFIG_CORE_SECTION, null,
|
||||||
|
ConfigConstants.CONFIG_KEY_SYMLINKS, "false");
|
||||||
git.add().addFilepattern("symlink").call();
|
git.add().addFilepattern("symlink").call();
|
||||||
git.commit().setMessage("commit").call();
|
git.commit().setMessage("commit").call();
|
||||||
|
|
||||||
|
|
|
@ -215,7 +215,7 @@ else if (startCommit != null)
|
||||||
gen.push(null, dc.getEntry(entry).getObjectId());
|
gen.push(null, dc.getEntry(entry).getObjectId());
|
||||||
|
|
||||||
File inTree = new File(repo.getWorkTree(), path);
|
File inTree = new File(repo.getWorkTree(), path);
|
||||||
if (inTree.isFile())
|
if (repo.getFS().isFile(inTree))
|
||||||
gen.push(null, new RawText(inTree));
|
gen.push(null, new RawText(inTree));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
import org.eclipse.jgit.api.errors.JGitInternalException;
|
import org.eclipse.jgit.api.errors.JGitInternalException;
|
||||||
import org.eclipse.jgit.errors.NoWorkTreeException;
|
import org.eclipse.jgit.errors.NoWorkTreeException;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.util.FS;
|
||||||
import org.eclipse.jgit.util.FileUtils;
|
import org.eclipse.jgit.util.FileUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -100,14 +101,14 @@ public Set<String> call() throws NoWorkTreeException, GitAPIException {
|
||||||
Set<String> untrackedAndIgnoredDirs = new TreeSet<String>(
|
Set<String> untrackedAndIgnoredDirs = new TreeSet<String>(
|
||||||
status.getUntrackedFolders());
|
status.getUntrackedFolders());
|
||||||
|
|
||||||
|
FS fs = getRepository().getFS();
|
||||||
for (String p : status.getIgnoredNotInIndex()) {
|
for (String p : status.getIgnoredNotInIndex()) {
|
||||||
File f = new File(repo.getWorkTree(), p);
|
File f = new File(repo.getWorkTree(), p);
|
||||||
if (f.isFile()) {
|
if (fs.isFile(f) || fs.isSymLink(f))
|
||||||
untrackedAndIgnoredFiles.add(p);
|
untrackedAndIgnoredFiles.add(p);
|
||||||
} else if (f.isDirectory()) {
|
else if (fs.isDirectory(f))
|
||||||
untrackedAndIgnoredDirs.add(p);
|
untrackedAndIgnoredDirs.add(p);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Set<String> filtered = filterFolders(untrackedAndIgnoredFiles,
|
Set<String> filtered = filterFolders(untrackedAndIgnoredFiles,
|
||||||
untrackedAndIgnoredDirs);
|
untrackedAndIgnoredDirs);
|
||||||
|
|
|
@ -731,7 +731,7 @@ private RevCommit checkoutCurrentHead() throws IOException, NoHeadException {
|
||||||
List<String> fileList = dco.getToBeDeleted();
|
List<String> fileList = dco.getToBeDeleted();
|
||||||
for (String filePath : fileList) {
|
for (String filePath : fileList) {
|
||||||
File fileToDelete = new File(repo.getWorkTree(), filePath);
|
File fileToDelete = new File(repo.getWorkTree(), filePath);
|
||||||
if (fileToDelete.exists())
|
if (repo.getFS().exists(fileToDelete))
|
||||||
FileUtils.delete(fileToDelete, FileUtils.RECURSIVE
|
FileUtils.delete(fileToDelete, FileUtils.RECURSIVE
|
||||||
| FileUtils.RETRY);
|
| FileUtils.RETRY);
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,7 @@
|
||||||
import org.eclipse.jgit.internal.JGitText;
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
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.SymLinks;
|
||||||
import org.eclipse.jgit.lib.FileMode;
|
import org.eclipse.jgit.lib.FileMode;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.ObjectLoader;
|
import org.eclipse.jgit.lib.ObjectLoader;
|
||||||
|
@ -399,7 +400,6 @@ private boolean doCheckout() throws CorruptObjectException, IOException,
|
||||||
MissingObjectException, IncorrectObjectTypeException,
|
MissingObjectException, IncorrectObjectTypeException,
|
||||||
CheckoutConflictException, IndexWriteException {
|
CheckoutConflictException, IndexWriteException {
|
||||||
toBeDeleted.clear();
|
toBeDeleted.clear();
|
||||||
|
|
||||||
ObjectReader objectReader = repo.getObjectDatabase().newReader();
|
ObjectReader objectReader = repo.getObjectDatabase().newReader();
|
||||||
try {
|
try {
|
||||||
if (headCommitTree != null)
|
if (headCommitTree != null)
|
||||||
|
@ -425,13 +425,13 @@ private boolean doCheckout() throws CorruptObjectException, IOException,
|
||||||
for (int i = removed.size() - 1; i >= 0; i--) {
|
for (int i = removed.size() - 1; i >= 0; i--) {
|
||||||
String r = removed.get(i);
|
String r = removed.get(i);
|
||||||
file = new File(repo.getWorkTree(), r);
|
file = new File(repo.getWorkTree(), r);
|
||||||
if (!file.delete() && file.exists()) {
|
if (!file.delete() && repo.getFS().exists(file)) {
|
||||||
// The list of stuff to delete comes from the index
|
// The list of stuff to delete comes from the index
|
||||||
// which will only contain a directory if it is
|
// which will only contain a directory if it is
|
||||||
// a submodule, in which case we shall not attempt
|
// a submodule, in which case we shall not attempt
|
||||||
// to delete it. A submodule is not empty, so it
|
// to delete it. A submodule is not empty, so it
|
||||||
// is safe to check this after a failed delete.
|
// is safe to check this after a failed delete.
|
||||||
if (!file.isDirectory())
|
if (!repo.getFS().isDirectory(file))
|
||||||
toBeDeleted.add(r);
|
toBeDeleted.add(r);
|
||||||
} else {
|
} else {
|
||||||
if (last != null && !isSamePrefix(r, last))
|
if (last != null && !isSamePrefix(r, last))
|
||||||
|
@ -583,9 +583,8 @@ void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
|
||||||
// represents the state for the merge iterator, the second last the
|
// represents the state for the merge iterator, the second last the
|
||||||
// state for the index iterator and the third last represents the state
|
// state for the index iterator and the third last represents the state
|
||||||
// for the head iterator. The hexadecimal constant "F" stands for
|
// for the head iterator. The hexadecimal constant "F" stands for
|
||||||
// "file",
|
// "file", a "D" stands for "directory" (tree), and a "0" stands for
|
||||||
// an "D" stands for "directory" (tree), and a "0" stands for
|
// non-existing. Symbolic links and git links are treated as File here.
|
||||||
// non-existing
|
|
||||||
//
|
//
|
||||||
// Examples:
|
// Examples:
|
||||||
// ffMask == 0xFFD -> Head=File, Index=File, Merge=Tree
|
// ffMask == 0xFFD -> Head=File, Index=File, Merge=Tree
|
||||||
|
@ -1117,8 +1116,18 @@ public static void checkoutEntry(final Repository repo, File f,
|
||||||
ObjectLoader ol = or.open(entry.getObjectId());
|
ObjectLoader ol = or.open(entry.getObjectId());
|
||||||
File parentDir = f.getParentFile();
|
File parentDir = f.getParentFile();
|
||||||
parentDir.mkdirs();
|
parentDir.mkdirs();
|
||||||
File tmpFile = File.createTempFile("._" + f.getName(), null, parentDir); //$NON-NLS-1$
|
FS fs = repo.getFS();
|
||||||
WorkingTreeOptions opt = repo.getConfig().get(WorkingTreeOptions.KEY);
|
WorkingTreeOptions opt = repo.getConfig().get(WorkingTreeOptions.KEY);
|
||||||
|
if (entry.getFileMode() == FileMode.SYMLINK
|
||||||
|
&& opt.getSymLinks() == SymLinks.TRUE) {
|
||||||
|
byte[] bytes = ol.getBytes();
|
||||||
|
String target = RawParseUtils.decode(bytes);
|
||||||
|
fs.createSymLink(f, target);
|
||||||
|
entry.setLength(bytes.length);
|
||||||
|
entry.setLastModified(fs.lastModified(f));
|
||||||
|
} else {
|
||||||
|
File tmpFile = File.createTempFile(
|
||||||
|
"._" + f.getName(), null, parentDir); //$NON-NLS-1$
|
||||||
FileOutputStream rawChannel = new FileOutputStream(tmpFile);
|
FileOutputStream rawChannel = new FileOutputStream(tmpFile);
|
||||||
OutputStream channel;
|
OutputStream channel;
|
||||||
if (opt.getAutoCRLF() == AutoCRLF.TRUE)
|
if (opt.getAutoCRLF() == AutoCRLF.TRUE)
|
||||||
|
@ -1130,7 +1139,6 @@ public static void checkoutEntry(final Repository repo, File f,
|
||||||
} finally {
|
} finally {
|
||||||
channel.close();
|
channel.close();
|
||||||
}
|
}
|
||||||
FS fs = repo.getFS();
|
|
||||||
if (opt.isFileMode() && fs.supportsExecute()) {
|
if (opt.isFileMode() && fs.supportsExecute()) {
|
||||||
if (FileMode.EXECUTABLE_FILE.equals(entry.getRawMode())) {
|
if (FileMode.EXECUTABLE_FILE.equals(entry.getRawMode())) {
|
||||||
if (!fs.canExecute(tmpFile))
|
if (!fs.canExecute(tmpFile))
|
||||||
|
@ -1147,6 +1155,7 @@ public static void checkoutEntry(final Repository repo, File f,
|
||||||
JGitText.get().couldNotWriteFile, tmpFile.getPath(),
|
JGitText.get().couldNotWriteFile, tmpFile.getPath(),
|
||||||
f.getPath()));
|
f.getPath()));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
entry.setLastModified(f.lastModified());
|
entry.setLastModified(f.lastModified());
|
||||||
if (opt.getAutoCRLF() != AutoCRLF.FALSE)
|
if (opt.getAutoCRLF() != AutoCRLF.FALSE)
|
||||||
entry.setLength(f.length()); // AutoCRLF wants on-disk-size
|
entry.setLength(f.length()); // AutoCRLF wants on-disk-size
|
||||||
|
|
|
@ -64,6 +64,7 @@
|
||||||
import org.eclipse.jgit.lib.BaseRepositoryBuilder;
|
import org.eclipse.jgit.lib.BaseRepositoryBuilder;
|
||||||
import org.eclipse.jgit.lib.ConfigConstants;
|
import org.eclipse.jgit.lib.ConfigConstants;
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
|
import org.eclipse.jgit.lib.CoreConfig.SymLinks;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.Ref;
|
import org.eclipse.jgit.lib.Ref;
|
||||||
import org.eclipse.jgit.lib.RefDatabase;
|
import org.eclipse.jgit.lib.RefDatabase;
|
||||||
|
@ -293,6 +294,21 @@ public void create(boolean bare) throws IOException {
|
||||||
fileMode = false;
|
fileMode = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SymLinks symLinks = SymLinks.FALSE;
|
||||||
|
if (getFS().supportsSymlinks()) {
|
||||||
|
File tmp = new File(getDirectory(), "tmplink"); //$NON-NLS-1$
|
||||||
|
try {
|
||||||
|
getFS().createSymLink(tmp, "target"); //$NON-NLS-1$
|
||||||
|
symLinks = null;
|
||||||
|
FileUtils.delete(tmp);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Normally a java.nio.file.FileSystemException
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (symLinks != null)
|
||||||
|
cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
|
||||||
|
ConfigConstants.CONFIG_KEY_SYMLINKS, symLinks.name()
|
||||||
|
.toLowerCase());
|
||||||
cfg.setInt(ConfigConstants.CONFIG_CORE_SECTION, null,
|
cfg.setInt(ConfigConstants.CONFIG_CORE_SECTION, null,
|
||||||
ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
|
ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
|
||||||
cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
|
cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
|
||||||
|
|
|
@ -123,6 +123,13 @@ public class ConfigConstants {
|
||||||
/** The "deltaBaseCacheLimit" key */
|
/** The "deltaBaseCacheLimit" key */
|
||||||
public static final String CONFIG_KEY_DELTA_BASE_CACHE_LIMIT = "deltaBaseCacheLimit";
|
public static final String CONFIG_KEY_DELTA_BASE_CACHE_LIMIT = "deltaBaseCacheLimit";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The "symlinks" key
|
||||||
|
*
|
||||||
|
* @since 3.3
|
||||||
|
*/
|
||||||
|
public static final String CONFIG_KEY_SYMLINKS = "symlinks";
|
||||||
|
|
||||||
/** The "streamFileThreshold" key */
|
/** The "streamFileThreshold" key */
|
||||||
public static final String CONFIG_KEY_STREAM_FILE_TRESHOLD = "streamFileThreshold";
|
public static final String CONFIG_KEY_STREAM_FILE_TRESHOLD = "streamFileThreshold";
|
||||||
|
|
||||||
|
|
|
@ -101,6 +101,18 @@ public static enum CheckStat {
|
||||||
|
|
||||||
private final String excludesfile;
|
private final String excludesfile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for symlink handling
|
||||||
|
*
|
||||||
|
* @since 3.3
|
||||||
|
*/
|
||||||
|
public static enum SymLinks {
|
||||||
|
/** Checkout symbolic links as plain files */
|
||||||
|
FALSE,
|
||||||
|
/** Checkout symbolic links as links */
|
||||||
|
TRUE
|
||||||
|
}
|
||||||
|
|
||||||
private CoreConfig(final Config rc) {
|
private CoreConfig(final Config rc) {
|
||||||
compression = rc.getInt(ConfigConstants.CONFIG_CORE_SECTION,
|
compression = rc.getInt(ConfigConstants.CONFIG_CORE_SECTION,
|
||||||
ConfigConstants.CONFIG_KEY_COMPRESSION, DEFAULT_COMPRESSION);
|
ConfigConstants.CONFIG_KEY_COMPRESSION, DEFAULT_COMPRESSION);
|
||||||
|
|
|
@ -86,6 +86,7 @@
|
||||||
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
|
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
|
||||||
import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
|
import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
|
||||||
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
|
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
|
||||||
|
import org.eclipse.jgit.util.FS;
|
||||||
import org.eclipse.jgit.util.FileUtils;
|
import org.eclipse.jgit.util.FileUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -255,11 +256,11 @@ private void checkout() throws NoWorkTreeException, IOException {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createDir(File f) throws IOException {
|
private void createDir(File f) throws IOException {
|
||||||
if (!f.isDirectory() && !f.mkdirs()) {
|
if (!db.getFS().isDirectory(f) && !f.mkdirs()) {
|
||||||
File p = f;
|
File p = f;
|
||||||
while (p != null && !p.exists())
|
while (p != null && !db.getFS().exists(p))
|
||||||
p = p.getParentFile();
|
p = p.getParentFile();
|
||||||
if (p == null || p.isDirectory())
|
if (p == null || db.getFS().isDirectory(p))
|
||||||
throw new IOException(JGitText.get().cannotCreateDirectory);
|
throw new IOException(JGitText.get().cannotCreateDirectory);
|
||||||
FileUtils.delete(p);
|
FileUtils.delete(p);
|
||||||
if (!f.mkdirs())
|
if (!f.mkdirs())
|
||||||
|
@ -719,9 +720,10 @@ private File writeMergedFile(MergeResult<RawText> result)
|
||||||
// support write operations
|
// support write operations
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
|
|
||||||
|
FS fs = db.getFS();
|
||||||
of = new File(workTree, tw.getPathString());
|
of = new File(workTree, tw.getPathString());
|
||||||
File parentFolder = of.getParentFile();
|
File parentFolder = of.getParentFile();
|
||||||
if (!parentFolder.exists())
|
if (!fs.exists(parentFolder))
|
||||||
parentFolder.mkdirs();
|
parentFolder.mkdirs();
|
||||||
fos = new FileOutputStream(of);
|
fos = new FileOutputStream(of);
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
|
|
||||||
package org.eclipse.jgit.treewalk;
|
package org.eclipse.jgit.treewalk;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -156,6 +157,8 @@ static public class FileEntry extends Entry {
|
||||||
|
|
||||||
private long lastModified;
|
private long lastModified;
|
||||||
|
|
||||||
|
private FS fs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new file entry.
|
* Create a new file entry.
|
||||||
*
|
*
|
||||||
|
@ -166,8 +169,14 @@ static public class FileEntry extends Entry {
|
||||||
*/
|
*/
|
||||||
public FileEntry(final File f, FS fs) {
|
public FileEntry(final File f, FS fs) {
|
||||||
file = f;
|
file = f;
|
||||||
|
this.fs = fs;
|
||||||
|
|
||||||
if (f.isDirectory()) {
|
@SuppressWarnings("hiding")
|
||||||
|
FileMode mode = null;
|
||||||
|
try {
|
||||||
|
if (fs.isSymLink(f)) {
|
||||||
|
mode = FileMode.SYMLINK;
|
||||||
|
} else if (fs.isDirectory(f)) {
|
||||||
if (fs.exists(new File(f, Constants.DOT_GIT)))
|
if (fs.exists(new File(f, Constants.DOT_GIT)))
|
||||||
mode = FileMode.GITLINK;
|
mode = FileMode.GITLINK;
|
||||||
else
|
else
|
||||||
|
@ -176,6 +185,10 @@ public FileEntry(final File f, FS fs) {
|
||||||
mode = FileMode.EXECUTABLE_FILE;
|
mode = FileMode.EXECUTABLE_FILE;
|
||||||
else
|
else
|
||||||
mode = FileMode.REGULAR_FILE;
|
mode = FileMode.REGULAR_FILE;
|
||||||
|
} catch (IOException e) {
|
||||||
|
mode = FileMode.MISSING;
|
||||||
|
}
|
||||||
|
this.mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -190,20 +203,34 @@ public String getName() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getLength() {
|
public long getLength() {
|
||||||
if (length < 0)
|
if (length < 0) {
|
||||||
length = file.length();
|
try {
|
||||||
|
length = fs.length(file);
|
||||||
|
} catch (IOException e) {
|
||||||
|
length = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getLastModified() {
|
public long getLastModified() {
|
||||||
if (lastModified == 0)
|
if (lastModified == 0) {
|
||||||
lastModified = file.lastModified();
|
try {
|
||||||
|
lastModified = fs.lastModified(file);
|
||||||
|
} catch (IOException e) {
|
||||||
|
lastModified = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
return lastModified;
|
return lastModified;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream openInputStream() throws IOException {
|
public InputStream openInputStream() throws IOException {
|
||||||
|
if (fs.isSymLink(file))
|
||||||
|
return new ByteArrayInputStream(fs.readSymLink(file).getBytes(
|
||||||
|
Constants.CHARACTER_ENCODING));
|
||||||
|
else
|
||||||
return new FileInputStream(file);
|
return new FileInputStream(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,7 @@
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.CoreConfig;
|
import org.eclipse.jgit.lib.CoreConfig;
|
||||||
import org.eclipse.jgit.lib.CoreConfig.CheckStat;
|
import org.eclipse.jgit.lib.CoreConfig.CheckStat;
|
||||||
|
import org.eclipse.jgit.lib.CoreConfig.SymLinks;
|
||||||
import org.eclipse.jgit.lib.FileMode;
|
import org.eclipse.jgit.lib.FileMode;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.ObjectLoader;
|
import org.eclipse.jgit.lib.ObjectLoader;
|
||||||
|
@ -202,6 +203,15 @@ protected void initRootIterator(Repository repo) {
|
||||||
ignoreNode = new RootIgnoreNode(entry, repo);
|
ignoreNode = new RootIgnoreNode(entry, repo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the repository this iterator works with
|
||||||
|
*
|
||||||
|
* @since 3.3
|
||||||
|
*/
|
||||||
|
public Repository getRepository() {
|
||||||
|
return repository;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define the matching {@link DirCacheIterator}, to optimize ObjectIds.
|
* Define the matching {@link DirCacheIterator}, to optimize ObjectIds.
|
||||||
*
|
*
|
||||||
|
@ -252,14 +262,10 @@ public byte[] idBuffer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch (mode & FileMode.TYPE_MASK) {
|
switch (mode & FileMode.TYPE_MASK) {
|
||||||
|
case FileMode.TYPE_SYMLINK:
|
||||||
case FileMode.TYPE_FILE:
|
case FileMode.TYPE_FILE:
|
||||||
contentIdFromPtr = ptr;
|
contentIdFromPtr = ptr;
|
||||||
return contentId = idBufferBlob(entries[ptr]);
|
return contentId = idBufferBlob(entries[ptr]);
|
||||||
case FileMode.TYPE_SYMLINK:
|
|
||||||
// Java does not support symbolic links, so we should not
|
|
||||||
// have reached this particular part of the walk code.
|
|
||||||
//
|
|
||||||
return zeroid;
|
|
||||||
case FileMode.TYPE_GITLINK:
|
case FileMode.TYPE_GITLINK:
|
||||||
contentIdFromPtr = ptr;
|
contentIdFromPtr = ptr;
|
||||||
return contentId = idSubmodule(entries[ptr]);
|
return contentId = idSubmodule(entries[ptr]);
|
||||||
|
@ -723,6 +729,7 @@ public boolean isModeDifferent(final int rawMode) {
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Do not rely on filemode differences in case of symbolic links
|
// Do not rely on filemode differences in case of symbolic links
|
||||||
|
if (getOptions().getSymLinks() == SymLinks.FALSE)
|
||||||
if (FileMode.SYMLINK.equals(rawMode))
|
if (FileMode.SYMLINK.equals(rawMode))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
import org.eclipse.jgit.lib.Config.SectionParser;
|
import org.eclipse.jgit.lib.Config.SectionParser;
|
||||||
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
|
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
|
||||||
import org.eclipse.jgit.lib.CoreConfig.CheckStat;
|
import org.eclipse.jgit.lib.CoreConfig.CheckStat;
|
||||||
|
import org.eclipse.jgit.lib.CoreConfig.SymLinks;
|
||||||
|
|
||||||
/** Options used by the {@link WorkingTreeIterator}. */
|
/** Options used by the {@link WorkingTreeIterator}. */
|
||||||
public class WorkingTreeOptions {
|
public class WorkingTreeOptions {
|
||||||
|
@ -64,6 +65,8 @@ public WorkingTreeOptions parse(final Config cfg) {
|
||||||
|
|
||||||
private final CheckStat checkStat;
|
private final CheckStat checkStat;
|
||||||
|
|
||||||
|
private final SymLinks symlinks;
|
||||||
|
|
||||||
private WorkingTreeOptions(final Config rc) {
|
private WorkingTreeOptions(final Config rc) {
|
||||||
fileMode = rc.getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
|
fileMode = rc.getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
|
||||||
ConfigConstants.CONFIG_KEY_FILEMODE, true);
|
ConfigConstants.CONFIG_KEY_FILEMODE, true);
|
||||||
|
@ -71,6 +74,8 @@ private WorkingTreeOptions(final Config rc) {
|
||||||
ConfigConstants.CONFIG_KEY_AUTOCRLF, AutoCRLF.FALSE);
|
ConfigConstants.CONFIG_KEY_AUTOCRLF, AutoCRLF.FALSE);
|
||||||
checkStat = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
|
checkStat = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
|
||||||
ConfigConstants.CONFIG_KEY_CHECKSTAT, CheckStat.DEFAULT);
|
ConfigConstants.CONFIG_KEY_CHECKSTAT, CheckStat.DEFAULT);
|
||||||
|
symlinks = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
|
||||||
|
ConfigConstants.CONFIG_KEY_SYMLINKS, SymLinks.TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return true if the execute bit on working files should be trusted. */
|
/** @return true if the execute bit on working files should be trusted. */
|
||||||
|
@ -90,4 +95,12 @@ public AutoCRLF getAutoCRLF() {
|
||||||
public CheckStat getCheckStat() {
|
public CheckStat getCheckStat() {
|
||||||
return checkStat;
|
return checkStat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return how we handle symbolic links
|
||||||
|
* @since 3.3
|
||||||
|
*/
|
||||||
|
public SymLinks getSymLinks() {
|
||||||
|
return symlinks;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue