|
|
@@ -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 class DirCacheEditor extends BaseDirCacheEditor { |
|
|
|
|
|
|
|
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 @@ public class DirCacheEditor extends BaseDirCacheEditor { |
|
|
|
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 @@ public class DirCacheEditor extends BaseDirCacheEditor { |
|
|
|
*/ |
|
|
|
public abstract static class PathEdit { |
|
|
|
final byte[] path; |
|
|
|
boolean replace = true; |
|
|
|
|
|
|
|
/** |
|
|
|
* Create a new update command by path name. |
|
|
@@ -192,6 +301,10 @@ public class DirCacheEditor extends BaseDirCacheEditor { |
|
|
|
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 class DirCacheEditor extends BaseDirCacheEditor { |
|
|
|
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 class DirCacheEditor extends BaseDirCacheEditor { |
|
|
|
* 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 class DirCacheEditor extends BaseDirCacheEditor { |
|
|
|
: 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); |
|
|
|
} |