DirCacheEditor unconditionally applied a PathEdit to all stages in the index. This gives wrong results if one wants to check out a file from some commit to resolve a conflict: JGit would update the working tree file multiple times (once per stage), and set all stages to point to the checked-out blob. C git replaces the stages by the entry for the checked-out file. To support this, add a DirCacheEntry.setStage() method so that CheckoutCommand can force the stage to zero. In DirCacheEditor, keep only the zero stage if the PathEdit re-set the stage. Bug: 568038 Change-Id: Ic7c635bb5aaa06ffaaeed50bc5e45702c56fc6d1 Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>tags/v5.10.0.202011251205-m3
@@ -1,5 +1,5 @@ | |||
/* | |||
* Copyright (C) 2011, Kevin Sawicki <kevin@github.com> and others | |||
* Copyright (C) 2011, 2020 Kevin Sawicki <kevin@github.com> and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
@@ -24,6 +24,7 @@ import org.eclipse.jgit.dircache.DirCacheEntry; | |||
import org.eclipse.jgit.errors.NoWorkTreeException; | |||
import org.eclipse.jgit.junit.RepositoryTestCase; | |||
import org.eclipse.jgit.lib.ConfigConstants; | |||
import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.lib.ObjectReader; | |||
import org.eclipse.jgit.lib.RepositoryState; | |||
import org.eclipse.jgit.lib.StoredConfig; | |||
@@ -309,6 +310,16 @@ public class PathCheckoutCommandTest extends RepositoryTestCase { | |||
assertStageOneToThree(FILE1); | |||
} | |||
@Test | |||
public void testCheckoutFileWithConflict() throws Exception { | |||
setupConflictingState(); | |||
assertEquals('[' + FILE1 + ']', | |||
git.status().call().getConflicting().toString()); | |||
git.checkout().setStartPoint(Constants.HEAD).addPath(FILE1).call(); | |||
assertEquals("3", read(FILE1)); | |||
assertTrue(git.status().call().isClean()); | |||
} | |||
@Test | |||
public void testCheckoutOursWhenNoBase() throws Exception { | |||
String file = "added.txt"; |
@@ -241,6 +241,46 @@ public class DirCacheEntryTest { | |||
} | |||
} | |||
@Test | |||
public void testSetStage() { | |||
DirCacheEntry e = new DirCacheEntry("some/path", DirCacheEntry.STAGE_1); | |||
e.setAssumeValid(true); | |||
e.setCreationTime(2L); | |||
e.setFileMode(FileMode.EXECUTABLE_FILE); | |||
e.setLastModified(EPOCH.plusMillis(3L)); | |||
e.setLength(100L); | |||
e.setObjectId(ObjectId | |||
.fromString("0123456789012345678901234567890123456789")); | |||
e.setUpdateNeeded(true); | |||
e.setStage(DirCacheEntry.STAGE_2); | |||
assertTrue(e.isAssumeValid()); | |||
assertEquals(2L, e.getCreationTime()); | |||
assertEquals( | |||
ObjectId.fromString("0123456789012345678901234567890123456789"), | |||
e.getObjectId()); | |||
assertEquals(FileMode.EXECUTABLE_FILE, e.getFileMode()); | |||
assertEquals(EPOCH.plusMillis(3L), e.getLastModifiedInstant()); | |||
assertEquals(100L, e.getLength()); | |||
assertEquals(DirCacheEntry.STAGE_2, e.getStage()); | |||
assertTrue(e.isUpdateNeeded()); | |||
assertEquals("some/path", e.getPathString()); | |||
e.setStage(DirCacheEntry.STAGE_0); | |||
assertTrue(e.isAssumeValid()); | |||
assertEquals(2L, e.getCreationTime()); | |||
assertEquals( | |||
ObjectId.fromString("0123456789012345678901234567890123456789"), | |||
e.getObjectId()); | |||
assertEquals(FileMode.EXECUTABLE_FILE, e.getFileMode()); | |||
assertEquals(EPOCH.plusMillis(3L), e.getLastModifiedInstant()); | |||
assertEquals(100L, e.getLength()); | |||
assertEquals(DirCacheEntry.STAGE_0, e.getStage()); | |||
assertTrue(e.isUpdateNeeded()); | |||
assertEquals("some/path", e.getPathString()); | |||
} | |||
@Test | |||
public void testCopyMetaDataWithStage() { | |||
copyMetaDataHelper(false); |
@@ -1,5 +1,5 @@ | |||
/* | |||
* Copyright (C) 2011, Robin Rosenberg and others | |||
* Copyright (C) 2011, 2020 Robin Rosenberg and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
@@ -123,6 +123,32 @@ public class DirCachePathEditTest { | |||
assertEquals(DirCacheEntry.STAGE_3, entries.get(2).getStage()); | |||
} | |||
@Test | |||
public void testPathEditWithStagesAndReset() throws Exception { | |||
DirCache dc = DirCache.newInCore(); | |||
DirCacheBuilder builder = new DirCacheBuilder(dc, 3); | |||
builder.add(createEntry("a", DirCacheEntry.STAGE_1)); | |||
builder.add(createEntry("a", DirCacheEntry.STAGE_2)); | |||
builder.add(createEntry("a", DirCacheEntry.STAGE_3)); | |||
builder.finish(); | |||
DirCacheEditor editor = dc.editor(); | |||
PathEdit edit = new PathEdit("a") { | |||
@Override | |||
public void apply(DirCacheEntry ent) { | |||
ent.setStage(DirCacheEntry.STAGE_0); | |||
} | |||
}; | |||
editor.add(edit); | |||
editor.finish(); | |||
assertEquals(1, dc.getEntryCount()); | |||
DirCacheEntry entry = dc.getEntry(0); | |||
assertEquals("a", entry.getPathString()); | |||
assertEquals(DirCacheEntry.STAGE_0, entry.getStage()); | |||
} | |||
@Test | |||
public void testFileReplacesTree() throws Exception { | |||
DirCache dc = DirCache.newInCore(); |
@@ -1,6 +1,6 @@ | |||
/* | |||
* Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com> | |||
* Copyright (C) 2011, Matthias Sohn <matthias.sohn@sap.com> and others | |||
* Copyright (C) 2011, 2020 Matthias Sohn <matthias.sohn@sap.com> and others | |||
* | |||
* This program and the accompanying materials are made available under the | |||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||
@@ -503,6 +503,11 @@ public class CheckoutCommand extends GitCommand<Ref> { | |||
editor.add(new PathEdit(path) { | |||
@Override | |||
public void apply(DirCacheEntry ent) { | |||
if (ent.getStage() != DirCacheEntry.STAGE_0) { | |||
// A checkout on a conflicting file stages the checked | |||
// out file and resolves the conflict. | |||
ent.setStage(DirCacheEntry.STAGE_0); | |||
} | |||
ent.setObjectId(blobId); | |||
ent.setFileMode(mode); | |||
checkoutPath(ent, r, |
@@ -1,6 +1,6 @@ | |||
/* | |||
* Copyright (C) 2008-2009, Google Inc. | |||
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others | |||
* 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 v. 1.0 which is available at | |||
@@ -139,10 +139,28 @@ public class DirCacheEditor extends BaseDirCacheEditor { | |||
: 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); | |||
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); | |||
} | |||
@@ -257,7 +275,9 @@ 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; |
@@ -541,6 +541,24 @@ public class DirCacheEntry { | |||
return (info[infoOffset + P_FLAGS] >>> 4) & 0x3; | |||
} | |||
/** | |||
* Sets the stage of an entry. | |||
* | |||
* @param stage | |||
* to set, in the range [0..3] | |||
* @throws IllegalArgumentException | |||
* if the stage is outside the range [0..3] | |||
* @since 5.10 | |||
*/ | |||
public void setStage(int stage) { | |||
if ((stage & ~0x3) != 0) { | |||
throw new IllegalArgumentException( | |||
"Invalid stage, must be in range [0..3]"); //$NON-NLS-1$ | |||
} | |||
byte flags = info[infoOffset + P_FLAGS]; | |||
info[infoOffset + P_FLAGS] = (byte) ((flags & 0xCF) | (stage << 4)); | |||
} | |||
/** | |||
* Returns whether this entry should be skipped from the working tree. | |||
* |