IndexDiff: Provide stage state for conflicting entries

Adds a new method getConflictingStageStates() which returns a
Map<String, StageState> (path to stage state). StageState is an enum for
all possible stage combinations (BOTH_DELETED, ADDED_BY_US, ...).

This can be used to implement the conflict text for unmerged paths in
output of "git status" or in EGit for decorations/hints.

Bug: 403697
Change-Id: Ib461640a43111b7df4a0debe92ff69b82171329c
Signed-off-by: Chris Aniszczyk <zx@twitter.com>
This commit is contained in:
Robin Stocker 2013-03-21 01:39:04 +01:00 committed by Chris Aniszczyk
parent 1c40d83f52
commit 1080cc5a0d
2 changed files with 181 additions and 5 deletions

View File

@ -2,6 +2,7 @@
* Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* Copyright (C) 2013, Robin Stocker <robin@nibor.org>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@ -46,6 +47,7 @@
package org.eclipse.jgit.lib;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.File;
@ -61,10 +63,12 @@
import org.eclipse.jgit.api.MergeResult.MergeStatus;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEditor;
import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.IndexDiff.StageState;
import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.treewalk.FileTreeIterator;
@ -212,6 +216,8 @@ public void testConflicting() throws Exception {
assertEquals("[]", diff.getMissing().toString());
assertEquals("[]", diff.getModified().toString());
assertEquals("[a]", diff.getConflicting().toString());
assertEquals(StageState.BOTH_MODIFIED,
diff.getConflictingStageStates().get("a"));
assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
}
@ -251,6 +257,8 @@ public void testConflictingDeletedAndModified() throws Exception {
assertEquals("[]", diff.getMissing().toString());
assertEquals("[]", diff.getModified().toString());
assertEquals("[a]", diff.getConflicting().toString());
assertEquals(StageState.DELETED_BY_THEM,
diff.getConflictingStageStates().get("a"));
assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
}
@ -500,6 +508,46 @@ public void testAssumeUnchanged() throws Exception {
assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
}
@Test
public void testStageState() throws IOException {
final int base = DirCacheEntry.STAGE_1;
final int ours = DirCacheEntry.STAGE_2;
final int theirs = DirCacheEntry.STAGE_3;
verifyStageState(StageState.BOTH_DELETED, base);
verifyStageState(StageState.DELETED_BY_THEM, ours, base);
verifyStageState(StageState.DELETED_BY_US, base, theirs);
verifyStageState(StageState.BOTH_MODIFIED, base, ours, theirs);
verifyStageState(StageState.ADDED_BY_US, ours);
verifyStageState(StageState.BOTH_ADDED, ours, theirs);
verifyStageState(StageState.ADDED_BY_THEM, theirs);
assertTrue(StageState.BOTH_DELETED.hasBase());
assertFalse(StageState.BOTH_DELETED.hasOurs());
assertFalse(StageState.BOTH_DELETED.hasTheirs());
assertFalse(StageState.BOTH_ADDED.hasBase());
assertTrue(StageState.BOTH_ADDED.hasOurs());
assertTrue(StageState.BOTH_ADDED.hasTheirs());
}
private void verifyStageState(StageState expected, int... stages)
throws IOException {
DirCacheBuilder builder = db.lockDirCache().builder();
for (int stage : stages) {
DirCacheEntry entry = createEntry("a", FileMode.REGULAR_FILE,
stage, "content");
builder.add(entry);
}
builder.commit();
IndexDiff diff = new IndexDiff(db, Constants.HEAD,
new FileTreeIterator(db));
diff.diff();
assertEquals(
"Conflict for entries in stages " + Arrays.toString(stages),
expected, diff.getConflictingStageStates().get("a"));
}
private void removeFromIndex(String path) throws IOException {
final DirCache dirc = db.lockDirCache();
final DirCacheEditor edit = dirc.editor();

View File

@ -2,6 +2,7 @@
* Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
* Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2010, Jens Baumgart <jens.baumgart@sap.com>
* Copyright (C) 2013, Robin Stocker <robin@nibor.org>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@ -49,7 +50,9 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.jgit.dircache.DirCache;
@ -85,6 +88,105 @@
*/
public class IndexDiff {
/**
* Represents the state of the index for a certain path regarding the stages
* - which stages exist for a path and which not (base, ours, theirs).
* <p>
* This is used for figuring out what kind of conflict occurred.
*
* @see IndexDiff#getConflictingStageStates()
* @since 3.0
*/
public static enum StageState {
/**
* Exists in base, but neither in ours nor in theirs.
*/
BOTH_DELETED(1),
/**
* Only exists in ours.
*/
ADDED_BY_US(2),
/**
* Exists in base and ours, but no in theirs.
*/
DELETED_BY_THEM(3),
/**
* Only exists in theirs.
*/
ADDED_BY_THEM(4),
/**
* Exists in base and theirs, but not in ours.
*/
DELETED_BY_US(5),
/**
* Exists in ours and theirs, but not in base.
*/
BOTH_ADDED(6),
/**
* Exists in all stages, content conflict.
*/
BOTH_MODIFIED(7);
private final int stageMask;
private StageState(int stageMask) {
this.stageMask = stageMask;
}
int getStageMask() {
return stageMask;
}
/**
* @return whether there is a "base" stage entry
*/
public boolean hasBase() {
return (stageMask & 1) != 0;
}
/**
* @return whether there is an "ours" stage entry
*/
public boolean hasOurs() {
return (stageMask & 2) != 0;
}
/**
* @return whether there is a "theirs" stage entry
*/
public boolean hasTheirs() {
return (stageMask & 4) != 0;
}
static StageState fromMask(int stageMask) {
// bits represent: theirs, ours, base
switch (stageMask) {
case 1: // 0b001
return BOTH_DELETED;
case 2: // 0b010
return ADDED_BY_US;
case 3: // 0b011
return DELETED_BY_THEM;
case 4: // 0b100
return ADDED_BY_THEM;
case 5: // 0b101
return DELETED_BY_US;
case 6: // 0b110
return BOTH_ADDED;
case 7: // 0b111
return BOTH_MODIFIED;
default:
return null;
}
}
}
private static final class ProgressReportingFilter extends TreeFilter {
private final ProgressMonitor monitor;
@ -156,7 +258,7 @@ public TreeFilter clone() {
private Set<String> untracked = new HashSet<String>();
private Set<String> conflicts = new HashSet<String>();
private Map<String, StageState> conflicts = new HashMap<String, StageState>();
private Set<String> ignored;
@ -295,9 +397,13 @@ public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize,
if (dirCacheIterator != null) {
final DirCacheEntry dirCacheEntry = dirCacheIterator
.getDirCacheEntry();
if (dirCacheEntry != null && dirCacheEntry.getStage() > 0) {
conflicts.add(treeWalk.getPathString());
continue;
if (dirCacheEntry != null) {
int stage = dirCacheEntry.getStage();
if (stage > 0) {
String path = treeWalk.getPathString();
addConflict(path, stage);
continue;
}
}
}
@ -355,6 +461,18 @@ public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize,
return true;
}
private void addConflict(String path, int stage) {
StageState existingStageStates = conflicts.get(path);
byte stageMask = 0;
if (existingStageStates != null)
stageMask |= existingStageStates.getStageMask();
// stage 1 (base) should be shifted 0 times
int shifts = stage - 1;
stageMask |= (1 << shifts);
StageState stageState = StageState.fromMask(stageMask);
conflicts.put(path, stageState);
}
/**
* @return list of files added to the index, not in the tree
*/
@ -398,9 +516,19 @@ public Set<String> getUntracked() {
}
/**
* @return list of files that are in conflict
* @return list of files that are in conflict, corresponds to the keys of
* {@link #getConflictingStageStates()}
*/
public Set<String> getConflicting() {
return conflicts.keySet();
}
/**
* @return the map from each path of {@link #getConflicting()} to its
* corresponding {@link StageState}
* @since 3.0
*/
public Map<String, StageState> getConflictingStageStates() {
return conflicts;
}