* stable-3.4: JGit v3.4.2.201412180340-r ObjectChecker: Disallow names potentially mapping to ".git" on HFS+ ObjectChecker: Disallow Windows shortname "GIT~1" ObjectChecker: Disallow ".git." and ".git<space>" Always ignore case when forbidding .git in ObjectChecker DirCache: Refuse to read files with invalid paths DirCache: Replace isValidPath with DirCacheCheckout.checkValidPath Replace "a." with "a-" in unit tests Apache HttpClientConnection: replace calls to deprecated LocalFile() Fix two nits about DirCacheEntry constructors Detect buffering failures while writing rebase todo file Deprecate TemporaryBuffer.LocalFile without parent directory Switch FileHeader.extractFileLines to TemporaryBuffer.Heap AmazonS3: Buffer pushed pack content under $GIT_DIR DirCache: Buffer TREE extension to $GIT_DIR Change-Id: I398cf40b006a05a6537788fc6eb1f84df1ed8814 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>tags/v3.5.3.201412180710-r
public void setFixedLengthStreamingMode(int contentLength) { | public void setFixedLengthStreamingMode(int contentLength) { | ||||
if (entity != null) | if (entity != null) | ||||
throw new IllegalArgumentException(); | throw new IllegalArgumentException(); | ||||
entity = new TemporaryBufferEntity(new LocalFile()); | |||||
entity = new TemporaryBufferEntity(new LocalFile(null)); | |||||
entity.setContentLength(contentLength); | entity.setContentLength(contentLength); | ||||
} | } | ||||
public OutputStream getOutputStream() throws IOException { | public OutputStream getOutputStream() throws IOException { | ||||
if (entity == null) | if (entity == null) | ||||
entity = new TemporaryBufferEntity(new LocalFile()); | |||||
entity = new TemporaryBufferEntity(new LocalFile(null)); | |||||
return entity.getBuffer(); | return entity.getBuffer(); | ||||
} | } | ||||
public void setChunkedStreamingMode(int chunklen) { | public void setChunkedStreamingMode(int chunklen) { | ||||
if (entity == null) | if (entity == null) | ||||
entity = new TemporaryBufferEntity(new LocalFile()); | |||||
entity = new TemporaryBufferEntity(new LocalFile(null)); | |||||
entity.setChunked(true); | entity.setChunked(true); | ||||
} | } | ||||
buf.destroy(); | buf.destroy(); | ||||
} | } | ||||
commitId = line.substring("commit ".length()); | commitId = line.substring("commit ".length()); | ||||
buf = new TemporaryBuffer.LocalFile(); | |||||
buf = new TemporaryBuffer.LocalFile(null); | |||||
} else if (buf != null) { | } else if (buf != null) { | ||||
buf.write(line.getBytes("ISO-8859-1")); | buf.write(line.getBytes("ISO-8859-1")); | ||||
buf.write('\n'); | buf.write('\n'); |
import static org.junit.Assert.assertFalse; | 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.assertTrue; | ||||
import static org.junit.Assert.fail; | |||||
import java.io.File; | import java.io.File; | ||||
import java.text.MessageFormat; | |||||
import org.eclipse.jgit.errors.CorruptObjectException; | |||||
import org.eclipse.jgit.internal.JGitText; | |||||
import org.eclipse.jgit.junit.MockSystemReader; | |||||
import org.eclipse.jgit.junit.RepositoryTestCase; | import org.eclipse.jgit.junit.RepositoryTestCase; | ||||
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.ObjectInserter; | |||||
import org.eclipse.jgit.util.SystemReader; | |||||
import org.junit.Test; | import org.junit.Test; | ||||
public class DirCacheBasicTest extends RepositoryTestCase { | public class DirCacheBasicTest extends RepositoryTestCase { | ||||
public void testBuildThenClear() throws Exception { | public void testBuildThenClear() throws Exception { | ||||
final DirCache dc = db.readDirCache(); | final DirCache dc = db.readDirCache(); | ||||
final String[] paths = { "a.", "a.b", "a/b", "a0b" }; | |||||
final String[] paths = { "a-", "a.b", "a/b", "a0b" }; | |||||
final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | ||||
for (int i = 0; i < paths.length; i++) { | for (int i = 0; i < paths.length; i++) { | ||||
ents[i] = new DirCacheEntry(paths[i]); | ents[i] = new DirCacheEntry(paths[i]); | ||||
final byte[] path = Constants.encode("a"); | final byte[] path = Constants.encode("a"); | ||||
assertEquals(-1, dc.findEntry(path, path.length)); | assertEquals(-1, dc.findEntry(path, path.length)); | ||||
} | } | ||||
@Test | |||||
public void testRejectInvalidWindowsPaths() throws Exception { | |||||
SystemReader.setInstance(new MockSystemReader() { | |||||
{ | |||||
setUnix(); | |||||
} | |||||
}); | |||||
String path = "src/con.txt"; | |||||
DirCache dc = db.lockDirCache(); | |||||
DirCacheBuilder b = dc.builder(); | |||||
DirCacheEntry e = new DirCacheEntry(path); | |||||
e.setFileMode(FileMode.REGULAR_FILE); | |||||
e.setObjectId(new ObjectInserter.Formatter().idFor( | |||||
Constants.OBJ_BLOB, | |||||
Constants.encode(path))); | |||||
b.add(e); | |||||
b.commit(); | |||||
db.readDirCache(); | |||||
SystemReader.setInstance(new MockSystemReader() { | |||||
{ | |||||
setWindows(); | |||||
} | |||||
}); | |||||
try { | |||||
db.readDirCache(); | |||||
fail("should have rejected " + path); | |||||
} catch (CorruptObjectException err) { | |||||
assertEquals(MessageFormat.format(JGitText.get().invalidPath, path), | |||||
err.getMessage()); | |||||
assertNotNull(err.getCause()); | |||||
assertEquals("invalid name 'CON'", err.getCause().getMessage()); | |||||
} | |||||
} | |||||
} | } |
final DirCache dc = db.readDirCache(); | final DirCache dc = db.readDirCache(); | ||||
final FileMode mode = FileMode.REGULAR_FILE; | final FileMode mode = FileMode.REGULAR_FILE; | ||||
final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" }; | |||||
final String[] paths = { "a-", "a/b", "a/c", "a/d", "a0b" }; | |||||
final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | ||||
for (int i = 0; i < paths.length; i++) { | for (int i = 0; i < paths.length; i++) { | ||||
ents[i] = new DirCacheEntry(paths[i]); | ents[i] = new DirCacheEntry(paths[i]); |
public void testAdd_InGitSortOrder() throws Exception { | public void testAdd_InGitSortOrder() throws Exception { | ||||
final DirCache dc = db.readDirCache(); | final DirCache dc = db.readDirCache(); | ||||
final String[] paths = { "a.", "a.b", "a/b", "a0b" }; | |||||
final String[] paths = { "a-", "a.b", "a/b", "a0b" }; | |||||
final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | ||||
for (int i = 0; i < paths.length; i++) { | for (int i = 0; i < paths.length; i++) { | ||||
ents[i] = new DirCacheEntry(paths[i]); | ents[i] = new DirCacheEntry(paths[i]); | ||||
public void testAdd_ReverseGitSortOrder() throws Exception { | public void testAdd_ReverseGitSortOrder() throws Exception { | ||||
final DirCache dc = db.readDirCache(); | final DirCache dc = db.readDirCache(); | ||||
final String[] paths = { "a.", "a.b", "a/b", "a0b" }; | |||||
final String[] paths = { "a-", "a.b", "a/b", "a0b" }; | |||||
final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | ||||
for (int i = 0; i < paths.length; i++) { | for (int i = 0; i < paths.length; i++) { | ||||
ents[i] = new DirCacheEntry(paths[i]); | ents[i] = new DirCacheEntry(paths[i]); | ||||
public void testBuilderClear() throws Exception { | public void testBuilderClear() throws Exception { | ||||
final DirCache dc = db.readDirCache(); | final DirCache dc = db.readDirCache(); | ||||
final String[] paths = { "a.", "a.b", "a/b", "a0b" }; | |||||
final String[] paths = { "a-", "a.b", "a/b", "a0b" }; | |||||
final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | ||||
for (int i = 0; i < paths.length; i++) { | for (int i = 0; i < paths.length; i++) { | ||||
ents[i] = new DirCacheEntry(paths[i]); | ents[i] = new DirCacheEntry(paths[i]); |
assertV3TreeEntry(9, "newfile.txt", false, true, dc); | assertV3TreeEntry(9, "newfile.txt", false, true, dc); | ||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream(); | final ByteArrayOutputStream bos = new ByteArrayOutputStream(); | ||||
dc.writeTo(bos); | |||||
dc.writeTo(null, bos); | |||||
final byte[] indexBytes = bos.toByteArray(); | final byte[] indexBytes = bos.toByteArray(); | ||||
final byte[] expectedBytes = IO.readFully(file); | final byte[] expectedBytes = IO.readFully(file); | ||||
assertArrayEquals(expectedBytes, indexBytes); | assertArrayEquals(expectedBytes, indexBytes); |
import static org.junit.Assert.assertTrue; | import static org.junit.Assert.assertTrue; | ||||
import static org.junit.Assert.fail; | import static org.junit.Assert.fail; | ||||
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; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
} | } | ||||
private static boolean isValidPath(final String path) { | private static boolean isValidPath(final String path) { | ||||
return DirCacheEntry.isValidPath(Constants.encode(path)); | |||||
try { | |||||
DirCacheCheckout.checkValidPath(path); | |||||
return true; | |||||
} catch (InvalidPathException e) { | |||||
return false; | |||||
} | |||||
} | } | ||||
@Test | @Test |
public void testEntriesWithin() throws Exception { | public void testEntriesWithin() throws Exception { | ||||
final DirCache dc = db.readDirCache(); | final DirCache dc = db.readDirCache(); | ||||
final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" }; | |||||
final String[] paths = { "a-", "a/b", "a/c", "a/d", "a0b" }; | |||||
final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | ||||
for (int i = 0; i < paths.length; i++) { | for (int i = 0; i < paths.length; i++) { | ||||
ents[i] = new DirCacheEntry(paths[i]); | ents[i] = new DirCacheEntry(paths[i]); | ||||
assertSame(ents[i], aContents[i]); | assertSame(ents[i], aContents[i]); | ||||
} | } | ||||
assertNotNull(dc.getEntriesWithin("a.")); | |||||
assertEquals(0, dc.getEntriesWithin("a.").length); | |||||
assertNotNull(dc.getEntriesWithin("a-")); | |||||
assertEquals(0, dc.getEntriesWithin("a-").length); | |||||
assertNotNull(dc.getEntriesWithin("a0b")); | assertNotNull(dc.getEntriesWithin("a0b")); | ||||
assertEquals(0, dc.getEntriesWithin("a0b.").length); | |||||
assertEquals(0, dc.getEntriesWithin("a0b-").length); | |||||
assertNotNull(dc.getEntriesWithin("zoo")); | assertNotNull(dc.getEntriesWithin("zoo")); | ||||
assertEquals(0, dc.getEntriesWithin("zoo.").length); | |||||
assertEquals(0, dc.getEntriesWithin("zoo-").length); | |||||
} | } | ||||
} | } |
public void testNoSubtree_NoTreeWalk() throws Exception { | public void testNoSubtree_NoTreeWalk() throws Exception { | ||||
final DirCache dc = DirCache.newInCore(); | final DirCache dc = DirCache.newInCore(); | ||||
final String[] paths = { "a.", "a0b" }; | |||||
final String[] paths = { "a-", "a0b" }; | |||||
final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | ||||
for (int i = 0; i < paths.length; i++) { | for (int i = 0; i < paths.length; i++) { | ||||
ents[i] = new DirCacheEntry(paths[i]); | ents[i] = new DirCacheEntry(paths[i]); | ||||
public void testNoSubtree_WithTreeWalk() throws Exception { | public void testNoSubtree_WithTreeWalk() throws Exception { | ||||
final DirCache dc = DirCache.newInCore(); | final DirCache dc = DirCache.newInCore(); | ||||
final String[] paths = { "a.", "a0b" }; | |||||
final String[] paths = { "a-", "a0b" }; | |||||
final FileMode[] modes = { FileMode.EXECUTABLE_FILE, FileMode.GITLINK }; | final FileMode[] modes = { FileMode.EXECUTABLE_FILE, FileMode.GITLINK }; | ||||
final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | ||||
for (int i = 0; i < paths.length; i++) { | for (int i = 0; i < paths.length; i++) { | ||||
public void testSingleSubtree_NoRecursion() throws Exception { | public void testSingleSubtree_NoRecursion() throws Exception { | ||||
final DirCache dc = DirCache.newInCore(); | final DirCache dc = DirCache.newInCore(); | ||||
final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" }; | |||||
final String[] paths = { "a-", "a/b", "a/c", "a/d", "a0b" }; | |||||
final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | ||||
for (int i = 0; i < paths.length; i++) { | for (int i = 0; i < paths.length; i++) { | ||||
ents[i] = new DirCacheEntry(paths[i]); | ents[i] = new DirCacheEntry(paths[i]); | ||||
b.add(ents[i]); | b.add(ents[i]); | ||||
b.finish(); | b.finish(); | ||||
final String[] expPaths = { "a.", "a", "a0b" }; | |||||
final String[] expPaths = { "a-", "a", "a0b" }; | |||||
final FileMode[] expModes = { FileMode.REGULAR_FILE, FileMode.TREE, | final FileMode[] expModes = { FileMode.REGULAR_FILE, FileMode.TREE, | ||||
FileMode.REGULAR_FILE }; | FileMode.REGULAR_FILE }; | ||||
final int expPos[] = { 0, -1, 4 }; | final int expPos[] = { 0, -1, 4 }; | ||||
final DirCache dc = DirCache.newInCore(); | final DirCache dc = DirCache.newInCore(); | ||||
final FileMode mode = FileMode.REGULAR_FILE; | final FileMode mode = FileMode.REGULAR_FILE; | ||||
final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" }; | |||||
final String[] paths = { "a-", "a/b", "a/c", "a/d", "a0b" }; | |||||
final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | ||||
for (int i = 0; i < paths.length; i++) { | for (int i = 0; i < paths.length; i++) { | ||||
ents[i] = new DirCacheEntry(paths[i]); | ents[i] = new DirCacheEntry(paths[i]); | ||||
final DirCache dc = DirCache.newInCore(); | final DirCache dc = DirCache.newInCore(); | ||||
final FileMode mode = FileMode.REGULAR_FILE; | final FileMode mode = FileMode.REGULAR_FILE; | ||||
final String[] paths = { "a.", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" }; | |||||
final String[] paths = { "a-", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" }; | |||||
final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | ||||
for (int i = 0; i < paths.length; i++) { | for (int i = 0; i < paths.length; i++) { | ||||
ents[i] = new DirCacheEntry(paths[i]); | ents[i] = new DirCacheEntry(paths[i]); | ||||
final DirCache dc = DirCache.newInCore(); | final DirCache dc = DirCache.newInCore(); | ||||
final FileMode mode = FileMode.REGULAR_FILE; | final FileMode mode = FileMode.REGULAR_FILE; | ||||
final String[] paths = { "a.", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" }; | |||||
final String[] paths = { "a-", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" }; | |||||
final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | ||||
for (int i = 0; i < paths.length; i++) { | for (int i = 0; i < paths.length; i++) { | ||||
ents[i] = new DirCacheEntry(paths[i]); | ents[i] = new DirCacheEntry(paths[i]); | ||||
DirCacheIterator dci = new DirCacheIterator(dc); | DirCacheIterator dci = new DirCacheIterator(dc); | ||||
assertFalse(dci.eof()); | assertFalse(dci.eof()); | ||||
assertEquals("a.", dci.getEntryPathString()); | |||||
assertEquals("a-", dci.getEntryPathString()); | |||||
dci.next(1); | dci.next(1); | ||||
assertFalse(dci.eof()); | assertFalse(dci.eof()); | ||||
assertEquals("a", dci.getEntryPathString()); | assertEquals("a", dci.getEntryPathString()); | ||||
// same entries the second time | // same entries the second time | ||||
dci.reset(); | dci.reset(); | ||||
assertFalse(dci.eof()); | assertFalse(dci.eof()); | ||||
assertEquals("a.", dci.getEntryPathString()); | |||||
assertEquals("a-", dci.getEntryPathString()); | |||||
dci.next(1); | dci.next(1); | ||||
assertFalse(dci.eof()); | assertFalse(dci.eof()); | ||||
assertEquals("a", dci.getEntryPathString()); | assertEquals("a", dci.getEntryPathString()); | ||||
assertEquals("a", dci.getEntryPathString()); | assertEquals("a", dci.getEntryPathString()); | ||||
dci.back(1); | dci.back(1); | ||||
assertFalse(dci.eof()); | assertFalse(dci.eof()); | ||||
assertEquals("a.", dci.getEntryPathString()); | |||||
assertEquals("a-", dci.getEntryPathString()); | |||||
assertTrue(dci.first()); | assertTrue(dci.first()); | ||||
// forward | // forward | ||||
assertFalse(dci.eof()); | assertFalse(dci.eof()); | ||||
assertEquals("a.", dci.getEntryPathString()); | |||||
assertEquals("a-", dci.getEntryPathString()); | |||||
dci.next(1); | dci.next(1); | ||||
assertFalse(dci.eof()); | assertFalse(dci.eof()); | ||||
assertEquals("a", dci.getEntryPathString()); | assertEquals("a", dci.getEntryPathString()); | ||||
final DirCache dc = DirCache.newInCore(); | final DirCache dc = DirCache.newInCore(); | ||||
final FileMode mode = FileMode.REGULAR_FILE; | final FileMode mode = FileMode.REGULAR_FILE; | ||||
final String[] paths = { "a.", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" }; | |||||
final String[] paths = { "a-", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" }; | |||||
final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | ||||
for (int i = 0; i < paths.length; i++) { | for (int i = 0; i < paths.length; i++) { | ||||
ents[i] = new DirCacheEntry(paths[i]); | ents[i] = new DirCacheEntry(paths[i]); |
DirCache dc = DirCache.newInCore(); | DirCache dc = DirCache.newInCore(); | ||||
DirCacheEditor editor = dc.editor(); | DirCacheEditor editor = dc.editor(); | ||||
editor.add(new AddEdit("a/b")); | editor.add(new AddEdit("a/b")); | ||||
editor.add(new AddEdit("a.")); | |||||
editor.add(new AddEdit("a-")); | |||||
editor.add(new AddEdit("ab")); | editor.add(new AddEdit("ab")); | ||||
editor.finish(); | editor.finish(); | ||||
assertEquals(3, dc.getEntryCount()); | assertEquals(3, dc.getEntryCount()); | ||||
// Validate sort order | // Validate sort order | ||||
assertEquals("a.", dc.getEntry(0).getPathString()); | |||||
assertEquals("a-", dc.getEntry(0).getPathString()); | |||||
assertEquals("a/b", dc.getEntry(1).getPathString()); | assertEquals("a/b", dc.getEntry(1).getPathString()); | ||||
assertEquals("ab", dc.getEntry(2).getPathString()); | assertEquals("ab", dc.getEntry(2).getPathString()); | ||||
editor = dc.editor(); | editor = dc.editor(); | ||||
// Sort order should not confuse DeleteTree | // Sort order should not confuse DeleteTree | ||||
editor.add(new DirCacheEditor.DeleteTree("a")); | editor.add(new DirCacheEditor.DeleteTree("a")); | ||||
editor.finish(); | editor.finish(); | ||||
assertEquals(2, dc.getEntryCount()); | assertEquals(2, dc.getEntryCount()); | ||||
assertEquals("a.", dc.getEntry(0).getPathString()); | |||||
assertEquals("a-", dc.getEntry(0).getPathString()); | |||||
assertEquals("ab", dc.getEntry(1).getPathString()); | assertEquals("ab", dc.getEntry(1).getPathString()); | ||||
} | } | ||||
public void testSingleSubtree() throws Exception { | public void testSingleSubtree() throws Exception { | ||||
final DirCache dc = db.readDirCache(); | final DirCache dc = db.readDirCache(); | ||||
final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" }; | |||||
final String[] paths = { "a-", "a/b", "a/c", "a/d", "a0b" }; | |||||
final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | ||||
for (int i = 0; i < paths.length; i++) { | for (int i = 0; i < paths.length; i++) { | ||||
ents[i] = new DirCacheEntry(paths[i]); | ents[i] = new DirCacheEntry(paths[i]); | ||||
public void testTwoLevelSubtree() throws Exception { | public void testTwoLevelSubtree() throws Exception { | ||||
final DirCache dc = db.readDirCache(); | final DirCache dc = db.readDirCache(); | ||||
final String[] paths = { "a.", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" }; | |||||
final String[] paths = { "a-", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" }; | |||||
final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | ||||
for (int i = 0; i < paths.length; i++) { | for (int i = 0; i < paths.length; i++) { | ||||
ents[i] = new DirCacheEntry(paths[i]); | ents[i] = new DirCacheEntry(paths[i]); | ||||
final String A = String.format("a%2000s", "a"); | final String A = String.format("a%2000s", "a"); | ||||
final String B = String.format("b%2000s", "b"); | final String B = String.format("b%2000s", "b"); | ||||
final String[] paths = { A + ".", A + "." + B, A + "/" + B, A + "0" + B }; | |||||
final String[] paths = { A + "-", A + "-" + B, A + "/" + B, A + "0" + B }; | |||||
final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; | ||||
for (int i = 0; i < paths.length; i++) { | for (int i = 0; i < paths.length; i++) { | ||||
ents[i] = new DirCacheEntry(paths[i]); | ents[i] = new DirCacheEntry(paths[i]); |
@Test | @Test | ||||
public void testMaliciousGitPathEndSpaceUnixOk() throws Exception { | public void testMaliciousGitPathEndSpaceUnixOk() throws Exception { | ||||
if (File.separatorChar == '\\') | |||||
return; // cannot emulate Unix on Windows for this test | |||||
((MockSystemReader) SystemReader.getInstance()).setUnix(); | |||||
testMaliciousPathGoodFirstCheckout(".git ", "konfig"); | |||||
testMaliciousPathBadFirstCheckout(".git ", "konfig"); | |||||
} | } | ||||
@Test | @Test | ||||
@Test | @Test | ||||
public void testMaliciousGitPathEndDotUnixOk() throws Exception { | public void testMaliciousGitPathEndDotUnixOk() throws Exception { | ||||
if (File.separatorChar == '\\') | |||||
return; // cannot emulate Unix on Windows for this test | |||||
((MockSystemReader) SystemReader.getInstance()).setUnix(); | |||||
testMaliciousPathGoodFirstCheckout(".git.", "konfig"); | |||||
testMaliciousPathBadFirstCheckout(".git.", "konfig"); | |||||
} | } | ||||
@Test | @Test |
} | } | ||||
@Test | @Test | ||||
public void testInvalidTreeNameIsMixedCaseGitWindows() { | |||||
public void testInvalidTreeNameIsMixedCaseGit() { | |||||
StringBuilder b = new StringBuilder(); | StringBuilder b = new StringBuilder(); | ||||
entry(b, "100644 .GiT"); | entry(b, "100644 .GiT"); | ||||
byte[] data = Constants.encodeASCII(b.toString()); | byte[] data = Constants.encodeASCII(b.toString()); | ||||
try { | try { | ||||
checker.setSafeForWindows(true); | |||||
checker.checkTree(data); | checker.checkTree(data); | ||||
fail("incorrectly accepted an invalid tree"); | fail("incorrectly accepted an invalid tree"); | ||||
} catch (CorruptObjectException e) { | } catch (CorruptObjectException e) { | ||||
} | } | ||||
@Test | @Test | ||||
public void testInvalidTreeNameIsMixedCaseGitMacOS() { | |||||
public void testInvalidTreeNameIsMacHFSGit() { | |||||
StringBuilder b = new StringBuilder(); | StringBuilder b = new StringBuilder(); | ||||
entry(b, "100644 .GiT"); | |||||
byte[] data = Constants.encodeASCII(b.toString()); | |||||
entry(b, "100644 .gi\u200Ct"); | |||||
byte[] data = Constants.encode(b.toString()); | |||||
try { | try { | ||||
checker.setSafeForMacOS(true); | checker.setSafeForMacOS(true); | ||||
checker.checkTree(data); | checker.checkTree(data); | ||||
fail("incorrectly accepted an invalid tree"); | fail("incorrectly accepted an invalid tree"); | ||||
} catch (CorruptObjectException e) { | } catch (CorruptObjectException e) { | ||||
assertEquals("invalid name '.GiT'", e.getMessage()); | |||||
assertEquals( | |||||
"invalid name '.gi\u200Ct' contains ignorable Unicode characters", | |||||
e.getMessage()); | |||||
} | |||||
} | |||||
@Test | |||||
public void testInvalidTreeNameIsMacHFSGit2() { | |||||
StringBuilder b = new StringBuilder(); | |||||
entry(b, "100644 \u206B.git"); | |||||
byte[] data = Constants.encode(b.toString()); | |||||
try { | |||||
checker.setSafeForMacOS(true); | |||||
checker.checkTree(data); | |||||
fail("incorrectly accepted an invalid tree"); | |||||
} catch (CorruptObjectException e) { | |||||
assertEquals( | |||||
"invalid name '\u206B.git' contains ignorable Unicode characters", | |||||
e.getMessage()); | |||||
} | |||||
} | |||||
@Test | |||||
public void testInvalidTreeNameIsMacHFSGit3() { | |||||
StringBuilder b = new StringBuilder(); | |||||
entry(b, "100644 .git\uFEFF"); | |||||
byte[] data = Constants.encode(b.toString()); | |||||
try { | |||||
checker.setSafeForMacOS(true); | |||||
checker.checkTree(data); | |||||
fail("incorrectly accepted an invalid tree"); | |||||
} catch (CorruptObjectException e) { | |||||
assertEquals( | |||||
"invalid name '.git\uFEFF' contains ignorable Unicode characters", | |||||
e.getMessage()); | |||||
} | |||||
} | |||||
private static byte[] concat(byte[] b1, byte[] b2) { | |||||
byte[] data = new byte[b1.length + b2.length]; | |||||
System.arraycopy(b1, 0, data, 0, b1.length); | |||||
System.arraycopy(b2, 0, data, b1.length, b2.length); | |||||
return data; | |||||
} | |||||
@Test | |||||
public void testInvalidTreeNameIsMacHFSGitCorruptUTF8AtEnd() { | |||||
byte[] data = concat(Constants.encode("100644 .git"), | |||||
new byte[] { (byte) 0xef }); | |||||
StringBuilder b = new StringBuilder(); | |||||
entry(b, ""); | |||||
data = concat(data, Constants.encode(b.toString())); | |||||
try { | |||||
checker.setSafeForMacOS(true); | |||||
checker.checkTree(data); | |||||
fail("incorrectly accepted an invalid tree"); | |||||
} catch (CorruptObjectException e) { | |||||
assertEquals( | |||||
"invalid name contains byte sequence '0xef' which is not a valid UTF-8 character", | |||||
e.getMessage()); | |||||
} | |||||
} | |||||
@Test | |||||
public void testInvalidTreeNameIsMacHFSGitCorruptUTF8AtEnd2() { | |||||
byte[] data = concat(Constants.encode("100644 .git"), new byte[] { | |||||
(byte) 0xe2, (byte) 0xab }); | |||||
StringBuilder b = new StringBuilder(); | |||||
entry(b, ""); | |||||
data = concat(data, Constants.encode(b.toString())); | |||||
try { | |||||
checker.setSafeForMacOS(true); | |||||
checker.checkTree(data); | |||||
fail("incorrectly accepted an invalid tree"); | |||||
} catch (CorruptObjectException e) { | |||||
assertEquals( | |||||
"invalid name contains byte sequence '0xe2ab' which is not a valid UTF-8 character", | |||||
e.getMessage()); | |||||
} | |||||
} | |||||
@Test | |||||
public void testInvalidTreeNameIsNotMacHFSGit() | |||||
throws CorruptObjectException { | |||||
StringBuilder b = new StringBuilder(); | |||||
entry(b, "100644 .git\u200Cx"); | |||||
byte[] data = Constants.encode(b.toString()); | |||||
checker.setSafeForMacOS(true); | |||||
checker.checkTree(data); | |||||
} | |||||
@Test | |||||
public void testInvalidTreeNameIsNotMacHFSGit2() | |||||
throws CorruptObjectException { | |||||
StringBuilder b = new StringBuilder(); | |||||
entry(b, "100644 .kit\u200C"); | |||||
byte[] data = Constants.encode(b.toString()); | |||||
checker.setSafeForMacOS(true); | |||||
checker.checkTree(data); | |||||
} | |||||
@Test | |||||
public void testInvalidTreeNameIsNotMacHFSGitOtherPlatform() | |||||
throws CorruptObjectException { | |||||
StringBuilder b = new StringBuilder(); | |||||
entry(b, "100644 .git\u200C"); | |||||
byte[] data = Constants.encode(b.toString()); | |||||
checker.checkTree(data); | |||||
} | |||||
@Test | |||||
public void testInvalidTreeNameIsDotGitDot() { | |||||
StringBuilder b = new StringBuilder(); | |||||
entry(b, "100644 .git."); | |||||
byte[] data = Constants.encodeASCII(b.toString()); | |||||
try { | |||||
checker.checkTree(data); | |||||
fail("incorrectly accepted an invalid tree"); | |||||
} catch (CorruptObjectException e) { | |||||
assertEquals("invalid name '.git.'", e.getMessage()); | |||||
} | |||||
} | |||||
@Test | |||||
public void testValidTreeNameIsDotGitDotDot() | |||||
throws CorruptObjectException { | |||||
StringBuilder b = new StringBuilder(); | |||||
entry(b, "100644 .git.."); | |||||
checker.checkTree(Constants.encodeASCII(b.toString())); | |||||
} | |||||
@Test | |||||
public void testInvalidTreeNameIsDotGitSpace() { | |||||
StringBuilder b = new StringBuilder(); | |||||
entry(b, "100644 .git "); | |||||
byte[] data = Constants.encodeASCII(b.toString()); | |||||
try { | |||||
checker.checkTree(data); | |||||
fail("incorrectly accepted an invalid tree"); | |||||
} catch (CorruptObjectException e) { | |||||
assertEquals("invalid name '.git '", e.getMessage()); | |||||
} | } | ||||
} | } | ||||
@Test | |||||
public void testInvalidTreeNameIsDotGitSomething() | |||||
throws CorruptObjectException { | |||||
StringBuilder b = new StringBuilder(); | |||||
entry(b, "100644 .gitfoobar"); | |||||
byte[] data = Constants.encodeASCII(b.toString()); | |||||
checker.checkTree(data); | |||||
} | |||||
@Test | |||||
public void testInvalidTreeNameIsDotGitSomethingSpaceSomething() | |||||
throws CorruptObjectException { | |||||
StringBuilder b = new StringBuilder(); | |||||
entry(b, "100644 .gitfoo bar"); | |||||
byte[] data = Constants.encodeASCII(b.toString()); | |||||
checker.checkTree(data); | |||||
} | |||||
@Test | |||||
public void testInvalidTreeNameIsDotGitSomethingDot() | |||||
throws CorruptObjectException { | |||||
StringBuilder b = new StringBuilder(); | |||||
entry(b, "100644 .gitfoobar."); | |||||
byte[] data = Constants.encodeASCII(b.toString()); | |||||
checker.checkTree(data); | |||||
} | |||||
@Test | |||||
public void testInvalidTreeNameIsDotGitSomethingDotDot() | |||||
throws CorruptObjectException { | |||||
StringBuilder b = new StringBuilder(); | |||||
entry(b, "100644 .gitfoobar.."); | |||||
byte[] data = Constants.encodeASCII(b.toString()); | |||||
checker.checkTree(data); | |||||
} | |||||
@Test | |||||
public void testInvalidTreeNameIsDotGitDotSpace() { | |||||
StringBuilder b = new StringBuilder(); | |||||
entry(b, "100644 .git. "); | |||||
byte[] data = Constants.encodeASCII(b.toString()); | |||||
try { | |||||
checker.checkTree(data); | |||||
fail("incorrectly accepted an invalid tree"); | |||||
} catch (CorruptObjectException e) { | |||||
assertEquals("invalid name '.git. '", e.getMessage()); | |||||
} | |||||
} | |||||
@Test | |||||
public void testInvalidTreeNameIsDotGitSpaceDot() { | |||||
StringBuilder b = new StringBuilder(); | |||||
entry(b, "100644 .git . "); | |||||
byte[] data = Constants.encodeASCII(b.toString()); | |||||
try { | |||||
checker.checkTree(data); | |||||
fail("incorrectly accepted an invalid tree"); | |||||
} catch (CorruptObjectException e) { | |||||
assertEquals("invalid name '.git . '", e.getMessage()); | |||||
} | |||||
} | |||||
@Test | |||||
public void testInvalidTreeNameIsGITTilde1() { | |||||
StringBuilder b = new StringBuilder(); | |||||
entry(b, "100644 GIT~1"); | |||||
byte[] data = Constants.encodeASCII(b.toString()); | |||||
try { | |||||
checker.checkTree(data); | |||||
fail("incorrectly accepted an invalid tree"); | |||||
} catch (CorruptObjectException e) { | |||||
assertEquals("invalid name 'GIT~1'", e.getMessage()); | |||||
} | |||||
} | |||||
@Test | |||||
public void testInvalidTreeNameIsGiTTilde1() { | |||||
StringBuilder b = new StringBuilder(); | |||||
entry(b, "100644 GiT~1"); | |||||
byte[] data = Constants.encodeASCII(b.toString()); | |||||
try { | |||||
checker.checkTree(data); | |||||
fail("incorrectly accepted an invalid tree"); | |||||
} catch (CorruptObjectException e) { | |||||
assertEquals("invalid name 'GiT~1'", e.getMessage()); | |||||
} | |||||
} | |||||
@Test | |||||
public void testValidTreeNameIsGitTilde11() throws CorruptObjectException { | |||||
StringBuilder b = new StringBuilder(); | |||||
entry(b, "100644 GIT~11"); | |||||
byte[] data = Constants.encodeASCII(b.toString()); | |||||
checker.checkTree(data); | |||||
} | |||||
@Test | @Test | ||||
public void testInvalidTreeTruncatedInName() { | public void testInvalidTreeTruncatedInName() { | ||||
final StringBuilder b = new StringBuilder(); | final StringBuilder b = new StringBuilder(); |
} | } | ||||
// less obvious due to git sorting order | // less obvious due to git sorting order | ||||
filter.include(fakeWalk("d.")); | |||||
filter.include(fakeWalk("d-")); | |||||
// less obvious due to git sorting order | // less obvious due to git sorting order | ||||
try { | try { |
public class TemporaryBufferTest { | public class TemporaryBufferTest { | ||||
@Test | @Test | ||||
public void testEmpty() throws IOException { | public void testEmpty() throws IOException { | ||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(); | |||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(null); | |||||
try { | try { | ||||
b.close(); | b.close(); | ||||
assertEquals(0, b.length()); | assertEquals(0, b.length()); | ||||
@Test | @Test | ||||
public void testOneByte() throws IOException { | public void testOneByte() throws IOException { | ||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(); | |||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(null); | |||||
final byte test = (byte) new TestRng(getName()).nextInt(); | final byte test = (byte) new TestRng(getName()).nextInt(); | ||||
try { | try { | ||||
b.write(test); | b.write(test); | ||||
@Test | @Test | ||||
public void testOneBlock_BulkWrite() throws IOException { | public void testOneBlock_BulkWrite() throws IOException { | ||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(); | |||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(null); | |||||
final byte[] test = new TestRng(getName()) | final byte[] test = new TestRng(getName()) | ||||
.nextBytes(TemporaryBuffer.Block.SZ); | .nextBytes(TemporaryBuffer.Block.SZ); | ||||
try { | try { | ||||
@Test | @Test | ||||
public void testOneBlockAndHalf_BulkWrite() throws IOException { | public void testOneBlockAndHalf_BulkWrite() throws IOException { | ||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(); | |||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(null); | |||||
final byte[] test = new TestRng(getName()) | final byte[] test = new TestRng(getName()) | ||||
.nextBytes(TemporaryBuffer.Block.SZ * 3 / 2); | .nextBytes(TemporaryBuffer.Block.SZ * 3 / 2); | ||||
try { | try { | ||||
@Test | @Test | ||||
public void testOneBlockAndHalf_SingleWrite() throws IOException { | public void testOneBlockAndHalf_SingleWrite() throws IOException { | ||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(); | |||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(null); | |||||
final byte[] test = new TestRng(getName()) | final byte[] test = new TestRng(getName()) | ||||
.nextBytes(TemporaryBuffer.Block.SZ * 3 / 2); | .nextBytes(TemporaryBuffer.Block.SZ * 3 / 2); | ||||
try { | try { | ||||
@Test | @Test | ||||
public void testOneBlockAndHalf_Copy() throws IOException { | public void testOneBlockAndHalf_Copy() throws IOException { | ||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(); | |||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(null); | |||||
final byte[] test = new TestRng(getName()) | final byte[] test = new TestRng(getName()) | ||||
.nextBytes(TemporaryBuffer.Block.SZ * 3 / 2); | .nextBytes(TemporaryBuffer.Block.SZ * 3 / 2); | ||||
try { | try { | ||||
@Test | @Test | ||||
public void testLarge_SingleWrite() throws IOException { | public void testLarge_SingleWrite() throws IOException { | ||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(); | |||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(null); | |||||
final byte[] test = new TestRng(getName()) | final byte[] test = new TestRng(getName()) | ||||
.nextBytes(TemporaryBuffer.DEFAULT_IN_CORE_LIMIT * 3); | .nextBytes(TemporaryBuffer.DEFAULT_IN_CORE_LIMIT * 3); | ||||
try { | try { | ||||
@Test | @Test | ||||
public void testInCoreLimit_SwitchOnAppendByte() throws IOException { | public void testInCoreLimit_SwitchOnAppendByte() throws IOException { | ||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(); | |||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(null); | |||||
final byte[] test = new TestRng(getName()) | final byte[] test = new TestRng(getName()) | ||||
.nextBytes(TemporaryBuffer.DEFAULT_IN_CORE_LIMIT + 1); | .nextBytes(TemporaryBuffer.DEFAULT_IN_CORE_LIMIT + 1); | ||||
try { | try { | ||||
@Test | @Test | ||||
public void testInCoreLimit_SwitchBeforeAppendByte() throws IOException { | public void testInCoreLimit_SwitchBeforeAppendByte() throws IOException { | ||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(); | |||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(null); | |||||
final byte[] test = new TestRng(getName()) | final byte[] test = new TestRng(getName()) | ||||
.nextBytes(TemporaryBuffer.DEFAULT_IN_CORE_LIMIT * 3); | .nextBytes(TemporaryBuffer.DEFAULT_IN_CORE_LIMIT * 3); | ||||
try { | try { | ||||
@Test | @Test | ||||
public void testInCoreLimit_SwitchOnCopy() throws IOException { | public void testInCoreLimit_SwitchOnCopy() throws IOException { | ||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(); | |||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(null); | |||||
final byte[] test = new TestRng(getName()) | final byte[] test = new TestRng(getName()) | ||||
.nextBytes(TemporaryBuffer.DEFAULT_IN_CORE_LIMIT * 2); | .nextBytes(TemporaryBuffer.DEFAULT_IN_CORE_LIMIT * 2); | ||||
try { | try { | ||||
@Test | @Test | ||||
public void testDestroyWhileOpen() throws IOException { | public void testDestroyWhileOpen() throws IOException { | ||||
@SuppressWarnings("resource" /* java 7 */) | @SuppressWarnings("resource" /* java 7 */) | ||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(); | |||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(null); | |||||
try { | try { | ||||
b.write(new TestRng(getName()) | b.write(new TestRng(getName()) | ||||
.nextBytes(TemporaryBuffer.DEFAULT_IN_CORE_LIMIT * 2)); | .nextBytes(TemporaryBuffer.DEFAULT_IN_CORE_LIMIT * 2)); | ||||
@Test | @Test | ||||
public void testRandomWrites() throws IOException { | public void testRandomWrites() throws IOException { | ||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(); | |||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(null); | |||||
final TestRng rng = new TestRng(getName()); | final TestRng rng = new TestRng(getName()); | ||||
final int max = TemporaryBuffer.DEFAULT_IN_CORE_LIMIT * 2; | final int max = TemporaryBuffer.DEFAULT_IN_CORE_LIMIT * 2; | ||||
final byte[] expect = new byte[max]; | final byte[] expect = new byte[max]; |
final LockFile tmp = myLock; | final LockFile tmp = myLock; | ||||
requireLocked(tmp); | requireLocked(tmp); | ||||
try { | try { | ||||
writeTo(new SafeBufferedOutputStream(tmp.getOutputStream())); | |||||
writeTo(liveFile.getParentFile(), | |||||
new SafeBufferedOutputStream(tmp.getOutputStream())); | |||||
} catch (IOException err) { | } catch (IOException err) { | ||||
tmp.unlock(); | tmp.unlock(); | ||||
throw err; | throw err; | ||||
} | } | ||||
} | } | ||||
void writeTo(final OutputStream os) throws IOException { | |||||
void writeTo(File dir, final OutputStream os) throws IOException { | |||||
final MessageDigest foot = Constants.newMessageDigest(); | final MessageDigest foot = Constants.newMessageDigest(); | ||||
final DigestOutputStream dos = new DigestOutputStream(os, foot); | final DigestOutputStream dos = new DigestOutputStream(os, foot); | ||||
} | } | ||||
if (writeTree) { | if (writeTree) { | ||||
final TemporaryBuffer bb = new TemporaryBuffer.LocalFile(); | |||||
tree.write(tmp, bb); | |||||
bb.close(); | |||||
NB.encodeInt32(tmp, 0, EXT_TREE); | |||||
NB.encodeInt32(tmp, 4, (int) bb.length()); | |||||
dos.write(tmp, 0, 8); | |||||
bb.writeTo(dos, null); | |||||
TemporaryBuffer bb = new TemporaryBuffer.LocalFile(dir, 5 << 20); | |||||
try { | |||||
tree.write(tmp, bb); | |||||
bb.close(); | |||||
NB.encodeInt32(tmp, 0, EXT_TREE); | |||||
NB.encodeInt32(tmp, 4, (int) bb.length()); | |||||
dos.write(tmp, 0, 8); | |||||
bb.writeTo(dos, null); | |||||
} finally { | |||||
bb.destroy(); | |||||
} | |||||
} | } | ||||
writeIndexChecksum = foot.digest(); | writeIndexChecksum = foot.digest(); | ||||
os.write(writeIndexChecksum); | os.write(writeIndexChecksum); |
} | } | ||||
chk.checkPathSegment(bytes, segmentStart, bytes.length); | chk.checkPathSegment(bytes, segmentStart, bytes.length); | ||||
} catch (CorruptObjectException e) { | } catch (CorruptObjectException e) { | ||||
throw new InvalidPathException(e.getMessage()); | |||||
InvalidPathException p = new InvalidPathException(path); | |||||
p.initCause(e); | |||||
throw p; | |||||
} | } | ||||
} | } | ||||
import java.text.MessageFormat; | import java.text.MessageFormat; | ||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import org.eclipse.jgit.errors.CorruptObjectException; | |||||
import org.eclipse.jgit.internal.JGitText; | import org.eclipse.jgit.internal.JGitText; | ||||
import org.eclipse.jgit.lib.AnyObjectId; | import org.eclipse.jgit.lib.AnyObjectId; | ||||
import org.eclipse.jgit.lib.Constants; | import org.eclipse.jgit.lib.Constants; | ||||
import org.eclipse.jgit.util.IO; | import org.eclipse.jgit.util.IO; | ||||
import org.eclipse.jgit.util.MutableInteger; | import org.eclipse.jgit.util.MutableInteger; | ||||
import org.eclipse.jgit.util.NB; | import org.eclipse.jgit.util.NB; | ||||
import org.eclipse.jgit.util.SystemReader; | |||||
/** | /** | ||||
* A single file (or stage of a file) in a {@link DirCache}. | * A single file (or stage of a file) in a {@link DirCache}. | ||||
md.update((byte) 0); | md.update((byte) 0); | ||||
} | } | ||||
try { | |||||
DirCacheCheckout.checkValidPath(toString(path)); | |||||
} catch (InvalidPathException e) { | |||||
CorruptObjectException p = | |||||
new CorruptObjectException(e.getMessage()); | |||||
if (e.getCause() != null) | |||||
p.initCause(e.getCause()); | |||||
throw p; | |||||
} | |||||
// Index records are padded out to the next 8 byte alignment | // Index records are padded out to the next 8 byte alignment | ||||
// for historical reasons related to how C Git read the files. | // for historical reasons related to how C Git read the files. | ||||
// | // | ||||
if (mightBeRacilyClean(smudge_s, smudge_ns)) | if (mightBeRacilyClean(smudge_s, smudge_ns)) | ||||
smudgeRacilyClean(); | smudgeRacilyClean(); | ||||
} | } | ||||
/** | /** | ||||
* or DirCache file. | * or DirCache file. | ||||
*/ | */ | ||||
public DirCacheEntry(final String newPath) { | public DirCacheEntry(final String newPath) { | ||||
this(Constants.encode(newPath)); | |||||
this(Constants.encode(newPath), STAGE_0); | |||||
} | } | ||||
/** | /** | ||||
*/ | */ | ||||
@SuppressWarnings("boxing") | @SuppressWarnings("boxing") | ||||
public DirCacheEntry(final byte[] newPath, final int stage) { | public DirCacheEntry(final byte[] newPath, final int stage) { | ||||
if (!isValidPath(newPath)) | |||||
throw new InvalidPathException(toString(newPath)); | |||||
DirCacheCheckout.checkValidPath(toString(newPath)); | |||||
if (stage < 0 || 3 < stage) | if (stage < 0 || 3 < stage) | ||||
throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidStageForPath | |||||
, stage, toString(newPath))); | |||||
throw new IllegalArgumentException(MessageFormat.format( | |||||
JGitText.get().invalidStageForPath, | |||||
stage, toString(newPath))); | |||||
info = new byte[INFO_LEN]; | info = new byte[INFO_LEN]; | ||||
infoOffset = 0; | infoOffset = 0; | ||||
return Constants.CHARSET.decode(ByteBuffer.wrap(path)).toString(); | return Constants.CHARSET.decode(ByteBuffer.wrap(path)).toString(); | ||||
} | } | ||||
static boolean isValidPath(final byte[] path) { | |||||
if (path.length == 0) | |||||
return false; // empty path is not permitted. | |||||
boolean componentHasChars = false; | |||||
for (final byte c : path) { | |||||
switch (c) { | |||||
case 0: | |||||
return false; // NUL is never allowed within the path. | |||||
case '/': | |||||
if (componentHasChars) | |||||
componentHasChars = false; | |||||
else | |||||
return false; | |||||
break; | |||||
case '\\': | |||||
case ':': | |||||
// Tree's never have a backslash in them, not even on Windows | |||||
// but even there we regard it as an invalid path | |||||
if (SystemReader.getInstance().isWindows()) | |||||
return false; | |||||
//$FALL-THROUGH$ | |||||
default: | |||||
componentHasChars = true; | |||||
} | |||||
} | |||||
return componentHasChars; | |||||
} | |||||
static int getMaximumInfoLength(boolean extended) { | static int getMaximumInfoLength(boolean extended) { | ||||
return extended ? INFO_LEN_EXTENDED : INFO_LEN; | return extended ? INFO_LEN_EXTENDED : INFO_LEN; | ||||
} | } |
throw new CorruptObjectException("invalid name '..'"); | throw new CorruptObjectException("invalid name '..'"); | ||||
break; | break; | ||||
case 4: | case 4: | ||||
if (isDotGit(raw, ptr + 1)) | |||||
if (isGit(raw, ptr + 1)) | |||||
throw new CorruptObjectException(String.format( | |||||
"invalid name '%s'", | |||||
RawParseUtils.decode(raw, ptr, end))); | |||||
break; | |||||
default: | |||||
if (end - ptr > 4 && isNormalizedGit(raw, ptr + 1, end)) | |||||
throw new CorruptObjectException(String.format( | throw new CorruptObjectException(String.format( | ||||
"invalid name '%s'", | "invalid name '%s'", | ||||
RawParseUtils.decode(raw, ptr, end))); | RawParseUtils.decode(raw, ptr, end))); | ||||
} | } | ||||
} else if (isGitTilde1(raw, ptr, end)) { | |||||
throw new CorruptObjectException(String.format("invalid name '%s'", | |||||
RawParseUtils.decode(raw, ptr, end))); | |||||
} | } | ||||
if (macosx && isMacHFSGit(raw, ptr, end)) | |||||
throw new CorruptObjectException(String.format( | |||||
"invalid name '%s' contains ignorable Unicode characters", | |||||
RawParseUtils.decode(raw, ptr, end))); | |||||
if (windows) { | if (windows) { | ||||
// Windows ignores space and dot at end of file name. | // Windows ignores space and dot at end of file name. | ||||
if (raw[end - 1] == ' ' || raw[end - 1] == '.') | if (raw[end - 1] == ' ' || raw[end - 1] == '.') | ||||
} | } | ||||
} | } | ||||
// Mac's HFS+ folds permutations of ".git" and Unicode ignorable characters | |||||
// to ".git" therefore we should prevent such names | |||||
private static boolean isMacHFSGit(byte[] raw, int ptr, int end) | |||||
throws CorruptObjectException { | |||||
boolean ignorable = false; | |||||
byte[] git = new byte[] { '.', 'g', 'i', 't' }; | |||||
int g = 0; | |||||
while (ptr < end) { | |||||
switch (raw[ptr]) { | |||||
case (byte) 0xe2: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=8192 | |||||
checkTruncatedIgnorableUTF8(raw, ptr, end); | |||||
switch (raw[ptr + 1]) { | |||||
case (byte) 0x80: | |||||
switch (raw[ptr + 2]) { | |||||
case (byte) 0x8c: // U+200C 0xe2808c ZERO WIDTH NON-JOINER | |||||
case (byte) 0x8d: // U+200D 0xe2808d ZERO WIDTH JOINER | |||||
case (byte) 0x8e: // U+200E 0xe2808e LEFT-TO-RIGHT MARK | |||||
case (byte) 0x8f: // U+200F 0xe2808f RIGHT-TO-LEFT MARK | |||||
case (byte) 0xaa: // U+202A 0xe280aa LEFT-TO-RIGHT EMBEDDING | |||||
case (byte) 0xab: // U+202B 0xe280ab RIGHT-TO-LEFT EMBEDDING | |||||
case (byte) 0xac: // U+202C 0xe280ac POP DIRECTIONAL FORMATTING | |||||
case (byte) 0xad: // U+202D 0xe280ad LEFT-TO-RIGHT OVERRIDE | |||||
case (byte) 0xae: // U+202E 0xe280ae RIGHT-TO-LEFT OVERRIDE | |||||
ignorable = true; | |||||
ptr += 3; | |||||
continue; | |||||
default: | |||||
return false; | |||||
} | |||||
case (byte) 0x81: | |||||
switch (raw[ptr + 2]) { | |||||
case (byte) 0xaa: // U+206A 0xe281aa INHIBIT SYMMETRIC SWAPPING | |||||
case (byte) 0xab: // U+206B 0xe281ab ACTIVATE SYMMETRIC SWAPPING | |||||
case (byte) 0xac: // U+206C 0xe281ac INHIBIT ARABIC FORM SHAPING | |||||
case (byte) 0xad: // U+206D 0xe281ad ACTIVATE ARABIC FORM SHAPING | |||||
case (byte) 0xae: // U+206E 0xe281ae NATIONAL DIGIT SHAPES | |||||
case (byte) 0xaf: // U+206F 0xe281af NOMINAL DIGIT SHAPES | |||||
ignorable = true; | |||||
ptr += 3; | |||||
continue; | |||||
default: | |||||
return false; | |||||
} | |||||
} | |||||
break; | |||||
case (byte) 0xef: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=65024 | |||||
checkTruncatedIgnorableUTF8(raw, ptr, end); | |||||
// U+FEFF 0xefbbbf ZERO WIDTH NO-BREAK SPACE | |||||
if ((raw[ptr + 1] == (byte) 0xbb) | |||||
&& (raw[ptr + 2] == (byte) 0xbf)) { | |||||
ignorable = true; | |||||
ptr += 3; | |||||
continue; | |||||
} | |||||
return false; | |||||
default: | |||||
if (g == 4) | |||||
return false; | |||||
if (raw[ptr++] != git[g++]) | |||||
return false; | |||||
} | |||||
} | |||||
if (g == 4 && ignorable) | |||||
return true; | |||||
return false; | |||||
} | |||||
private static void checkTruncatedIgnorableUTF8(byte[] raw, int ptr, int end) | |||||
throws CorruptObjectException { | |||||
if ((ptr + 2) >= end) | |||||
throw new CorruptObjectException(MessageFormat.format( | |||||
"invalid name contains byte sequence ''{0}'' which is not a valid UTF-8 character", | |||||
toHexString(raw, ptr, end))); | |||||
} | |||||
private static String toHexString(byte[] raw, int ptr, int end) { | |||||
StringBuilder b = new StringBuilder("0x"); //$NON-NLS-1$ | |||||
for (int i = ptr; i < end; i++) | |||||
b.append(String.format("%02x", Byte.valueOf(raw[i]))); //$NON-NLS-1$ | |||||
return b.toString(); | |||||
} | |||||
private static void checkNotWindowsDevice(byte[] raw, int ptr, int end) | private static void checkNotWindowsDevice(byte[] raw, int ptr, int end) | ||||
throws CorruptObjectException { | throws CorruptObjectException { | ||||
switch (toLower(raw[ptr])) { | switch (toLower(raw[ptr])) { | ||||
return 1 <= c && c <= 31; | return 1 <= c && c <= 31; | ||||
} | } | ||||
private boolean isDotGit(byte[] buf, int p) { | |||||
if (windows || macosx) | |||||
return toLower(buf[p]) == 'g' | |||||
&& toLower(buf[p + 1]) == 'i' | |||||
&& toLower(buf[p + 2]) == 't'; | |||||
return buf[p] == 'g' && buf[p + 1] == 'i' && buf[p + 2] == 't'; | |||||
private static boolean isGit(byte[] buf, int p) { | |||||
return toLower(buf[p]) == 'g' | |||||
&& toLower(buf[p + 1]) == 'i' | |||||
&& toLower(buf[p + 2]) == 't'; | |||||
} | |||||
private static boolean isGitTilde1(byte[] buf, int p, int end) { | |||||
if (end - p != 5) | |||||
return false; | |||||
return toLower(buf[p]) == 'g' && toLower(buf[p + 1]) == 'i' | |||||
&& toLower(buf[p + 2]) == 't' && buf[p + 3] == '~' | |||||
&& buf[p + 4] == '1'; | |||||
} | |||||
private static boolean isNormalizedGit(byte[] raw, int ptr, int end) { | |||||
if (isGit(raw, ptr)) { | |||||
int dots = 0; | |||||
boolean space = false; | |||||
int p = end - 1; | |||||
for (; (ptr + 2) < p; p--) { | |||||
if (raw[p] == '.') | |||||
dots++; | |||||
else if (raw[p] == ' ') | |||||
space = true; | |||||
else | |||||
break; | |||||
} | |||||
return p == ptr + 2 && (dots == 1 || space); | |||||
} | |||||
return false; | |||||
} | } | ||||
private static char toLower(byte b) { | private static char toLower(byte b) { |
package org.eclipse.jgit.lib; | package org.eclipse.jgit.lib; | ||||
import java.io.BufferedWriter; | |||||
import java.io.File; | import java.io.File; | ||||
import java.io.FileOutputStream; | import java.io.FileOutputStream; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.OutputStreamWriter; | |||||
import java.io.OutputStream; | |||||
import java.util.LinkedList; | import java.util.LinkedList; | ||||
import java.util.List; | import java.util.List; | ||||
import org.eclipse.jgit.lib.RebaseTodoLine.Action; | import org.eclipse.jgit.lib.RebaseTodoLine.Action; | ||||
import org.eclipse.jgit.util.IO; | import org.eclipse.jgit.util.IO; | ||||
import org.eclipse.jgit.util.RawParseUtils; | import org.eclipse.jgit.util.RawParseUtils; | ||||
import org.eclipse.jgit.util.io.SafeBufferedOutputStream; | |||||
/** | /** | ||||
* Offers methods to read and write files formatted like the git-rebase-todo | * Offers methods to read and write files formatted like the git-rebase-todo | ||||
*/ | */ | ||||
public void writeRebaseTodoFile(String path, List<RebaseTodoLine> steps, | public void writeRebaseTodoFile(String path, List<RebaseTodoLine> steps, | ||||
boolean append) throws IOException { | boolean append) throws IOException { | ||||
BufferedWriter fw = new BufferedWriter(new OutputStreamWriter( | |||||
new FileOutputStream(new File(repo.getDirectory(), path), | |||||
append), Constants.CHARACTER_ENCODING)); | |||||
OutputStream fw = new SafeBufferedOutputStream(new FileOutputStream( | |||||
new File(repo.getDirectory(), path), append)); | |||||
try { | try { | ||||
StringBuilder sb = new StringBuilder(); | StringBuilder sb = new StringBuilder(); | ||||
for (RebaseTodoLine step : steps) { | for (RebaseTodoLine step : steps) { | ||||
sb.append(" "); //$NON-NLS-1$ | sb.append(" "); //$NON-NLS-1$ | ||||
sb.append(step.getShortMessage().trim()); | sb.append(step.getShortMessage().trim()); | ||||
} | } | ||||
fw.write(sb.toString()); | |||||
fw.newLine(); | |||||
sb.append('\n'); | |||||
fw.write(Constants.encode(sb.toString())); | |||||
} | } | ||||
} finally { | } finally { | ||||
fw.close(); | fw.close(); |
final TemporaryBuffer[] tmp = new TemporaryBuffer[getParentCount() + 1]; | final TemporaryBuffer[] tmp = new TemporaryBuffer[getParentCount() + 1]; | ||||
try { | try { | ||||
for (int i = 0; i < tmp.length; i++) | for (int i = 0; i < tmp.length; i++) | ||||
tmp[i] = new TemporaryBuffer.LocalFile(); | |||||
tmp[i] = new TemporaryBuffer.Heap(Integer.MAX_VALUE); | |||||
for (final HunkHeader h : getHunks()) | for (final HunkHeader h : getHunks()) | ||||
h.extractFileLines(tmp); | h.extractFileLines(tmp); | ||||
return r; | return r; | ||||
} catch (IOException ioe) { | } catch (IOException ioe) { | ||||
throw new RuntimeException(JGitText.get().cannotConvertScriptToText, ioe); | throw new RuntimeException(JGitText.get().cannotConvertScriptToText, ioe); | ||||
} finally { | |||||
for (final TemporaryBuffer b : tmp) { | |||||
if (b != null) | |||||
b.destroy(); | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
private static byte[] readFully(final InputStream is) throws IOException { | private static byte[] readFully(final InputStream is) throws IOException { | ||||
final TemporaryBuffer b = new TemporaryBuffer.LocalFile(); | |||||
try { | |||||
b.copy(is); | |||||
b.close(); | |||||
return b.toByteArray(); | |||||
} finally { | |||||
b.destroy(); | |||||
} | |||||
TemporaryBuffer b = new TemporaryBuffer.Heap(Integer.MAX_VALUE); | |||||
b.copy(is); | |||||
b.close(); | |||||
return b.toByteArray(); | |||||
} | } | ||||
/** | /** |
/** Encryption algorithm, may be a null instance that provides pass-through. */ | /** Encryption algorithm, may be a null instance that provides pass-through. */ | ||||
private final WalkEncryption encryption; | private final WalkEncryption encryption; | ||||
/** Directory for locally buffered content. */ | |||||
private final File tmpDir; | |||||
/** | /** | ||||
* Create a new S3 client for the supplied user information. | * Create a new S3 client for the supplied user information. | ||||
* <p> | * <p> | ||||
maxAttempts = Integer.parseInt(props.getProperty( | maxAttempts = Integer.parseInt(props.getProperty( | ||||
"httpclient.retry-max", "3")); //$NON-NLS-1$ //$NON-NLS-2$ | "httpclient.retry-max", "3")); //$NON-NLS-1$ //$NON-NLS-2$ | ||||
proxySelector = ProxySelector.getDefault(); | proxySelector = ProxySelector.getDefault(); | ||||
String tmp = props.getProperty("tmpdir"); //$NON-NLS-1$ | |||||
tmpDir = tmp != null && tmp.length() > 0 ? new File(tmp) : null; | |||||
} | } | ||||
/** | /** | ||||
final ProgressMonitor monitor, final String monitorTask) | final ProgressMonitor monitor, final String monitorTask) | ||||
throws IOException { | throws IOException { | ||||
final MessageDigest md5 = newMD5(); | final MessageDigest md5 = newMD5(); | ||||
final TemporaryBuffer buffer = new TemporaryBuffer.LocalFile() { | |||||
final TemporaryBuffer buffer = new TemporaryBuffer.LocalFile(tmpDir) { | |||||
@Override | @Override | ||||
public void close() throws IOException { | public void close() throws IOException { | ||||
super.close(); | super.close(); |
throws NotSupportedException { | throws NotSupportedException { | ||||
super(local, uri); | super(local, uri); | ||||
s3 = new AmazonS3(loadProperties()); | |||||
Properties props = loadProperties(); | |||||
if (!props.contains("tmpdir") && local.getDirectory() != null) //$NON-NLS-1$ | |||||
props.put("tmpdir", local.getDirectory().getPath()); //$NON-NLS-1$ | |||||
s3 = new AmazonS3(props); | |||||
bucket = uri.getHost(); | bucket = uri.getHost(); | ||||
String p = uri.getPath(); | String p = uri.getPath(); |
*/ | */ | ||||
private File onDiskFile; | private File onDiskFile; | ||||
/** Create a new temporary buffer. */ | |||||
/** | |||||
* Create a new temporary buffer. | |||||
* | |||||
* @deprecated Use the {@code File} overload to supply a directory. | |||||
*/ | |||||
@Deprecated | |||||
public LocalFile() { | public LocalFile() { | ||||
this(null, DEFAULT_IN_CORE_LIMIT); | this(null, DEFAULT_IN_CORE_LIMIT); | ||||
} | } | ||||
* @param inCoreLimit | * @param inCoreLimit | ||||
* maximum number of bytes to store in memory. Storage beyond | * maximum number of bytes to store in memory. Storage beyond | ||||
* this limit will use the local file. | * this limit will use the local file. | ||||
* @deprecated Use the {@code File,int} overload to supply a directory. | |||||
*/ | */ | ||||
@Deprecated | |||||
public LocalFile(final int inCoreLimit) { | public LocalFile(final int inCoreLimit) { | ||||
this(null, inCoreLimit); | this(null, inCoreLimit); | ||||
} | } |