in org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java [1074:1428]
protected boolean processEntry(CanonicalTreeParser base,
CanonicalTreeParser ours, CanonicalTreeParser theirs,
DirCacheBuildIterator index, WorkingTreeIterator work,
boolean ignoreConflicts, Attributes[] attributes)
throws IOException {
enterSubtree = true;
final int modeO = tw.getRawMode(T_OURS);
final int modeT = tw.getRawMode(T_THEIRS);
final int modeB = tw.getRawMode(T_BASE);
boolean gitLinkMerging = isGitLink(modeO) || isGitLink(modeT)
|| isGitLink(modeB);
if (modeO == 0 && modeT == 0 && modeB == 0) {
// File is either untracked or new, staged but uncommitted
return true;
}
if (isIndexDirty()) {
return false;
}
DirCacheEntry ourDce = null;
if (index == null || index.getDirCacheEntry() == null) {
// create a fake DCE, but only if ours is valid. ours is kept only
// in case it is valid, so a null ourDce is ok in all other cases.
if (nonTree(modeO)) {
ourDce = new DirCacheEntry(tw.getRawPath());
ourDce.setObjectId(tw.getObjectId(T_OURS));
ourDce.setFileMode(tw.getFileMode(T_OURS));
}
} else {
ourDce = index.getDirCacheEntry();
}
if (nonTree(modeO) && nonTree(modeT) && tw.idEqual(T_OURS, T_THEIRS)) {
// OURS and THEIRS have equal content. Check the file mode
if (modeO == modeT) {
// content and mode of OURS and THEIRS are equal: it doesn't
// matter which one we choose. OURS is chosen. Since the index
// is clean (the index matches already OURS) we can keep the existing one
keep(ourDce);
// no checkout needed!
return true;
}
// same content but different mode on OURS and THEIRS.
// Try to merge the mode and report an error if this is
// not possible.
int newMode = mergeFileModes(modeB, modeO, modeT);
if (newMode != FileMode.MISSING.getBits()) {
if (newMode == modeO) {
// ours version is preferred
keep(ourDce);
} else {
// the preferred version THEIRS has a different mode
// than ours. Check it out!
if (isWorktreeDirty(work, ourDce)) {
return false;
}
// we know about length and lastMod only after we have
// written the new content.
// This will happen later. Set these values to 0 for know.
DirCacheEntry e = add(tw.getRawPath(), theirs,
DirCacheEntry.STAGE_0, EPOCH, 0);
addToCheckout(tw.getPathString(), e, attributes);
}
return true;
}
if (!ignoreConflicts) {
// FileModes are not mergeable. We found a conflict on modes.
// For conflicting entries we don't know lastModified and
// length.
// This path can be skipped on ignoreConflicts, so the caller
// could use virtual commit.
addConflict(base, ours, theirs);
unmergedPaths.add(tw.getPathString());
mergeResults.put(tw.getPathString(),
new MergeResult<>(Collections.emptyList()));
}
return true;
}
if (modeB == modeT && tw.idEqual(T_BASE, T_THEIRS)) {
// THEIRS was not changed compared to BASE. All changes must be in
// OURS. OURS is chosen. We can keep the existing entry.
if (ourDce != null) {
keep(ourDce);
}
// no checkout needed!
return true;
}
if (modeB == modeO && tw.idEqual(T_BASE, T_OURS)) {
// OURS was not changed compared to BASE. All changes must be in
// THEIRS. THEIRS is chosen.
// Check worktree before checking out THEIRS
if (isWorktreeDirty(work, ourDce)) {
return false;
}
if (nonTree(modeT)) {
// we know about length and lastMod only after we have written
// the new content.
// This will happen later. Set these values to 0 for know.
DirCacheEntry e = add(tw.getRawPath(), theirs,
DirCacheEntry.STAGE_0, EPOCH, 0);
if (e != null) {
addToCheckout(tw.getPathString(), e, attributes);
}
return true;
}
// we want THEIRS ... but THEIRS contains a folder or the
// deletion of the path. Delete what's in the working tree,
// which we know to be clean.
if (tw.getTreeCount() > T_FILE && tw.getRawMode(T_FILE) == 0) {
// Not present in working tree, so nothing to delete
return true;
}
if (modeT != 0 && modeT == modeB) {
// Base, ours, and theirs all contain a folder: don't delete
return true;
}
addDeletion(tw.getPathString(), nonTree(modeO), attributes[T_OURS]);
return true;
}
if (tw.isSubtree()) {
// file/folder conflicts: here I want to detect only file/folder
// conflict between ours and theirs. file/folder conflicts between
// base/index/workingTree and something else are not relevant or
// detected later
if (nonTree(modeO) != nonTree(modeT)) {
if (ignoreConflicts) {
// In case of merge failures, ignore this path instead of reporting unmerged, so
// a caller can use virtual commit. This will not result in files with conflict
// markers in the index/working tree. The actual diff on the path will be
// computed directly on children.
enterSubtree = false;
return true;
}
if (nonTree(modeB)) {
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
}
if (nonTree(modeO)) {
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
}
if (nonTree(modeT)) {
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
}
unmergedPaths.add(tw.getPathString());
enterSubtree = false;
return true;
}
// ours and theirs are both folders or both files (and treewalk
// tells us we are in a subtree because of index or working-dir).
// If they are both folders no content-merge is required - we can
// return here.
if (!nonTree(modeO)) {
return true;
}
// ours and theirs are both files, just fall out of the if block
// and do the content merge
}
if (nonTree(modeO) && nonTree(modeT)) {
// Check worktree before modifying files
boolean worktreeDirty = isWorktreeDirty(work, ourDce);
if (!attributes[T_OURS].canBeContentMerged() && worktreeDirty) {
return false;
}
if (gitLinkMerging && ignoreConflicts) {
// Always select 'ours' in case of GITLINK merge failures so
// a caller can use virtual commit.
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0, EPOCH, 0);
return true;
} else if (gitLinkMerging) {
addConflict(base, ours, theirs);
MergeResult<SubmoduleConflict> result = createGitLinksMergeResult(
base, ours, theirs);
result.setContainsConflicts(true);
mergeResults.put(tw.getPathString(), result);
unmergedPaths.add(tw.getPathString());
return true;
} else if (!attributes[T_OURS].canBeContentMerged()) {
// File marked as binary
switch (getContentMergeStrategy()) {
case OURS:
keep(ourDce);
return true;
case THEIRS:
DirCacheEntry theirEntry = add(tw.getRawPath(), theirs,
DirCacheEntry.STAGE_0, EPOCH, 0);
addToCheckout(tw.getPathString(), theirEntry, attributes);
return true;
default:
break;
}
addConflict(base, ours, theirs);
// attribute merge issues are conflicts but not failures
unmergedPaths.add(tw.getPathString());
return true;
}
// Check worktree before modifying files
if (worktreeDirty) {
return false;
}
MergeResult<RawText> result = null;
boolean hasSymlink = FileMode.SYMLINK.equals(modeO)
|| FileMode.SYMLINK.equals(modeT);
if (!hasSymlink) {
try {
result = contentMerge(base, ours, theirs, attributes,
getContentMergeStrategy());
} catch (BinaryBlobException e) {
// result == null
}
}
if (result == null) {
switch (getContentMergeStrategy()) {
case OURS:
keep(ourDce);
return true;
case THEIRS:
DirCacheEntry e = add(tw.getRawPath(), theirs,
DirCacheEntry.STAGE_0, EPOCH, 0);
if (e != null) {
addToCheckout(tw.getPathString(), e, attributes);
}
return true;
default:
result = new MergeResult<>(Collections.emptyList());
result.setContainsConflicts(true);
break;
}
}
if (ignoreConflicts) {
result.setContainsConflicts(false);
}
String currentPath = tw.getPathString();
if (hasSymlink) {
if (ignoreConflicts) {
if (((modeT & FileMode.TYPE_MASK) == FileMode.TYPE_FILE)) {
DirCacheEntry e = add(tw.getRawPath(), theirs,
DirCacheEntry.STAGE_0, EPOCH, 0);
addToCheckout(currentPath, e, attributes);
} else {
keep(ourDce);
}
} else {
// Record the conflict
DirCacheEntry e = addConflict(base, ours, theirs);
mergeResults.put(currentPath, result);
// If theirs is a file, check it out. In link/file
// conflicts, C git prefers the file.
if (((modeT & FileMode.TYPE_MASK) == FileMode.TYPE_FILE)
&& e != null) {
addToCheckout(currentPath, e, attributes);
}
}
} else {
updateIndex(base, ours, theirs, result, attributes[T_OURS]);
}
if (result.containsConflicts() && !ignoreConflicts) {
unmergedPaths.add(currentPath);
}
workTreeUpdater.markAsModified(currentPath);
// Entry is null - only adds the metadata.
addToCheckout(currentPath, null, attributes);
} else if (modeO != modeT) {
// OURS or THEIRS has been deleted
if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw
.idEqual(T_BASE, T_THEIRS)))) {
if (gitLinkMerging && ignoreConflicts) {
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0, EPOCH, 0);
} else if (gitLinkMerging) {
addConflict(base, ours, theirs);
MergeResult<SubmoduleConflict> result = createGitLinksMergeResult(
base, ours, theirs);
result.setContainsConflicts(true);
mergeResults.put(tw.getPathString(), result);
unmergedPaths.add(tw.getPathString());
} else {
boolean isSymLink = ((modeO | modeT)
& FileMode.TYPE_MASK) == FileMode.TYPE_SYMLINK;
// Content merge strategy does not apply to delete-modify
// conflicts!
MergeResult<RawText> result;
if (isSymLink) {
// No need to do a content merge
result = new MergeResult<>(Collections.emptyList());
result.setContainsConflicts(true);
} else {
try {
result = contentMerge(base, ours, theirs,
attributes, ContentMergeStrategy.CONFLICT);
} catch (BinaryBlobException e) {
result = new MergeResult<>(Collections.emptyList());
result.setContainsConflicts(true);
}
}
if (ignoreConflicts) {
result.setContainsConflicts(false);
if (isSymLink) {
if (modeO != 0) {
keep(ourDce);
} else {
// Check out theirs
if (isWorktreeDirty(work, ourDce)) {
return false;
}
DirCacheEntry e = add(tw.getRawPath(), theirs,
DirCacheEntry.STAGE_0, EPOCH, 0);
if (e != null) {
addToCheckout(tw.getPathString(), e,
attributes);
}
}
} else {
// In case a conflict is detected the working tree
// file is again filled with new content (containing
// conflict markers). But also stage 0 of the index
// is filled with that content.
updateIndex(base, ours, theirs, result,
attributes[T_OURS]);
}
} else {
DirCacheEntry e = addConflict(base, ours, theirs);
// OURS was deleted checkout THEIRS
if (modeO == 0) {
// Check worktree before checking out THEIRS
if (isWorktreeDirty(work, ourDce)) {
return false;
}
if (nonTree(modeT) && e != null) {
addToCheckout(tw.getPathString(), e,
attributes);
}
}
unmergedPaths.add(tw.getPathString());
// generate a MergeResult for the deleted file
mergeResults.put(tw.getPathString(), result);
}
}
}
}
return true;
}