Browse Source

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
tags/v4.2.0.201601211800-r
Shawn Pearce 8 years ago
parent
commit
163be57d0f

+ 97
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java View File

@@ -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 class AddCommandTest extends RepositoryTestCase {

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();

+ 104
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java View File

@@ -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 class DirCachePathEditTest {
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);

+ 2
- 4
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java View File

@@ -1084,7 +1084,7 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
assertWorkDir(mkmap(linkName, "a", fname, "a"));

Status st = git.status().call();
assertFalse(st.isClean());
assertTrue(st.isClean());
}

@Test
@@ -1213,9 +1213,7 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
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

+ 18
- 5
org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java View File

@@ -45,6 +45,7 @@ package org.eclipse.jgit.api;

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.ObjectId;
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 class AddCommand extends GitCommand<DirCache> {
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 class AddCommand extends GitCommand<DirCache> {
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 class AddCommand extends GitCommand<DirCache> {
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 class AddCommand extends GitCommand<DirCache> {
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);

+ 98
- 0
org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java View File

@@ -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 @@ abstract class BaseDirCacheEditor {
* {@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 @@ abstract class BaseDirCacheEditor {
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>

+ 5
- 2
org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java View File

@@ -800,8 +800,11 @@ public class DirCache {
* information. If &lt; 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;

+ 5
- 0
org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java View File

@@ -130,4 +130,9 @@ public class DirCacheBuildIterator extends DirCacheIterator {
if (cur < cnt)
builder.keep(cur, cnt - cur);
}

@Override
protected boolean needsStopWalk() {
return ptr < cache.getEntryCount();
}
}

+ 156
- 6
org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java View File

@@ -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);
}

+ 1
- 1
org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java View File

@@ -745,7 +745,7 @@ public class DirCacheEntry {
}
}

private static String toString(final byte[] path) {
static String toString(final byte[] path) {
return Constants.CHARSET.decode(ByteBuffer.wrap(path)).toString();
}


+ 80
- 0
org.eclipse.jgit/src/org/eclipse/jgit/errors/DirCacheNameConflictException.java View File

@@ -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;
}
}

+ 8
- 0
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java View File

@@ -691,6 +691,14 @@ public abstract class AbstractTreeIterator {
// 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
*/

+ 5
- 0
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java View File

@@ -142,4 +142,9 @@ public class EmptyTreeIterator extends AbstractTreeIterator {
if (parent != null)
parent.stopWalk();
}

@Override
protected boolean needsStopWalk() {
return parent != null && parent.needsStopWalk();
}
}

+ 37
- 0
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java View File

@@ -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 @@ public class NameConflictTreeWalk extends TreeWalk {
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.
*

+ 22
- 4
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java View File

@@ -57,6 +57,7 @@ import org.eclipse.jgit.attributes.Attributes;
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 class TreeWalk implements AutoCloseable, AttributesProvider {

private boolean postOrderTraversal;

private int depth;
int depth;

private boolean advance;

@@ -665,12 +666,29 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
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 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
}
}

private void exitSubtree() {
void exitSubtree() {
depth--;
for (int i = 0; i < trees.length; i++)
trees[i] = trees[i].parent;

Loading…
Cancel
Save