Merge changes from topic 'add-df'
* changes: DirCache: Do not create duplicate tree entries DirCacheEditor: Replace file-with-tree and tree-with-file AddCommand: Use NameConflictTreeWalk to identify file-dir changes
This commit is contained in:
commit
163be57d0f
|
@ -43,6 +43,7 @@
|
|||
*/
|
||||
package org.eclipse.jgit.api;
|
||||
|
||||
import static org.eclipse.jgit.util.FileUtils.RECURSIVE;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
@ -777,12 +778,107 @@ public void testAssumeUnchanged() throws Exception {
|
|||
|
||||
assertEquals("[a.txt, mode:100644, content:more content,"
|
||||
+ " assume-unchanged:false][b.txt, mode:100644,"
|
||||
+ "" + ""
|
||||
+ " content:content, assume-unchanged:true]",
|
||||
indexState(CONTENT
|
||||
| ASSUME_UNCHANGED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplaceFileWithDirectory()
|
||||
throws IOException, NoFilepatternException, GitAPIException {
|
||||
try (Git git = new Git(db)) {
|
||||
writeTrashFile("df", "before replacement");
|
||||
git.add().addFilepattern("df").call();
|
||||
assertEquals("[df, mode:100644, content:before replacement]",
|
||||
indexState(CONTENT));
|
||||
FileUtils.delete(new File(db.getWorkTree(), "df"));
|
||||
writeTrashFile("df/f", "after replacement");
|
||||
git.add().addFilepattern("df").call();
|
||||
assertEquals("[df/f, mode:100644, content:after replacement]",
|
||||
indexState(CONTENT));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplaceDirectoryWithFile()
|
||||
throws IOException, NoFilepatternException, GitAPIException {
|
||||
try (Git git = new Git(db)) {
|
||||
writeTrashFile("df/f", "before replacement");
|
||||
git.add().addFilepattern("df").call();
|
||||
assertEquals("[df/f, mode:100644, content:before replacement]",
|
||||
indexState(CONTENT));
|
||||
FileUtils.delete(new File(db.getWorkTree(), "df"), RECURSIVE);
|
||||
writeTrashFile("df", "after replacement");
|
||||
git.add().addFilepattern("df").call();
|
||||
assertEquals("[df, mode:100644, content:after replacement]",
|
||||
indexState(CONTENT));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplaceFileByPartOfDirectory()
|
||||
throws IOException, NoFilepatternException, GitAPIException {
|
||||
try (Git git = new Git(db)) {
|
||||
writeTrashFile("src/main", "df", "before replacement");
|
||||
writeTrashFile("src/main", "z", "z");
|
||||
writeTrashFile("z", "z2");
|
||||
git.add().addFilepattern("src/main/df")
|
||||
.addFilepattern("src/main/z")
|
||||
.addFilepattern("z")
|
||||
.call();
|
||||
assertEquals(
|
||||
"[src/main/df, mode:100644, content:before replacement]" +
|
||||
"[src/main/z, mode:100644, content:z]" +
|
||||
"[z, mode:100644, content:z2]",
|
||||
indexState(CONTENT));
|
||||
FileUtils.delete(new File(db.getWorkTree(), "src/main/df"));
|
||||
writeTrashFile("src/main/df", "a", "after replacement");
|
||||
writeTrashFile("src/main/df", "b", "unrelated file");
|
||||
git.add().addFilepattern("src/main/df/a").call();
|
||||
assertEquals(
|
||||
"[src/main/df/a, mode:100644, content:after replacement]" +
|
||||
"[src/main/z, mode:100644, content:z]" +
|
||||
"[z, mode:100644, content:z2]",
|
||||
indexState(CONTENT));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplaceDirectoryConflictsWithFile()
|
||||
throws IOException, NoFilepatternException, GitAPIException {
|
||||
DirCache dc = db.lockDirCache();
|
||||
try (ObjectInserter oi = db.newObjectInserter()) {
|
||||
DirCacheBuilder builder = dc.builder();
|
||||
File f = writeTrashFile("a", "df", "content");
|
||||
addEntryToBuilder("a", f, oi, builder, 1);
|
||||
|
||||
f = writeTrashFile("a", "df", "other content");
|
||||
addEntryToBuilder("a/df", f, oi, builder, 3);
|
||||
|
||||
f = writeTrashFile("a", "df", "our content");
|
||||
addEntryToBuilder("a/df", f, oi, builder, 2);
|
||||
|
||||
f = writeTrashFile("z", "z");
|
||||
addEntryToBuilder("z", f, oi, builder, 0);
|
||||
builder.commit();
|
||||
}
|
||||
assertEquals(
|
||||
"[a, mode:100644, stage:1, content:content]" +
|
||||
"[a/df, mode:100644, stage:2, content:our content]" +
|
||||
"[a/df, mode:100644, stage:3, content:other content]" +
|
||||
"[z, mode:100644, content:z]",
|
||||
indexState(CONTENT));
|
||||
|
||||
try (Git git = new Git(db)) {
|
||||
FileUtils.delete(new File(db.getWorkTree(), "a"), RECURSIVE);
|
||||
writeTrashFile("a", "merged");
|
||||
git.add().addFilepattern("a").call();
|
||||
assertEquals("[a, mode:100644, content:merged]" +
|
||||
"[z, mode:100644, content:z]",
|
||||
indexState(CONTENT));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecutableRetention() throws Exception {
|
||||
StoredConfig config = db.getConfig();
|
||||
|
|
|
@ -43,11 +43,13 @@
|
|||
package org.eclipse.jgit.dircache;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
|
||||
import org.eclipse.jgit.errors.DirCacheNameConflictException;
|
||||
import org.eclipse.jgit.lib.FileMode;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.junit.Test;
|
||||
|
@ -154,6 +156,108 @@ public void testPathEditShouldBeCalledForEachStage() throws Exception {
|
|||
assertEquals(DirCacheEntry.STAGE_3, entries.get(2).getStage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileReplacesTree() throws Exception {
|
||||
DirCache dc = DirCache.newInCore();
|
||||
DirCacheEditor editor = dc.editor();
|
||||
editor.add(new AddEdit("a"));
|
||||
editor.add(new AddEdit("b/c"));
|
||||
editor.add(new AddEdit("b/d"));
|
||||
editor.add(new AddEdit("e"));
|
||||
editor.finish();
|
||||
|
||||
editor = dc.editor();
|
||||
editor.add(new AddEdit("b"));
|
||||
editor.finish();
|
||||
|
||||
assertEquals(3, dc.getEntryCount());
|
||||
assertEquals("a", dc.getEntry(0).getPathString());
|
||||
assertEquals("b", dc.getEntry(1).getPathString());
|
||||
assertEquals("e", dc.getEntry(2).getPathString());
|
||||
|
||||
dc.clear();
|
||||
editor = dc.editor();
|
||||
editor.add(new AddEdit("A.c"));
|
||||
editor.add(new AddEdit("A/c"));
|
||||
editor.add(new AddEdit("A0c"));
|
||||
editor.finish();
|
||||
|
||||
editor = dc.editor();
|
||||
editor.add(new AddEdit("A"));
|
||||
editor.finish();
|
||||
assertEquals(3, dc.getEntryCount());
|
||||
assertEquals("A", dc.getEntry(0).getPathString());
|
||||
assertEquals("A.c", dc.getEntry(1).getPathString());
|
||||
assertEquals("A0c", dc.getEntry(2).getPathString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTreeReplacesFile() throws Exception {
|
||||
DirCache dc = DirCache.newInCore();
|
||||
DirCacheEditor editor = dc.editor();
|
||||
editor.add(new AddEdit("a"));
|
||||
editor.add(new AddEdit("ab"));
|
||||
editor.add(new AddEdit("b"));
|
||||
editor.add(new AddEdit("e"));
|
||||
editor.finish();
|
||||
|
||||
editor = dc.editor();
|
||||
editor.add(new AddEdit("b/c/d/f"));
|
||||
editor.add(new AddEdit("b/g/h/i"));
|
||||
editor.finish();
|
||||
|
||||
assertEquals(5, dc.getEntryCount());
|
||||
assertEquals("a", dc.getEntry(0).getPathString());
|
||||
assertEquals("ab", dc.getEntry(1).getPathString());
|
||||
assertEquals("b/c/d/f", dc.getEntry(2).getPathString());
|
||||
assertEquals("b/g/h/i", dc.getEntry(3).getPathString());
|
||||
assertEquals("e", dc.getEntry(4).getPathString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileOverlapsTree() throws Exception {
|
||||
DirCache dc = DirCache.newInCore();
|
||||
DirCacheEditor editor = dc.editor();
|
||||
editor.add(new AddEdit("a"));
|
||||
editor.add(new AddEdit("a/b").setReplace(false));
|
||||
try {
|
||||
editor.finish();
|
||||
fail("Expected DirCacheNameConflictException to be thrown");
|
||||
} catch (DirCacheNameConflictException e) {
|
||||
assertEquals("a a/b", e.getMessage());
|
||||
assertEquals("a", e.getPath1());
|
||||
assertEquals("a/b", e.getPath2());
|
||||
}
|
||||
|
||||
editor = dc.editor();
|
||||
editor.add(new AddEdit("A.c"));
|
||||
editor.add(new AddEdit("A/c").setReplace(false));
|
||||
editor.add(new AddEdit("A0c"));
|
||||
editor.add(new AddEdit("A"));
|
||||
try {
|
||||
editor.finish();
|
||||
fail("Expected DirCacheNameConflictException to be thrown");
|
||||
} catch (DirCacheNameConflictException e) {
|
||||
assertEquals("A A/c", e.getMessage());
|
||||
assertEquals("A", e.getPath1());
|
||||
assertEquals("A/c", e.getPath2());
|
||||
}
|
||||
|
||||
editor = dc.editor();
|
||||
editor.add(new AddEdit("A.c"));
|
||||
editor.add(new AddEdit("A/b/c/d").setReplace(false));
|
||||
editor.add(new AddEdit("A/b/c"));
|
||||
editor.add(new AddEdit("A0c"));
|
||||
try {
|
||||
editor.finish();
|
||||
fail("Expected DirCacheNameConflictException to be thrown");
|
||||
} catch (DirCacheNameConflictException e) {
|
||||
assertEquals("A/b/c A/b/c/d", e.getMessage());
|
||||
assertEquals("A/b/c", e.getPath1());
|
||||
assertEquals("A/b/c/d", e.getPath2());
|
||||
}
|
||||
}
|
||||
|
||||
private static DirCacheEntry createEntry(String path, int stage) {
|
||||
DirCacheEntry entry = new DirCacheEntry(path, stage);
|
||||
entry.setFileMode(FileMode.REGULAR_FILE);
|
||||
|
|
|
@ -1084,7 +1084,7 @@ public void testCheckoutChangeLinkToNonEmptyDirsAndNewIndexEntry()
|
|||
assertWorkDir(mkmap(linkName, "a", fname, "a"));
|
||||
|
||||
Status st = git.status().call();
|
||||
assertFalse(st.isClean());
|
||||
assertTrue(st.isClean());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1213,9 +1213,7 @@ public void testCheckoutChangeFileToNonEmptyDirsAndNewIndexEntry()
|
|||
assertWorkDir(mkmap(fname, "a"));
|
||||
|
||||
Status st = git.status().call();
|
||||
assertFalse(st.isClean());
|
||||
assertEquals(1, st.getAdded().size());
|
||||
assertTrue(st.getAdded().contains(fname + "/dir/file1"));
|
||||
assertTrue(st.isClean());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
|
||||
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
|
||||
import static org.eclipse.jgit.lib.FileMode.GITLINK;
|
||||
import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -66,7 +67,7 @@
|
|||
import org.eclipse.jgit.lib.ObjectInserter;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.treewalk.FileTreeIterator;
|
||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||
import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
|
||||
import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
|
||||
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
|
||||
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
|
||||
|
@ -141,7 +142,7 @@ public DirCache call() throws GitAPIException, NoFilepatternException {
|
|||
boolean addAll = filepatterns.contains("."); //$NON-NLS-1$
|
||||
|
||||
try (ObjectInserter inserter = repo.newObjectInserter();
|
||||
final TreeWalk tw = new TreeWalk(repo)) {
|
||||
NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) {
|
||||
tw.setOperationType(OperationType.CHECKIN_OP);
|
||||
dc = repo.lockDirCache();
|
||||
|
||||
|
@ -151,7 +152,6 @@ public DirCache call() throws GitAPIException, NoFilepatternException {
|
|||
workingTreeIterator = new FileTreeIterator(repo);
|
||||
workingTreeIterator.setDirCacheIterator(tw, 0);
|
||||
tw.addTree(workingTreeIterator);
|
||||
tw.setRecursive(true);
|
||||
if (!addAll)
|
||||
tw.setFilter(PathFilterGroup.createFromStrings(filepatterns));
|
||||
|
||||
|
@ -180,9 +180,14 @@ public DirCache call() throws GitAPIException, NoFilepatternException {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (tw.isSubtree() && !tw.isDirectoryFileConflict()) {
|
||||
tw.enterSubtree();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (f == null) { // working tree file does not exist
|
||||
if (c != null
|
||||
&& (!update || GITLINK == c.getEntryFileMode())) {
|
||||
if (entry != null
|
||||
&& (!update || GITLINK == entry.getFileMode())) {
|
||||
builder.add(entry);
|
||||
}
|
||||
continue;
|
||||
|
@ -196,6 +201,14 @@ public DirCache call() throws GitAPIException, NoFilepatternException {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (f.getEntryRawMode() == TYPE_TREE) {
|
||||
// Index entry exists and is symlink, gitlink or file,
|
||||
// otherwise the tree would have been entered above.
|
||||
// Replace the index entry by diving into tree of files.
|
||||
tw.enterSubtree();
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] path = tw.getRawPath();
|
||||
if (entry == null || entry.getStage() > 0) {
|
||||
entry = new DirCacheEntry(path);
|
||||
|
|
|
@ -44,8 +44,13 @@
|
|||
|
||||
package org.eclipse.jgit.dircache;
|
||||
|
||||
import static org.eclipse.jgit.lib.FileMode.TREE;
|
||||
import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jgit.errors.DirCacheNameConflictException;
|
||||
|
||||
/**
|
||||
* Generic update/editing support for {@link DirCache}.
|
||||
* <p>
|
||||
|
@ -168,6 +173,7 @@ protected void fastKeep(final int pos, int cnt) {
|
|||
* {@link #finish()}, and only after {@link #entries} is sorted.
|
||||
*/
|
||||
protected void replace() {
|
||||
checkNameConflicts();
|
||||
if (entryCnt < entries.length / 2) {
|
||||
final DirCacheEntry[] n = new DirCacheEntry[entryCnt];
|
||||
System.arraycopy(entries, 0, n, 0, entryCnt);
|
||||
|
@ -176,6 +182,98 @@ protected void replace() {
|
|||
cache.replace(entries, entryCnt);
|
||||
}
|
||||
|
||||
private void checkNameConflicts() {
|
||||
int end = entryCnt - 1;
|
||||
for (int eIdx = 0; eIdx < end; eIdx++) {
|
||||
DirCacheEntry e = entries[eIdx];
|
||||
if (e.getStage() != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] ePath = e.path;
|
||||
int prefixLen = lastSlash(ePath) + 1;
|
||||
|
||||
for (int nIdx = eIdx + 1; nIdx < entryCnt; nIdx++) {
|
||||
DirCacheEntry n = entries[nIdx];
|
||||
if (n.getStage() != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] nPath = n.path;
|
||||
if (!startsWith(ePath, nPath, prefixLen)) {
|
||||
// Different prefix; this entry is in another directory.
|
||||
break;
|
||||
}
|
||||
|
||||
int s = nextSlash(nPath, prefixLen);
|
||||
int m = s < nPath.length ? TYPE_TREE : n.getRawMode();
|
||||
int cmp = pathCompare(
|
||||
ePath, prefixLen, ePath.length, TYPE_TREE,
|
||||
nPath, prefixLen, s, m);
|
||||
if (cmp < 0) {
|
||||
break;
|
||||
} else if (cmp == 0) {
|
||||
throw new DirCacheNameConflictException(
|
||||
e.getPathString(),
|
||||
n.getPathString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int lastSlash(byte[] path) {
|
||||
for (int i = path.length - 1; i >= 0; i--) {
|
||||
if (path[i] == '/') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int nextSlash(byte[] b, int p) {
|
||||
final int n = b.length;
|
||||
for (; p < n; p++) {
|
||||
if (b[p] == '/') {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
private static boolean startsWith(byte[] a, byte[] b, int n) {
|
||||
if (b.length < n) {
|
||||
return false;
|
||||
}
|
||||
for (n--; n >= 0; n--) {
|
||||
if (a[n] != b[n]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int pathCompare(byte[] aPath, int aPos, int aEnd, int aMode,
|
||||
byte[] bPath, int bPos, int bEnd, int bMode) {
|
||||
while (aPos < aEnd && bPos < bEnd) {
|
||||
int cmp = (aPath[aPos++] & 0xff) - (bPath[bPos++] & 0xff);
|
||||
if (cmp != 0) {
|
||||
return cmp;
|
||||
}
|
||||
}
|
||||
|
||||
if (aPos < aEnd) {
|
||||
return (aPath[aPos] & 0xff) - lastPathChar(bMode);
|
||||
}
|
||||
if (bPos < bEnd) {
|
||||
return lastPathChar(aMode) - (bPath[bPos] & 0xff);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int lastPathChar(int mode) {
|
||||
return TREE.equals(mode) ? '/' : '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish, write, commit this change, and release the index lock.
|
||||
* <p>
|
||||
|
|
|
@ -800,8 +800,11 @@ public int findEntry(final String path) {
|
|||
* information. If < 0 the entry does not exist in the index.
|
||||
* @since 3.4
|
||||
*/
|
||||
public int findEntry(final byte[] p, final int pLen) {
|
||||
int low = 0;
|
||||
public int findEntry(byte[] p, int pLen) {
|
||||
return findEntry(0, p, pLen);
|
||||
}
|
||||
|
||||
int findEntry(int low, byte[] p, int pLen) {
|
||||
int high = entryCnt;
|
||||
while (low < high) {
|
||||
int mid = (low + high) >>> 1;
|
||||
|
|
|
@ -130,4 +130,9 @@ public void stopWalk() {
|
|||
if (cur < cnt)
|
||||
builder.keep(cur, cnt - cur);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean needsStopWalk() {
|
||||
return ptr < cache.getEntryCount();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,10 @@
|
|||
|
||||
package org.eclipse.jgit.dircache;
|
||||
|
||||
import static org.eclipse.jgit.dircache.DirCache.cmp;
|
||||
import static org.eclipse.jgit.dircache.DirCacheTree.peq;
|
||||
import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
|
@ -72,11 +76,12 @@ public class DirCacheEditor extends BaseDirCacheEditor {
|
|||
public int compare(final PathEdit o1, final PathEdit o2) {
|
||||
final byte[] a = o1.path;
|
||||
final byte[] b = o2.path;
|
||||
return DirCache.cmp(a, a.length, b, b.length);
|
||||
return cmp(a, a.length, b, b.length);
|
||||
}
|
||||
};
|
||||
|
||||
private final List<PathEdit> edits;
|
||||
private int editIdx;
|
||||
|
||||
/**
|
||||
* Construct a new editor.
|
||||
|
@ -126,37 +131,44 @@ public void finish() {
|
|||
|
||||
private void applyEdits() {
|
||||
Collections.sort(edits, EDIT_CMP);
|
||||
editIdx = 0;
|
||||
|
||||
final int maxIdx = cache.getEntryCount();
|
||||
int lastIdx = 0;
|
||||
for (final PathEdit e : edits) {
|
||||
int eIdx = cache.findEntry(e.path, e.path.length);
|
||||
while (editIdx < edits.size()) {
|
||||
PathEdit e = edits.get(editIdx++);
|
||||
int eIdx = cache.findEntry(lastIdx, e.path, e.path.length);
|
||||
final boolean missing = eIdx < 0;
|
||||
if (eIdx < 0)
|
||||
eIdx = -(eIdx + 1);
|
||||
final int cnt = Math.min(eIdx, maxIdx) - lastIdx;
|
||||
if (cnt > 0)
|
||||
fastKeep(lastIdx, cnt);
|
||||
lastIdx = missing ? eIdx : cache.nextEntry(eIdx);
|
||||
|
||||
if (e instanceof DeletePath)
|
||||
if (e instanceof DeletePath) {
|
||||
lastIdx = missing ? eIdx : cache.nextEntry(eIdx);
|
||||
continue;
|
||||
}
|
||||
if (e instanceof DeleteTree) {
|
||||
lastIdx = cache.nextEntry(e.path, e.path.length, eIdx);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (missing) {
|
||||
final DirCacheEntry ent = new DirCacheEntry(e.path);
|
||||
DirCacheEntry ent = new DirCacheEntry(e.path);
|
||||
e.apply(ent);
|
||||
if (ent.getRawMode() == 0) {
|
||||
throw new IllegalArgumentException(MessageFormat.format(
|
||||
JGitText.get().fileModeNotSetForPath,
|
||||
ent.getPathString()));
|
||||
}
|
||||
lastIdx = e.replace
|
||||
? deleteOverlappingSubtree(ent, eIdx)
|
||||
: eIdx;
|
||||
fastAdd(ent);
|
||||
} else {
|
||||
// Apply to all entries of the current path (different stages)
|
||||
lastIdx = cache.nextEntry(eIdx);
|
||||
for (int i = eIdx; i < lastIdx; i++) {
|
||||
final DirCacheEntry ent = cache.getEntry(i);
|
||||
e.apply(ent);
|
||||
|
@ -170,6 +182,102 @@ private void applyEdits() {
|
|||
fastKeep(lastIdx, cnt);
|
||||
}
|
||||
|
||||
private int deleteOverlappingSubtree(DirCacheEntry ent, int eIdx) {
|
||||
byte[] entPath = ent.path;
|
||||
int entLen = entPath.length;
|
||||
|
||||
// Delete any file that was previously processed and overlaps
|
||||
// the parent directory for the new entry. Since the editor
|
||||
// always processes entries in path order, binary search back
|
||||
// for the overlap for each parent directory.
|
||||
for (int p = pdir(entPath, entLen); p > 0; p = pdir(entPath, p)) {
|
||||
int i = findEntry(entPath, p);
|
||||
if (i >= 0) {
|
||||
// A file does overlap, delete the file from the array.
|
||||
// No other parents can have overlaps as the file should
|
||||
// have taken care of that itself.
|
||||
int n = --entryCnt - i;
|
||||
System.arraycopy(entries, i + 1, entries, i, n);
|
||||
break;
|
||||
}
|
||||
|
||||
// If at least one other entry already exists in this parent
|
||||
// directory there is no need to continue searching up the tree.
|
||||
i = -(i + 1);
|
||||
if (i < entryCnt && inDir(entries[i], entPath, p)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int maxEnt = cache.getEntryCount();
|
||||
if (eIdx >= maxEnt) {
|
||||
return maxEnt;
|
||||
}
|
||||
|
||||
DirCacheEntry next = cache.getEntry(eIdx);
|
||||
if (pathCompare(next.path, 0, next.path.length, 0,
|
||||
entPath, 0, entLen, TYPE_TREE) < 0) {
|
||||
// Next DirCacheEntry sorts before new entry as tree. Defer a
|
||||
// DeleteTree command to delete any entries if they exist. This
|
||||
// case only happens for A, A.c, A/c type of conflicts (rare).
|
||||
insertEdit(new DeleteTree(entPath));
|
||||
return eIdx;
|
||||
}
|
||||
|
||||
// Next entry may be contained by the entry-as-tree, skip if so.
|
||||
while (eIdx < maxEnt && inDir(cache.getEntry(eIdx), entPath, entLen)) {
|
||||
eIdx++;
|
||||
}
|
||||
return eIdx;
|
||||
}
|
||||
|
||||
private int findEntry(byte[] p, int pLen) {
|
||||
int low = 0;
|
||||
int high = entryCnt;
|
||||
while (low < high) {
|
||||
int mid = (low + high) >>> 1;
|
||||
int cmp = cmp(p, pLen, entries[mid]);
|
||||
if (cmp < 0) {
|
||||
high = mid;
|
||||
} else if (cmp == 0) {
|
||||
while (mid > 0 && cmp(p, pLen, entries[mid - 1]) == 0) {
|
||||
mid--;
|
||||
}
|
||||
return mid;
|
||||
} else {
|
||||
low = mid + 1;
|
||||
}
|
||||
}
|
||||
return -(low + 1);
|
||||
}
|
||||
|
||||
private void insertEdit(DeleteTree d) {
|
||||
for (int i = editIdx; i < edits.size(); i++) {
|
||||
int cmp = EDIT_CMP.compare(d, edits.get(i));
|
||||
if (cmp < 0) {
|
||||
edits.add(i, d);
|
||||
return;
|
||||
} else if (cmp == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
edits.add(d);
|
||||
}
|
||||
|
||||
private static boolean inDir(DirCacheEntry e, byte[] path, int pLen) {
|
||||
return e.path.length > pLen && e.path[pLen] == '/'
|
||||
&& peq(path, e.path, pLen);
|
||||
}
|
||||
|
||||
private static int pdir(byte[] path, int e) {
|
||||
for (e--; e > 0; e--) {
|
||||
if (path[e] == '/') {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Any index record update.
|
||||
* <p>
|
||||
|
@ -181,6 +289,7 @@ private void applyEdits() {
|
|||
*/
|
||||
public abstract static class PathEdit {
|
||||
final byte[] path;
|
||||
boolean replace = true;
|
||||
|
||||
/**
|
||||
* Create a new update command by path name.
|
||||
|
@ -192,6 +301,10 @@ public PathEdit(final String entryPath) {
|
|||
path = Constants.encode(entryPath);
|
||||
}
|
||||
|
||||
PathEdit(byte[] path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new update command for an existing entry instance.
|
||||
*
|
||||
|
@ -203,6 +316,22 @@ public PathEdit(final DirCacheEntry ent) {
|
|||
path = ent.path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure if a file can replace a directory (or vice versa).
|
||||
* <p>
|
||||
* Default is {@code true} as this is usually the desired behavior.
|
||||
*
|
||||
* @param ok
|
||||
* if true a file can replace a directory, or a directory can
|
||||
* replace a file.
|
||||
* @return {@code this}
|
||||
* @since 4.2
|
||||
*/
|
||||
public PathEdit setReplace(boolean ok) {
|
||||
replace = ok;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the update to a single cache entry matching the path.
|
||||
* <p>
|
||||
|
@ -214,6 +343,12 @@ public PathEdit(final DirCacheEntry ent) {
|
|||
* the path is a new path in the index.
|
||||
*/
|
||||
public abstract void apply(DirCacheEntry ent);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String p = DirCacheEntry.toString(path);
|
||||
return getClass().getSimpleName() + '[' + p + ']';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -281,6 +416,21 @@ public DeleteTree(String entryPath) {
|
|||
: entryPath + '/');
|
||||
}
|
||||
|
||||
DeleteTree(byte[] path) {
|
||||
super(appendSlash(path));
|
||||
}
|
||||
|
||||
private static byte[] appendSlash(byte[] path) {
|
||||
int n = path.length;
|
||||
if (n > 0 && path[n - 1] != '/') {
|
||||
byte[] r = new byte[n + 1];
|
||||
System.arraycopy(path, 0, r, 0, n);
|
||||
r[n] = '/';
|
||||
return r;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public void apply(final DirCacheEntry ent) {
|
||||
throw new UnsupportedOperationException(JGitText.get().noApplyInDelete);
|
||||
}
|
||||
|
|
|
@ -745,7 +745,7 @@ private static void checkPath(byte[] path) {
|
|||
}
|
||||
}
|
||||
|
||||
private static String toString(final byte[] path) {
|
||||
static String toString(final byte[] path) {
|
||||
return Constants.CHARSET.decode(ByteBuffer.wrap(path)).toString();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright (C) 2015, Google Inc.
|
||||
* and other copyright owners as documented in the project's IP log.
|
||||
*
|
||||
* This program and the accompanying materials are made available
|
||||
* under the terms of the Eclipse Distribution License v1.0 which
|
||||
* accompanies this distribution, is reproduced below, and is
|
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or
|
||||
* without modification, are permitted provided that the following
|
||||
* conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
*
|
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the
|
||||
* names of its contributors may be used to endorse or promote
|
||||
* products derived from this software without specific prior
|
||||
* written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.eclipse.jgit.errors;
|
||||
|
||||
/**
|
||||
* Thrown by DirCache code when entries overlap in impossible way.
|
||||
*
|
||||
* @since 4.2
|
||||
*/
|
||||
public class DirCacheNameConflictException extends IllegalStateException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String path1;
|
||||
private final String path2;
|
||||
|
||||
/**
|
||||
* Construct an exception for a specific path.
|
||||
*
|
||||
* @param path1
|
||||
* one path that conflicts.
|
||||
* @param path2
|
||||
* another path that conflicts.
|
||||
*/
|
||||
public DirCacheNameConflictException(String path1, String path2) {
|
||||
super(path1 + ' ' + path2);
|
||||
this.path1 = path1;
|
||||
this.path2 = path2;
|
||||
}
|
||||
|
||||
/** @return one of the paths that has a conflict. */
|
||||
public String getPath1() {
|
||||
return path1;
|
||||
}
|
||||
|
||||
/** @return another path that has a conflict. */
|
||||
public String getPath2() {
|
||||
return path2;
|
||||
}
|
||||
}
|
|
@ -691,6 +691,14 @@ public void stopWalk() {
|
|||
// Do nothing by default. Most iterators do not care.
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the iterator implements {@link #stopWalk()}.
|
||||
* @since 4.2
|
||||
*/
|
||||
protected boolean needsStopWalk() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the length of the name component of the path for the current entry
|
||||
*/
|
||||
|
|
|
@ -142,4 +142,9 @@ public void stopWalk() {
|
|||
if (parent != null)
|
||||
parent.stopWalk();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean needsStopWalk() {
|
||||
return parent != null && parent.needsStopWalk();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,8 @@
|
|||
|
||||
package org.eclipse.jgit.treewalk;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jgit.dircache.DirCacheBuilder;
|
||||
import org.eclipse.jgit.errors.CorruptObjectException;
|
||||
import org.eclipse.jgit.lib.FileMode;
|
||||
|
@ -338,6 +340,41 @@ void skipEntriesEqual() throws CorruptObjectException {
|
|||
dfConflict = null;
|
||||
}
|
||||
|
||||
void stopWalk() throws IOException {
|
||||
if (!needsStopWalk()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Name conflicts make aborting early difficult. Multiple paths may
|
||||
// exist between the file and directory versions of a name. To ensure
|
||||
// the directory version is skipped over (as it was previously visited
|
||||
// during the file version step) requires popping up the stack and
|
||||
// finishing out each subtree that the walker dove into. Siblings in
|
||||
// parents do not need to be recursed into, bounding the cost.
|
||||
for (;;) {
|
||||
AbstractTreeIterator t = min();
|
||||
if (t.eof()) {
|
||||
if (depth > 0) {
|
||||
exitSubtree();
|
||||
popEntriesEqual();
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
currentHead = t;
|
||||
skipEntriesEqual();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean needsStopWalk() {
|
||||
for (AbstractTreeIterator t : trees) {
|
||||
if (t.needsStopWalk()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the current entry is covered by a directory/file conflict.
|
||||
*
|
||||
|
|
|
@ -57,6 +57,7 @@
|
|||
import org.eclipse.jgit.attributes.AttributesNode;
|
||||
import org.eclipse.jgit.attributes.AttributesNodeProvider;
|
||||
import org.eclipse.jgit.attributes.AttributesProvider;
|
||||
import org.eclipse.jgit.dircache.DirCacheBuildIterator;
|
||||
import org.eclipse.jgit.dircache.DirCacheIterator;
|
||||
import org.eclipse.jgit.errors.CorruptObjectException;
|
||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||
|
@ -256,7 +257,7 @@ public static TreeWalk forPath(final Repository db, final String path,
|
|||
|
||||
private boolean postOrderTraversal;
|
||||
|
||||
private int depth;
|
||||
int depth;
|
||||
|
||||
private boolean advance;
|
||||
|
||||
|
@ -665,12 +666,29 @@ public boolean next() throws MissingObjectException,
|
|||
return true;
|
||||
}
|
||||
} catch (StopWalkException stop) {
|
||||
for (final AbstractTreeIterator t : trees)
|
||||
t.stopWalk();
|
||||
stopWalk();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify iterators the walk is aborting.
|
||||
* <p>
|
||||
* Primarily to notify {@link DirCacheBuildIterator} the walk is aborting so
|
||||
* that it can copy any remaining entries.
|
||||
*
|
||||
* @throws IOException
|
||||
* if traversal of remaining entries throws an exception during
|
||||
* object access. This should never occur as remaining trees
|
||||
* should already be in memory, however the methods used to
|
||||
* finish traversal are declared to throw IOException.
|
||||
*/
|
||||
void stopWalk() throws IOException {
|
||||
for (AbstractTreeIterator t : trees) {
|
||||
t.stopWalk();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the tree iterator for the current entry.
|
||||
* <p>
|
||||
|
@ -1065,7 +1083,7 @@ void skipEntriesEqual() throws CorruptObjectException {
|
|||
}
|
||||
}
|
||||
|
||||
private void exitSubtree() {
|
||||
void exitSubtree() {
|
||||
depth--;
|
||||
for (int i = 0; i < trees.length; i++)
|
||||
trees[i] = trees[i].parent;
|
||||
|
|
Loading…
Reference in New Issue