Browse Source

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>
tags/v5.10.0.202011251205-m3
Thomas Wolf 3 years ago
parent
commit
e84881ea6b

+ 12
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java View File

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

+ 40
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java View File

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

+ 27
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java View File

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

+ 6
- 1
org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java View File

@@ -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,

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

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

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

@@ -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.
*

Loading…
Cancel
Save