aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java')
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java298
1 files changed, 221 insertions, 77 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
index 13885d370c..b1f4e7db21 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
@@ -1,49 +1,20 @@
/*
- * Copyright (C) 2008-2009, Google Inc.
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
+ * Copyright (C) 2008, 2009, Google Inc.
+ * Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> and others
*
- * 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
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://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.
+ * SPDX-License-Identifier: BSD-3-Clause
*/
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;
@@ -53,30 +24,32 @@ import java.util.List;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.util.Paths;
/**
- * Updates a {@link DirCache} by supplying discrete edit commands.
+ * Updates a {@link org.eclipse.jgit.dircache.DirCache} by supplying discrete
+ * edit commands.
* <p>
- * An editor updates a DirCache by taking a list of {@link PathEdit} commands
- * and executing them against the entries of the destination cache to produce a
- * new cache. This edit style allows applications to insert a few commands and
- * then have the editor compute the proper entry indexes necessary to perform an
+ * An editor updates a DirCache by taking a list of
+ * {@link org.eclipse.jgit.dircache.DirCacheEditor.PathEdit} commands and
+ * executing them against the entries of the destination cache to produce a new
+ * cache. This edit style allows applications to insert a few commands and then
+ * have the editor compute the proper entry indexes necessary to perform an
* efficient in-order update of the index records. This can be easier to use
- * than {@link DirCacheBuilder}.
- * <p>
+ * than {@link org.eclipse.jgit.dircache.DirCacheBuilder}.
*
* @see DirCacheBuilder
*/
public class DirCacheEditor extends BaseDirCacheEditor {
- private static final Comparator<PathEdit> EDIT_CMP = new Comparator<PathEdit>() {
- 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);
- }
+ private static final Comparator<PathEdit> EDIT_CMP = (PathEdit o1,
+ PathEdit o2) -> {
+ final byte[] a = o1.path;
+ final byte[] b = o2.path;
+ return cmp(a, a.length, b, b.length);
};
private final List<PathEdit> edits;
+ private int editIdx;
/**
* Construct a new editor.
@@ -87,9 +60,9 @@ public class DirCacheEditor extends BaseDirCacheEditor {
* estimated number of entries the editor will have upon
* completion. This sizes the initial entry table.
*/
- protected DirCacheEditor(final DirCache dc, final int ecnt) {
+ protected DirCacheEditor(DirCache dc, int ecnt) {
super(dc, ecnt);
- edits = new ArrayList<PathEdit>();
+ edits = new ArrayList<>();
}
/**
@@ -102,7 +75,7 @@ public class DirCacheEditor extends BaseDirCacheEditor {
* @param edit
* another edit command.
*/
- public void add(final PathEdit edit) {
+ public void add(PathEdit edit) {
edits.add(edit);
}
@@ -117,6 +90,7 @@ public class DirCacheEditor extends BaseDirCacheEditor {
return super.commit();
}
+ @Override
public void finish() {
if (!edits.isEmpty()) {
applyEdits();
@@ -126,37 +100,64 @@ 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()));
+ 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)
- for (int i = eIdx; i < lastIdx; i++) {
- final DirCacheEntry ent = cache.getEntry(i);
+ lastIdx = cache.nextEntry(eIdx);
+ if (lastIdx > eIdx + 1) {
+ // Apply to all entries of the current path (different
+ // stages). If any apply() resets the stage to STAGE_0, take
+ // only that entry and omit all others.
+ DirCacheEntry[] tmp = new DirCacheEntry[lastIdx - eIdx];
+ int n = 0;
+ for (int i = eIdx; i < lastIdx; i++) {
+ DirCacheEntry ent = cache.getEntry(i);
+ e.apply(ent);
+ if (ent.getStage() == DirCacheEntry.STAGE_0) {
+ fastAdd(ent);
+ n = 0;
+ break;
+ }
+ tmp[n++] = ent;
+ }
+ for (int i = 0; i < n; i++) {
+ fastAdd(tmp[i]);
+ }
+ } else {
+ DirCacheEntry ent = cache.getEntry(eIdx);
e.apply(ent);
fastAdd(ent);
}
@@ -168,6 +169,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 (Paths.compare(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>
@@ -175,10 +272,13 @@ public class DirCacheEditor extends BaseDirCacheEditor {
* {@link #apply(DirCacheEntry)} method. The editor will invoke apply once
* for each record in the index which matches the path name. If there are
* multiple records (for example in stages 1, 2 and 3), the edit instance
- * will be called multiple times, once for each stage.
+ * will be called multiple times, once for each stage. If any of these calls
+ * resets the stage to 0, only this entry will be taken and entries for
+ * other stages are discarded.
*/
public abstract static class PathEdit {
final byte[] path;
+ boolean replace = true;
/**
* Create a new update command by path name.
@@ -186,10 +286,14 @@ public class DirCacheEditor extends BaseDirCacheEditor {
* @param entryPath
* path of the file within the repository.
*/
- public PathEdit(final String entryPath) {
+ public PathEdit(String entryPath) {
path = Constants.encode(entryPath);
}
+ PathEdit(byte[] path) {
+ this.path = path;
+ }
+
/**
* Create a new update command for an existing entry instance.
*
@@ -197,11 +301,27 @@ public class DirCacheEditor extends BaseDirCacheEditor {
* entry instance to match path of. Only the path of this
* entry is actually considered during command evaluation.
*/
- public PathEdit(final DirCacheEntry ent) {
+ public PathEdit(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>
* After apply is invoked the entry is added to the output table, and
@@ -212,6 +332,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 + ']';
+ }
}
/**
@@ -230,7 +356,7 @@ public class DirCacheEditor extends BaseDirCacheEditor {
* @param entryPath
* path of the file within the repository.
*/
- public DeletePath(final String entryPath) {
+ public DeletePath(String entryPath) {
super(entryPath);
}
@@ -241,11 +367,12 @@ public class DirCacheEditor extends BaseDirCacheEditor {
* entry instance to remove. Only the path of this entry is
* actually considered during command evaluation.
*/
- public DeletePath(final DirCacheEntry ent) {
+ public DeletePath(DirCacheEntry ent) {
super(ent);
}
- public void apply(final DirCacheEntry ent) {
+ @Override
+ public void apply(DirCacheEntry ent) {
throw new UnsupportedOperationException(JGitText.get().noApplyInDelete);
}
}
@@ -272,13 +399,30 @@ public class DirCacheEditor extends BaseDirCacheEditor {
* only the subtree's contents are matched by the command.
* The special case "" (not "/"!) deletes all entries.
*/
- public DeleteTree(final String entryPath) {
- super(
- (entryPath.endsWith("/") || entryPath.length() == 0) ? entryPath //$NON-NLS-1$
- : entryPath + "/"); //$NON-NLS-1$
+ public DeleteTree(String entryPath) {
+ super(entryPath.isEmpty()
+ || entryPath.charAt(entryPath.length() - 1) == '/'
+ ? 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) {
+ @Override
+ public void apply(DirCacheEntry ent) {
throw new UnsupportedOperationException(JGitText.get().noApplyInDelete);
}
}