Content length is computed and cached (short term) in the working tree iterator when core.autocrlf is set. Hopefully this is a cleaner fix than my previous attempt to make autocrlf work. Change-Id: I1b6bbb643101a00db94e5514b5e2b069f338907atags/v2.0.0.201206130900-r
package org.eclipse.jgit.api; | package org.eclipse.jgit.api; | ||||
import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||
import static org.junit.Assert.assertFalse; | |||||
import static org.junit.Assert.assertNotNull; | import static org.junit.Assert.assertNotNull; | ||||
import static org.junit.Assert.assertTrue; | |||||
import static org.junit.Assert.fail; | import static org.junit.Assert.fail; | ||||
import java.io.File; | import java.io.File; | ||||
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.ObjectInserter; | import org.eclipse.jgit.lib.ObjectInserter; | ||||
import org.eclipse.jgit.lib.Repository; | |||||
import org.eclipse.jgit.lib.RepositoryTestCase; | import org.eclipse.jgit.lib.RepositoryTestCase; | ||||
import org.eclipse.jgit.lib.StoredConfig; | import org.eclipse.jgit.lib.StoredConfig; | ||||
import org.eclipse.jgit.revwalk.RevCommit; | import org.eclipse.jgit.revwalk.RevCommit; | ||||
} | } | ||||
@Test | @Test | ||||
public void testAddExistingSingleFileWithNewLine() throws IOException, | |||||
public void testAddExistingSingleSmallFileWithNewLine() throws IOException, | |||||
NoFilepatternException { | NoFilepatternException { | ||||
File file = new File(db.getWorkTree(), "a.txt"); | File file = new File(db.getWorkTree(), "a.txt"); | ||||
FileUtils.createNewFile(file); | FileUtils.createNewFile(file); | ||||
indexState(CONTENT)); | indexState(CONTENT)); | ||||
} | } | ||||
@Test | |||||
public void testAddExistingSingleMediumSizeFileWithNewLine() | |||||
throws IOException, NoFilepatternException { | |||||
File file = new File(db.getWorkTree(), "a.txt"); | |||||
FileUtils.createNewFile(file); | |||||
StringBuilder data = new StringBuilder(); | |||||
for (int i = 0; i < 1000; ++i) { | |||||
data.append("row1\r\nrow2"); | |||||
} | |||||
String crData = data.toString(); | |||||
PrintWriter writer = new PrintWriter(file); | |||||
writer.print(crData); | |||||
writer.close(); | |||||
String lfData = data.toString().replaceAll("\r", ""); | |||||
Git git = new Git(db); | |||||
db.getConfig().setString("core", null, "autocrlf", "false"); | |||||
git.add().addFilepattern("a.txt").call(); | |||||
assertEquals("[a.txt, mode:100644, content:" + data + "]", | |||||
indexState(CONTENT)); | |||||
db.getConfig().setString("core", null, "autocrlf", "true"); | |||||
git.add().addFilepattern("a.txt").call(); | |||||
assertEquals("[a.txt, mode:100644, content:" + lfData + "]", | |||||
indexState(CONTENT)); | |||||
db.getConfig().setString("core", null, "autocrlf", "input"); | |||||
git.add().addFilepattern("a.txt").call(); | |||||
assertEquals("[a.txt, mode:100644, content:" + lfData + "]", | |||||
indexState(CONTENT)); | |||||
} | |||||
@Test | @Test | ||||
public void testAddExistingSingleBinaryFile() throws IOException, | public void testAddExistingSingleBinaryFile() throws IOException, | ||||
NoFilepatternException { | NoFilepatternException { | ||||
assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0)); | assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0)); | ||||
} | } | ||||
@Test | |||||
public void testSubmoduleDeleteNotStagedWithUpdate() throws Exception { | |||||
Git git = new Git(db); | |||||
writeTrashFile("file.txt", "content"); | |||||
git.add().addFilepattern("file.txt").call(); | |||||
assertNotNull(git.commit().setMessage("create file").call()); | |||||
SubmoduleAddCommand command = new SubmoduleAddCommand(db); | |||||
String path = "sub"; | |||||
command.setPath(path); | |||||
String uri = db.getDirectory().toURI().toString(); | |||||
command.setURI(uri); | |||||
Repository repo = command.call(); | |||||
assertNotNull(repo); | |||||
assertNotNull(git.commit().setMessage("add submodule").call()); | |||||
assertTrue(git.status().call().isClean()); | |||||
FileUtils.delete(repo.getWorkTree(), FileUtils.RECURSIVE); | |||||
FileUtils.mkdir(new File(db.getWorkTree(), path), false); | |||||
assertNotNull(git.add().addFilepattern(".").setUpdate(true).call()); | |||||
Status status = git.status().call(); | |||||
assertFalse(status.isClean()); | |||||
assertTrue(status.getAdded().isEmpty()); | |||||
assertTrue(status.getChanged().isEmpty()); | |||||
assertTrue(status.getRemoved().isEmpty()); | |||||
assertTrue(status.getUntracked().isEmpty()); | |||||
assertTrue(status.getModified().isEmpty()); | |||||
assertEquals(1, status.getMissing().size()); | |||||
assertEquals(path, status.getMissing().iterator().next()); | |||||
} | |||||
private DirCacheEntry addEntryToBuilder(String path, File file, | private DirCacheEntry addEntryToBuilder(String path, File file, | ||||
ObjectInserter newObjectInserter, DirCacheBuilder builder, int stage) | ObjectInserter newObjectInserter, DirCacheBuilder builder, int stage) | ||||
throws IOException { | throws IOException { |
entry.setLength(sz); | entry.setLength(sz); | ||||
entry.setLastModified(f | entry.setLastModified(f | ||||
.getEntryLastModified()); | .getEntryLastModified()); | ||||
long contentSize = f | |||||
.getEntryContentLength(); | |||||
InputStream in = f.openEntryStream(); | InputStream in = f.openEntryStream(); | ||||
try { | try { | ||||
entry.setObjectId(inserter.insert( | entry.setObjectId(inserter.insert( | ||||
Constants.OBJ_BLOB, sz, in)); | |||||
Constants.OBJ_BLOB, contentSize, in)); | |||||
} finally { | } finally { | ||||
in.close(); | in.close(); | ||||
} | } |
// insert object | // insert object | ||||
if (inserter == null) | if (inserter == null) | ||||
inserter = repo.newObjectInserter(); | inserter = repo.newObjectInserter(); | ||||
long contentLength = fTree.getEntryContentLength(); | |||||
InputStream inputStream = fTree.openEntryStream(); | InputStream inputStream = fTree.openEntryStream(); | ||||
try { | try { | ||||
dcEntry.setObjectId(inserter.insert( | dcEntry.setObjectId(inserter.insert( | ||||
Constants.OBJ_BLOB, entryLength, | |||||
Constants.OBJ_BLOB, contentLength, | |||||
inputStream)); | inputStream)); | ||||
} finally { | } finally { | ||||
inputStream.close(); | inputStream.close(); |
entry.setLength(wtIter.getEntryLength()); | entry.setLength(wtIter.getEntryLength()); | ||||
entry.setLastModified(wtIter.getEntryLastModified()); | entry.setLastModified(wtIter.getEntryLastModified()); | ||||
entry.setFileMode(wtIter.getEntryFileMode()); | entry.setFileMode(wtIter.getEntryFileMode()); | ||||
long contentLength = wtIter.getEntryContentLength(); | |||||
InputStream in = wtIter.openEntryStream(); | InputStream in = wtIter.openEntryStream(); | ||||
try { | try { | ||||
entry.setObjectId(inserter.insert( | entry.setObjectId(inserter.insert( | ||||
Constants.OBJ_BLOB, | |||||
wtIter.getEntryLength(), in)); | |||||
Constants.OBJ_BLOB, contentLength, in)); | |||||
} finally { | } finally { | ||||
in.close(); | in.close(); | ||||
} | } |
@Override | @Override | ||||
public ObjectStream openStream() throws MissingObjectException, | public ObjectStream openStream() throws MissingObjectException, | ||||
IOException { | IOException { | ||||
long contentLength = ptr.getEntryContentLength(); | |||||
InputStream in = ptr.openEntryStream(); | InputStream in = ptr.openEntryStream(); | ||||
in = new BufferedInputStream(in); | in = new BufferedInputStream(in); | ||||
return new ObjectStream.Filter(getType(), getSize(), in); | |||||
return new ObjectStream.Filter(getType(), contentLength, in); | |||||
} | } | ||||
@Override | @Override |
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; | import org.eclipse.jgit.lib.CoreConfig; | ||||
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; | |||||
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.Repository; | import org.eclipse.jgit.lib.Repository; | ||||
/** Repository that is the root level being iterated over */ | /** Repository that is the root level being iterated over */ | ||||
protected Repository repository; | protected Repository repository; | ||||
/** Cached canonical length, initialized from {@link #idBuffer()} */ | |||||
private long canonLen = -1; | |||||
/** | /** | ||||
* Create a new iterator with no parent. | * Create a new iterator with no parent. | ||||
* | * | ||||
state.initializeDigestAndReadBuffer(); | state.initializeDigestAndReadBuffer(); | ||||
final long len = e.getLength(); | final long len = e.getLength(); | ||||
if (!mightNeedCleaning()) | |||||
return computeHash(is, len); | |||||
if (len <= MAXIMUM_FILE_SIZE_TO_READ_FULLY) { | |||||
ByteBuffer rawbuf = IO.readWholeStream(is, (int) len); | |||||
byte[] raw = rawbuf.array(); | |||||
int n = rawbuf.limit(); | |||||
if (!isBinary(raw, n)) { | |||||
rawbuf = filterClean(raw, n); | |||||
raw = rawbuf.array(); | |||||
n = rawbuf.limit(); | |||||
} | |||||
return computeHash(new ByteArrayInputStream(raw, 0, n), n); | |||||
} | |||||
if (isBinary(e)) | |||||
return computeHash(is, len); | |||||
final long canonLen; | |||||
final InputStream lenIs = filterClean(e.openInputStream()); | |||||
try { | |||||
canonLen = computeLength(lenIs); | |||||
} finally { | |||||
safeClose(lenIs); | |||||
} | |||||
return computeHash(filterClean(is), canonLen); | |||||
InputStream filteredIs = possiblyFilteredInputStream(e, is, len); | |||||
return computeHash(filteredIs, canonLen); | |||||
} finally { | } finally { | ||||
safeClose(is); | safeClose(is); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
private InputStream possiblyFilteredInputStream(final Entry e, | |||||
final InputStream is, final long len) throws IOException { | |||||
InputStream filteredIs; | |||||
if (!mightNeedCleaning()) { | |||||
filteredIs = is; | |||||
canonLen = len; | |||||
} else { | |||||
if (len <= MAXIMUM_FILE_SIZE_TO_READ_FULLY) { | |||||
ByteBuffer rawbuf = IO.readWholeStream(is, (int) len); | |||||
byte[] raw = rawbuf.array(); | |||||
int n = rawbuf.limit(); | |||||
if (!isBinary(raw, n)) { | |||||
rawbuf = filterClean(raw, n); | |||||
raw = rawbuf.array(); | |||||
n = rawbuf.limit(); | |||||
} | |||||
filteredIs = new ByteArrayInputStream(raw, 0, n); | |||||
canonLen = n; | |||||
} else { | |||||
if (isBinary(e)) { | |||||
filteredIs = is; | |||||
canonLen = len; | |||||
} else { | |||||
final InputStream lenIs = filterClean(e | |||||
.openInputStream()); | |||||
try { | |||||
canonLen = computeLength(lenIs); | |||||
} finally { | |||||
safeClose(lenIs); | |||||
} | |||||
filteredIs = filterClean(is); | |||||
} | |||||
} | |||||
} | |||||
return filteredIs; | |||||
} | |||||
private static void safeClose(final InputStream in) { | private static void safeClose(final InputStream in) { | ||||
try { | try { | ||||
in.close(); | in.close(); | ||||
@Override | @Override | ||||
public void next(final int delta) throws CorruptObjectException { | public void next(final int delta) throws CorruptObjectException { | ||||
ptr += delta; | ptr += delta; | ||||
if (!eof()) | |||||
if (!eof()) { | |||||
canonLen = -1; | |||||
parseEntry(); | parseEntry(); | ||||
} | |||||
} | } | ||||
@Override | @Override | ||||
} | } | ||||
/** | /** | ||||
* Get the byte length of this entry. | |||||
* Get the raw byte length of this entry. | |||||
* | * | ||||
* @return size of this file, in bytes. | * @return size of this file, in bytes. | ||||
*/ | */ | ||||
return current().getLength(); | return current().getLength(); | ||||
} | } | ||||
/** | |||||
* Get the filtered input length of this entry | |||||
* | |||||
* @return size of the content, in bytes | |||||
* @throws IOException | |||||
*/ | |||||
public long getEntryContentLength() throws IOException { | |||||
if (canonLen == -1) { | |||||
long rawLen = getEntryLength(); | |||||
if (rawLen == 0) | |||||
canonLen = 0; | |||||
InputStream is = current().openInputStream(); | |||||
try { | |||||
// canonLen gets updated here | |||||
possiblyFilteredInputStream(current(), is, current() | |||||
.getLength()); | |||||
} finally { | |||||
safeClose(is); | |||||
} | |||||
} | |||||
return canonLen; | |||||
} | |||||
/** | /** | ||||
* Get the last modified time of this entry. | * Get the last modified time of this entry. | ||||
* | * | ||||
*/ | */ | ||||
public InputStream openEntryStream() throws IOException { | public InputStream openEntryStream() throws IOException { | ||||
InputStream rawis = current().openInputStream(); | InputStream rawis = current().openInputStream(); | ||||
InputStream is; | |||||
if (getOptions().getAutoCRLF() != AutoCRLF.FALSE) | |||||
is = new EolCanonicalizingInputStream(rawis, true); | |||||
if (mightNeedCleaning()) | |||||
return filterClean(rawis); | |||||
else | else | ||||
is = rawis; | |||||
return is; | |||||
return rawis; | |||||
} | } | ||||
/** | /** |