Allow to resolve a conflict by checking out a file
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>
This commit is contained in:
parent
ff6a827f4f
commit
e84881ea6b
|
@ -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.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 void testCheckoutTheirs() throws Exception {
|
|||
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 void testSetFileMode() {
|
|||
}
|
||||
}
|
||||
|
||||
@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 void testPathEditShouldBeCalledForEachStage() throws Exception {
|
|||
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 @@ private void checkoutPathsFromCommit(TreeWalk treeWalk, DirCache dc,
|
|||
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 @@ private void applyEdits() {
|
|||
: 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 @@ private static int pdir(byte[] path, int e) {
|
|||
* {@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 int getStage() {
|
|||
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.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue