aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java')
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java214
1 files changed, 152 insertions, 62 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
index e56513d4e9..dc96f65b87 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -3,8 +3,8 @@
* Copyright (C) 2010-2012, Matthias Sohn <matthias.sohn@sap.com>
* Copyright (C) 2012, Research In Motion Limited
* Copyright (C) 2017, Obeo (mathieu.cartaud@obeo.fr)
- * Copyright (C) 2018, 2022 Thomas Wolf <twolf@apache.org>
- * Copyright (C) 2022, Google Inc. and others
+ * Copyright (C) 2018, 2023 Thomas Wolf <twolf@apache.org>
+ * Copyright (C) 2023, Google Inc. 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
@@ -32,7 +32,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -42,11 +41,13 @@ import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.attributes.Attribute;
import org.eclipse.jgit.attributes.Attributes;
+import org.eclipse.jgit.attributes.AttributesNodeProvider;
import org.eclipse.jgit.diff.DiffAlgorithm;
import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.diff.Sequence;
+import org.eclipse.jgit.dircache.Checkout;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuildIterator;
import org.eclipse.jgit.dircache.DirCacheBuilder;
@@ -79,7 +80,6 @@ import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
-import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.LfsFactory;
import org.eclipse.jgit.util.LfsFactory.LfsInputStream;
import org.eclipse.jgit.util.TemporaryBuffer;
@@ -106,13 +106,15 @@ public class ResolveMerger extends ThreeWayMerger {
*/
public static class Result {
- private final List<String> modifiedFiles = new LinkedList<>();
+ private final List<String> modifiedFiles = new ArrayList<>();
- private final List<String> failedToDelete = new LinkedList<>();
+ private final List<String> failedToDelete = new ArrayList<>();
private ObjectId treeId = null;
/**
+ * Get modified tree id if any
+ *
* @return Modified tree ID if any, or null otherwise.
*/
public ObjectId getTreeId() {
@@ -120,6 +122,8 @@ public class ResolveMerger extends ThreeWayMerger {
}
/**
+ * Get path of files that couldn't be deleted
+ *
* @return Files that couldn't be deleted.
*/
public List<String> getFailedToDelete() {
@@ -127,6 +131,8 @@ public class ResolveMerger extends ThreeWayMerger {
}
/**
+ * Get path of modified files
+ *
* @return Files modified during this operation.
*/
public List<String> getModifiedFiles() {
@@ -205,6 +211,12 @@ public class ResolveMerger extends ThreeWayMerger {
private boolean indexChangesWritten;
/**
+ * {@link Checkout} to use for actually checking out files if
+ * {@link #inCore} is {@code false}.
+ */
+ private Checkout checkout;
+
+ /**
* @param repo
* the {@link Repository}.
* @param dirCache
@@ -223,6 +235,7 @@ public class ResolveMerger extends ThreeWayMerger {
this.inCoreFileSizeLimit = getInCoreFileSizeLimit(config);
this.checkoutMetadataByPath = new HashMap<>();
this.cleanupMetadataByPath = new HashMap<>();
+ this.checkout = new Checkout(nonNullRepo(), workingTreeOptions);
}
/**
@@ -350,9 +363,8 @@ public class ResolveMerger extends ThreeWayMerger {
}
// All content operations are successfully done. If we can now write
- // the
- // new index we are on quite safe ground. Even if the checkout of
- // files coming from "theirs" fails the user can work around such
+ // the new index we are on quite safe ground. Even if the checkout
+ // of files coming from "theirs" fails the user can work around such
// failures by checking out the index again.
if (!builder.commit()) {
revertModifiedFiles();
@@ -466,7 +478,6 @@ public class ResolveMerger extends ThreeWayMerger {
/**
* Detects if CRLF conversion has been configured.
* <p>
- * </p>
* See {@link EolStreamTypeUtil#detectStreamType} for more info.
*
* @param attributes
@@ -518,14 +529,14 @@ public class ResolveMerger extends ThreeWayMerger {
for (Map.Entry<String, DirCacheEntry> entry : toBeCheckedOut
.entrySet()) {
DirCacheEntry dirCacheEntry = entry.getValue();
+ String gitPath = entry.getKey();
if (dirCacheEntry.getFileMode() == FileMode.GITLINK) {
- new File(nonNullRepo().getWorkTree(), entry.getKey())
- .mkdirs();
+ checkout.checkoutGitlink(dirCacheEntry, gitPath);
} else {
- DirCacheCheckout.checkoutEntry(repo, dirCacheEntry, reader,
- false, checkoutMetadataByPath.get(entry.getKey()),
- workingTreeOptions);
- result.modifiedFiles.add(entry.getKey());
+ checkout.checkout(dirCacheEntry,
+ checkoutMetadataByPath.get(gitPath), reader,
+ gitPath);
+ result.modifiedFiles.add(gitPath);
}
}
}
@@ -550,9 +561,8 @@ public class ResolveMerger extends ThreeWayMerger {
for (String path : result.modifiedFiles) {
DirCacheEntry entry = dirCache.getEntry(path);
if (entry != null) {
- DirCacheCheckout.checkoutEntry(repo, entry, reader, false,
- cleanupMetadataByPath.get(path),
- workingTreeOptions);
+ checkout.checkout(entry, cleanupMetadataByPath.get(path),
+ reader, path);
}
}
}
@@ -586,6 +596,8 @@ public class ResolveMerger extends ThreeWayMerger {
if (inCore) {
return;
}
+ checkout.safeCreateParentDirectory(path, file.getParentFile(),
+ false);
CheckoutMetadata metadata = new CheckoutMetadata(streamType,
smudgeCommand);
@@ -826,6 +838,13 @@ public class ResolveMerger extends ThreeWayMerger {
@NonNull
private ContentMergeStrategy contentStrategy = ContentMergeStrategy.CONFLICT;
+ /**
+ * The {@link AttributesNodeProvider} to use while merging trees.
+ *
+ * @since 6.10.1
+ */
+ protected AttributesNodeProvider attributesNodeProvider;
+
private static MergeAlgorithm getMergeAlgorithm(Config config) {
SupportedAlgorithm diffAlg = config.getEnum(
CONFIG_DIFF_SECTION, null, CONFIG_KEY_ALGORITHM,
@@ -904,7 +923,6 @@ public class ResolveMerger extends ThreeWayMerger {
: strategy;
}
- /** {@inheritDoc} */
@Override
protected boolean mergeImpl() throws IOException {
return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1],
@@ -915,18 +933,23 @@ public class ResolveMerger extends ThreeWayMerger {
* adds a new path with the specified stage to the index builder
*
* @param path
+ * the new path
* @param p
+ * canonical tree parser
* @param stage
- * @param lastMod
+ * the stage
+ * @param lastModified
+ * lastModified attribute of the file
* @param len
+ * file length
* @return the entry which was added to the index
*/
private DirCacheEntry add(byte[] path, CanonicalTreeParser p, int stage,
- Instant lastMod, long len) {
+ Instant lastModified, long len) {
if (p != null && !p.getEntryFileMode().equals(FileMode.TREE)) {
return workTreeUpdater.addExistingToIndex(p.getEntryObjectId(), path,
p.getEntryFileMode(), stage,
- lastMod, (int) len);
+ lastModified, (int) len);
}
return null;
}
@@ -1056,6 +1079,7 @@ public class ResolveMerger extends ThreeWayMerger {
* didn't match ours or the working-dir file was dirty and a
* conflict occurred
* @throws java.io.IOException
+ * if an IO error occurred
* @since 6.1
*/
protected boolean processEntry(CanonicalTreeParser base,
@@ -1257,10 +1281,22 @@ public class ResolveMerger extends ThreeWayMerger {
default:
break;
}
+ if (ignoreConflicts) {
+ // If the path is selected to be treated as binary via attributes, we do not perform
+ // content merge. When ignoreConflicts = true, we simply keep OURS to allow virtual commit
+ // to be built.
+ keep(ourDce);
+ return true;
+ }
+ // add the conflicting path to merge result
+ String currentPath = tw.getPathString();
+ MergeResult<RawText> result = new MergeResult<>(
+ Collections.emptyList());
+ result.setContainsConflicts(true);
+ mergeResults.put(currentPath, result);
addConflict(base, ours, theirs);
-
// attribute merge issues are conflicts but not failures
- unmergedPaths.add(tw.getPathString());
+ unmergedPaths.add(currentPath);
return true;
}
@@ -1272,38 +1308,52 @@ public class ResolveMerger extends ThreeWayMerger {
MergeResult<RawText> result = null;
boolean hasSymlink = FileMode.SYMLINK.equals(modeO)
|| FileMode.SYMLINK.equals(modeT);
+
+ String currentPath = tw.getPathString();
+ // if the path is not a symlink in ours and theirs
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);
+ if (result.containsConflicts() && !ignoreConflicts) {
+ result.setContainsConflicts(true);
+ unmergedPaths.add(currentPath);
+ } else if (ignoreConflicts) {
+ result.setContainsConflicts(false);
}
+ updateIndex(base, ours, theirs, result, attributes[T_OURS]);
+ workTreeUpdater.markAsModified(currentPath);
+ // Entry is null - only add the metadata
+ addToCheckout(currentPath, null, attributes);
return true;
- default:
- result = new MergeResult<>(Collections.emptyList());
- result.setContainsConflicts(true);
- break;
+ } catch (BinaryBlobException e) {
+ // The file is binary in either OURS, THEIRS or BASE
+ if (ignoreConflicts) {
+ // When ignoreConflicts = true, we simply keep OURS to allow virtual commit to be built.
+ keep(ourDce);
+ return true;
+ }
}
}
- if (ignoreConflicts) {
- result.setContainsConflicts(false);
+ 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(currentPath, e, attributes);
+ }
+ return true;
+ default:
+ result = new MergeResult<>(Collections.emptyList());
+ result.setContainsConflicts(true);
+ break;
}
- String currentPath = tw.getPathString();
if (hasSymlink) {
if (ignoreConflicts) {
+ result.setContainsConflicts(false);
if (((modeT & FileMode.TYPE_MASK) == FileMode.TYPE_FILE)) {
DirCacheEntry e = add(tw.getRawPath(), theirs,
DirCacheEntry.STAGE_0, EPOCH, 0);
@@ -1312,9 +1362,9 @@ public class ResolveMerger extends ThreeWayMerger {
keep(ourDce);
}
} else {
- // Record the conflict
DirCacheEntry e = addConflict(base, ours, theirs);
mergeResults.put(currentPath, result);
+ unmergedPaths.add(currentPath);
// 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)
@@ -1323,14 +1373,14 @@ public class ResolveMerger extends ThreeWayMerger {
}
}
} else {
- updateIndex(base, ours, theirs, result, attributes[T_OURS]);
- }
- if (result.containsConflicts() && !ignoreConflicts) {
+ // This is reachable if contentMerge() call above threw BinaryBlobException, so we don't
+ // need to check ignoreConflicts here, since it's already handled above.
+ result.setContainsConflicts(true);
+ addConflict(base, ours, theirs);
unmergedPaths.add(currentPath);
+ mergeResults.put(currentPath, result);
}
- workTreeUpdater.markAsModified(currentPath);
- // Entry is null - only adds the metadata.
- addToCheckout(currentPath, null, attributes);
+ return true;
} else if (modeO != modeT) {
// OURS or THEIRS has been deleted
if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw
@@ -1432,15 +1482,21 @@ public class ResolveMerger extends ThreeWayMerger {
* specified as <code>null</code> then an empty text will be used instead.
*
* @param base
+ * used to parse base tree
* @param ours
+ * used to parse ours tree
* @param theirs
+ * used to parse theirs tree
* @param attributes
+ * attributes for the different stages
* @param strategy
+ * merge strategy
*
* @return the result of the content merge
* @throws BinaryBlobException
* if any of the blobs looks like a binary blob
* @throws IOException
+ * if an IO error occurred
*/
private MergeResult<RawText> contentMerge(CanonicalTreeParser base,
CanonicalTreeParser ours, CanonicalTreeParser theirs,
@@ -1454,11 +1510,26 @@ public class ResolveMerger extends ThreeWayMerger {
: getRawText(ours.getEntryObjectId(), attributes[T_OURS]);
RawText theirsText = theirs == null ? RawText.EMPTY_TEXT
: getRawText(theirs.getEntryObjectId(), attributes[T_THEIRS]);
- mergeAlgorithm.setContentMergeStrategy(strategy);
+ mergeAlgorithm.setContentMergeStrategy(
+ getAttributesContentMergeStrategy(attributes[T_OURS],
+ strategy));
return mergeAlgorithm.merge(RawTextComparator.DEFAULT, baseText,
ourText, theirsText);
}
+ private ContentMergeStrategy getAttributesContentMergeStrategy(
+ Attributes attributes, ContentMergeStrategy strategy) {
+ Attribute attr = attributes.get(Constants.ATTR_MERGE);
+ if (attr != null) {
+ String attrValue = attr.getValue();
+ if (attrValue != null && attrValue
+ .equals(Constants.ATTR_BUILTIN_UNION_MERGE_DRIVER)) {
+ return ContentMergeStrategy.UNION;
+ }
+ }
+ return strategy;
+ }
+
private boolean isIndexDirty() {
if (inCore) {
return false;
@@ -1516,11 +1587,17 @@ public class ResolveMerger extends ThreeWayMerger {
* correct stages to the index.
*
* @param base
+ * used to parse base tree
* @param ours
+ * used to parse ours tree
* @param theirs
+ * used to parse theirs tree
* @param result
+ * merge result
* @param attributes
+ * the file's attributes
* @throws IOException
+ * if an IO error occurred
*/
private void updateIndex(CanonicalTreeParser base,
CanonicalTreeParser ours, CanonicalTreeParser theirs,
@@ -1571,20 +1648,17 @@ public class ResolveMerger extends ThreeWayMerger {
* the files .gitattributes entries
* @return the working tree file to which the merged content was written.
* @throws IOException
+ * if an IO error occurred
*/
private File writeMergedFile(TemporaryBuffer rawMerged,
Attributes attributes)
throws IOException {
File workTree = nonNullRepo().getWorkTree();
- FS fs = nonNullRepo().getFS();
- File of = new File(workTree, tw.getPathString());
- File parentFolder = of.getParentFile();
+ String gitPath = tw.getPathString();
+ File of = new File(workTree, gitPath);
EolStreamType eol = workTreeUpdater.detectCheckoutStreamType(attributes);
- if (!fs.exists(parentFolder)) {
- parentFolder.mkdirs();
- }
workTreeUpdater.updateFileWithContent(rawMerged::openInputStream,
- eol, tw.getSmudgeCommand(attributes), of.getPath(), of);
+ eol, tw.getSmudgeCommand(attributes), gitPath, of);
return of;
}
@@ -1659,7 +1733,6 @@ public class ResolveMerger extends ThreeWayMerger {
return FileMode.GITLINK.equals(mode);
}
- /** {@inheritDoc} */
@Override
public ObjectId getResultTreeId() {
return (resultTree == null) ? null : resultTree.toObjectId();
@@ -1787,6 +1860,18 @@ public class ResolveMerger extends ThreeWayMerger {
this.workingTreeIterator = workingTreeIterator;
}
+ /**
+ * Sets the {@link AttributesNodeProvider} to be used by this merger.
+ *
+ * @param attributesNodeProvider
+ * the attributeNodeProvider to set
+ * @since 6.10.1
+ */
+ public void setAttributesNodeProvider(
+ AttributesNodeProvider attributesNodeProvider) {
+ this.attributesNodeProvider = attributesNodeProvider;
+ }
+
/**
* The resolve conflict way of three way merging
@@ -1819,6 +1904,7 @@ public class ResolveMerger extends ThreeWayMerger {
* content-merge conflicts.
* @return whether the trees merged cleanly
* @throws java.io.IOException
+ * if an IO error occurred
* @since 3.5
*/
protected boolean mergeTrees(AbstractTreeIterator baseTree,
@@ -1830,6 +1916,9 @@ public class ResolveMerger extends ThreeWayMerger {
WorkTreeUpdater.createWorkTreeUpdater(db, dircache);
dircache = workTreeUpdater.getLockedDirCache();
tw = new NameConflictTreeWalk(db, reader);
+ if (attributesNodeProvider != null) {
+ tw.setAttributesNodeProvider(attributesNodeProvider);
+ }
tw.addTree(baseTree);
tw.setHead(tw.addTree(headTree));
@@ -1878,6 +1967,7 @@ public class ResolveMerger extends ThreeWayMerger {
* {@link org.eclipse.jgit.merge.ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, RevTree, boolean)}
* @return Whether the trees merged cleanly.
* @throws java.io.IOException
+ * if an IO error occurred
* @since 3.5
*/
protected boolean mergeTreeWalk(TreeWalk treeWalk, boolean ignoreConflicts)