Browse Source

Support -merge attribute in binary macro

The merger is now able to react to the use of the merge attribute.
The value unset and the custom value 'binary' are handled (-merge
and merge=binary)

Since the specification of the merge attribute states that when the
attribute is unset, ours version must be kept in case of a conflict, we
don't overwrite the file but keep the local version.

Bug: 517128
Change-Id: Ib5fbf17bdaf727bc5d0e106ce88f2620d9f87a6f
Signed-off-by: Mathieu Cartaud <mathieu.cartaud@obeo.fr>
stable-4.9
Mathieu Cartaud 8 years ago
parent
commit
f7e233e450
  1. BIN
      org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/attributes/merge/disabled_checked.gif
  2. BIN
      org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/attributes/merge/enabled_checked.gif
  3. 585
      org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/merge/MergeGitAttributeTest.java
  4. 8
      org.eclipse.jgit/.settings/.api_filters
  5. 24
      org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java
  6. 16
      org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
  7. 28
      org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java

BIN
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/attributes/merge/disabled_checked.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 B

BIN
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/attributes/merge/enabled_checked.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 874 B

585
org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/merge/MergeGitAttributeTest.java

@ -0,0 +1,585 @@
/*
* Copyright (C) 2017, Obeo (mathieu.cartaud@obeo.fr)
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.attributes.merge;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.function.Consumer;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.MergeResult;
import org.eclipse.jgit.api.MergeResult.MergeStatus;
import org.eclipse.jgit.api.errors.CheckoutConflictException;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidMergeHeadsException;
import org.eclipse.jgit.api.errors.NoFilepatternException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.NoMessageException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.attributes.Attribute;
import org.eclipse.jgit.attributes.Attributes;
import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.junit.Ignore;
import org.junit.Test;
public class MergeGitAttributeTest extends RepositoryTestCase {
private static final String REFS_HEADS_RIGHT = "refs/heads/right";
private static final String REFS_HEADS_MASTER = "refs/heads/master";
private static final String REFS_HEADS_LEFT = "refs/heads/left";
private static final String DISABLE_CHECK_BRANCH = "refs/heads/disabled_checked";
private static final String ENABLE_CHECKED_BRANCH = "refs/heads/enabled_checked";
private static final String ENABLED_CHECKED_GIF = "enabled_checked.gif";
public Git createRepositoryBinaryConflict(Consumer<Git> initialCommit,
Consumer<Git> leftCommit, Consumer<Git> rightCommit)
throws NoFilepatternException, GitAPIException, NoWorkTreeException,
IOException {
// Set up a git whith conflict commits on images
Git git = new Git(db);
// First commit
initialCommit.accept(git);
git.add().addFilepattern(".").call();
RevCommit firstCommit = git.commit().setAll(true)
.setMessage("initial commit adding git attribute file").call();
// Create branch and add an icon Checked_Boxe (enabled_checked)
createBranch(firstCommit, REFS_HEADS_LEFT);
checkoutBranch(REFS_HEADS_LEFT);
leftCommit.accept(git);
git.add().addFilepattern(".").call();
git.commit().setMessage("Left").call();
// Create a second branch from master Unchecked_Boxe
checkoutBranch(REFS_HEADS_MASTER);
createBranch(firstCommit, REFS_HEADS_RIGHT);
checkoutBranch(REFS_HEADS_RIGHT);
rightCommit.accept(git);
git.add().addFilepattern(".").call();
git.commit().setMessage("Right").call();
checkoutBranch(REFS_HEADS_LEFT);
return git;
}
@Test
public void mergeTextualFile_NoAttr() throws NoWorkTreeException,
NoFilepatternException, GitAPIException, IOException {
try (Git git = createRepositoryBinaryConflict(g -> {
try {
writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
} catch (IOException e) {
e.printStackTrace();
}
}, g -> {
try {
writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
} catch (IOException e) {
e.printStackTrace();
}
}, g -> {
try {
writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
} catch (IOException e) {
e.printStackTrace();
}
})) {
checkoutBranch(REFS_HEADS_LEFT);
// Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
MergeResult mergeResult = git.merge()
.include(git.getRepository().resolve(REFS_HEADS_RIGHT))
.call();
assertEquals(MergeStatus.MERGED, mergeResult.getMergeStatus());
assertNull(mergeResult.getConflicts());
// Check that the image was not modified (not conflict marker added)
String result = read(
writeTrashFile("res.cat", "A\n" + "E\n" + "C\n" + "F\n"));
assertEquals(result, read(git.getRepository().getWorkTree().toPath()
.resolve("main.cat").toFile()));
}
}
@Test
public void mergeTextualFile_UnsetMerge_Conflict()
throws NoWorkTreeException, NoFilepatternException, GitAPIException,
IOException {
try (Git git = createRepositoryBinaryConflict(g -> {
try {
writeTrashFile(".gitattributes", "*.cat -merge");
writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
} catch (IOException e) {
e.printStackTrace();
}
}, g -> {
try {
writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
} catch (IOException e) {
e.printStackTrace();
}
}, g -> {
try {
writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
} catch (IOException e) {
e.printStackTrace();
}
})) {
// Check that the merge attribute is unset
assertAddMergeAttributeUnset(REFS_HEADS_LEFT, "main.cat");
assertAddMergeAttributeUnset(REFS_HEADS_RIGHT, "main.cat");
checkoutBranch(REFS_HEADS_LEFT);
// Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
String catContent = read(git.getRepository().getWorkTree().toPath()
.resolve("main.cat").toFile());
MergeResult mergeResult = git.merge()
.include(git.getRepository().resolve(REFS_HEADS_RIGHT))
.call();
assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
// Check that the image was not modified (not conflict marker added)
assertEquals(catContent, read(git.getRepository().getWorkTree()
.toPath().resolve("main.cat").toFile()));
}
}
@Test
public void mergeTextualFile_UnsetMerge_NoConflict()
throws NoWorkTreeException, NoFilepatternException, GitAPIException,
IOException {
try (Git git = createRepositoryBinaryConflict(g -> {
try {
writeTrashFile(".gitattributes", "*.txt -merge");
writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
} catch (IOException e) {
e.printStackTrace();
}
}, g -> {
try {
writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
} catch (IOException e) {
e.printStackTrace();
}
}, g -> {
try {
writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
} catch (IOException e) {
e.printStackTrace();
}
})) {
// Check that the merge attribute is unset
assertAddMergeAttributeUndefined(REFS_HEADS_LEFT, "main.cat");
assertAddMergeAttributeUndefined(REFS_HEADS_RIGHT, "main.cat");
checkoutBranch(REFS_HEADS_LEFT);
// Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
MergeResult mergeResult = git.merge()
.include(git.getRepository().resolve(REFS_HEADS_RIGHT))
.call();
assertEquals(MergeStatus.MERGED, mergeResult.getMergeStatus());
// Check that the image was not modified (not conflict marker added)
String result = read(
writeTrashFile("res.cat", "A\n" + "E\n" + "C\n" + "F\n"));
assertEquals(result, read(git.getRepository().getWorkTree()
.toPath().resolve("main.cat").toFile()));
}
}
@Test
public void mergeTextualFile_SetBinaryMerge_Conflict()
throws NoWorkTreeException, NoFilepatternException, GitAPIException,
IOException {
try (Git git = createRepositoryBinaryConflict(g -> {
try {
writeTrashFile(".gitattributes", "*.cat merge=binary");
writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
} catch (IOException e) {
e.printStackTrace();
}
}, g -> {
try {
writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
} catch (IOException e) {
e.printStackTrace();
}
}, g -> {
try {
writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
} catch (IOException e) {
e.printStackTrace();
}
})) {
// Check that the merge attribute is set to binary
assertAddMergeAttributeCustom(REFS_HEADS_LEFT, "main.cat",
"binary");
assertAddMergeAttributeCustom(REFS_HEADS_RIGHT, "main.cat",
"binary");
checkoutBranch(REFS_HEADS_LEFT);
// Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
String catContent = read(git.getRepository().getWorkTree().toPath()
.resolve("main.cat").toFile());
MergeResult mergeResult = git.merge()
.include(git.getRepository().resolve(REFS_HEADS_RIGHT))
.call();
assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
// Check that the image was not modified (not conflict marker added)
assertEquals(catContent, read(git.getRepository().getWorkTree()
.toPath().resolve("main.cat").toFile()));
}
}
/*
* This test is commented because JGit add conflict markers in binary files.
* cf. https://www.eclipse.org/forums/index.php/t/1086511/
*/
@Test
@Ignore
public void mergeBinaryFile_NoAttr_Conflict() throws IllegalStateException,
IOException, NoHeadException, ConcurrentRefUpdateException,
CheckoutConflictException, InvalidMergeHeadsException,
WrongRepositoryStateException, NoMessageException, GitAPIException {
RevCommit disableCheckedCommit;
FileInputStream mergeResultFile = null;
// Set up a git with conflict commits on images
try (Git git = new Git(db)) {
// First commit
write(new File(db.getWorkTree(), ".gitattributes"), "");
git.add().addFilepattern(".gitattributes").call();
RevCommit firstCommit = git.commit()
.setMessage("initial commit adding git attribute file")
.call();
// Create branch and add an icon Checked_Boxe (enabled_checked)
createBranch(firstCommit, ENABLE_CHECKED_BRANCH);
checkoutBranch(ENABLE_CHECKED_BRANCH);
copy(ENABLED_CHECKED_GIF, ENABLED_CHECKED_GIF, "");
git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
git.commit().setMessage("enabled_checked commit").call();
// Create a second branch from master Unchecked_Boxe
checkoutBranch(REFS_HEADS_MASTER);
createBranch(firstCommit, DISABLE_CHECK_BRANCH);
checkoutBranch(DISABLE_CHECK_BRANCH);
copy("disabled_checked.gif", ENABLED_CHECKED_GIF, "");
git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
disableCheckedCommit = git.commit()
.setMessage("disabled_checked commit").call();
// Check that the merge attribute is unset
assertAddMergeAttributeUndefined(ENABLE_CHECKED_BRANCH,
ENABLED_CHECKED_GIF);
assertAddMergeAttributeUndefined(DISABLE_CHECK_BRANCH,
ENABLED_CHECKED_GIF);
checkoutBranch(ENABLE_CHECKED_BRANCH);
// Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
MergeResult mergeResult = git.merge().include(disableCheckedCommit)
.call();
assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
// Check that the image was not modified (no conflict marker added)
mergeResultFile = new FileInputStream(
db.getWorkTree().toPath().resolve(ENABLED_CHECKED_GIF)
.toFile());
assertTrue(contentEquals(
getClass().getResourceAsStream(ENABLED_CHECKED_GIF),
mergeResultFile));
} finally {
if (mergeResultFile != null) {
mergeResultFile.close();
}
}
}
@Test
public void mergeBinaryFile_UnsetMerge_Conflict()
throws IllegalStateException,
IOException, NoHeadException, ConcurrentRefUpdateException,
CheckoutConflictException, InvalidMergeHeadsException,
WrongRepositoryStateException, NoMessageException, GitAPIException {
RevCommit disableCheckedCommit;
FileInputStream mergeResultFile = null;
// Set up a git whith conflict commits on images
try (Git git = new Git(db)) {
// First commit
write(new File(db.getWorkTree(), ".gitattributes"), "*.gif -merge");
git.add().addFilepattern(".gitattributes").call();
RevCommit firstCommit = git.commit()
.setMessage("initial commit adding git attribute file")
.call();
// Create branch and add an icon Checked_Boxe (enabled_checked)
createBranch(firstCommit, ENABLE_CHECKED_BRANCH);
checkoutBranch(ENABLE_CHECKED_BRANCH);
copy(ENABLED_CHECKED_GIF, ENABLED_CHECKED_GIF, "");
git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
git.commit().setMessage("enabled_checked commit").call();
// Create a second branch from master Unchecked_Boxe
checkoutBranch(REFS_HEADS_MASTER);
createBranch(firstCommit, DISABLE_CHECK_BRANCH);
checkoutBranch(DISABLE_CHECK_BRANCH);
copy("disabled_checked.gif", ENABLED_CHECKED_GIF, "");
git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
disableCheckedCommit = git.commit()
.setMessage("disabled_checked commit").call();
// Check that the merge attribute is unset
assertAddMergeAttributeUnset(ENABLE_CHECKED_BRANCH,
ENABLED_CHECKED_GIF);
assertAddMergeAttributeUnset(DISABLE_CHECK_BRANCH,
ENABLED_CHECKED_GIF);
checkoutBranch(ENABLE_CHECKED_BRANCH);
// Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
MergeResult mergeResult = git.merge().include(disableCheckedCommit)
.call();
assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
// Check that the image was not modified (not conflict marker added)
mergeResultFile = new FileInputStream(db.getWorkTree().toPath()
.resolve(ENABLED_CHECKED_GIF).toFile());
assertTrue(contentEquals(
getClass().getResourceAsStream(ENABLED_CHECKED_GIF),
mergeResultFile));
} finally {
if (mergeResultFile != null) {
mergeResultFile.close();
}
}
}
@Test
public void mergeBinaryFile_SetMerge_Conflict()
throws IllegalStateException, IOException, NoHeadException,
ConcurrentRefUpdateException, CheckoutConflictException,
InvalidMergeHeadsException, WrongRepositoryStateException,
NoMessageException, GitAPIException {
RevCommit disableCheckedCommit;
FileInputStream mergeResultFile = null;
// Set up a git whith conflict commits on images
try (Git git = new Git(db)) {
// First commit
write(new File(db.getWorkTree(), ".gitattributes"), "*.gif merge");
git.add().addFilepattern(".gitattributes").call();
RevCommit firstCommit = git.commit()
.setMessage("initial commit adding git attribute file")
.call();
// Create branch and add an icon Checked_Boxe (enabled_checked)
createBranch(firstCommit, ENABLE_CHECKED_BRANCH);
checkoutBranch(ENABLE_CHECKED_BRANCH);
copy(ENABLED_CHECKED_GIF, ENABLED_CHECKED_GIF, "");
git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
git.commit().setMessage("enabled_checked commit").call();
// Create a second branch from master Unchecked_Boxe
checkoutBranch(REFS_HEADS_MASTER);
createBranch(firstCommit, DISABLE_CHECK_BRANCH);
checkoutBranch(DISABLE_CHECK_BRANCH);
copy("disabled_checked.gif", ENABLED_CHECKED_GIF, "");
git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
disableCheckedCommit = git.commit()
.setMessage("disabled_checked commit").call();
// Check that the merge attribute is set
assertAddMergeAttributeSet(ENABLE_CHECKED_BRANCH,
ENABLED_CHECKED_GIF);
assertAddMergeAttributeSet(DISABLE_CHECK_BRANCH,
ENABLED_CHECKED_GIF);
checkoutBranch(ENABLE_CHECKED_BRANCH);
// Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
MergeResult mergeResult = git.merge().include(disableCheckedCommit)
.call();
assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
// Check that the image was not modified (not conflict marker added)
mergeResultFile = new FileInputStream(db.getWorkTree().toPath()
.resolve(ENABLED_CHECKED_GIF).toFile());
assertFalse(contentEquals(
getClass().getResourceAsStream(ENABLED_CHECKED_GIF),
mergeResultFile));
} finally {
if (mergeResultFile != null) {
mergeResultFile.close();
}
}
}
/*
* Copied from org.apache.commons.io.IOUtils
*/
private boolean contentEquals(InputStream input1, InputStream input2)
throws IOException {
if (input1 == input2) {
return true;
}
if (!(input1 instanceof BufferedInputStream)) {
input1 = new BufferedInputStream(input1);
}
if (!(input2 instanceof BufferedInputStream)) {
input2 = new BufferedInputStream(input2);
}
int ch = input1.read();
while (-1 != ch) {
final int ch2 = input2.read();
if (ch != ch2) {
return false;
}
ch = input1.read();
}
final int ch2 = input2.read();
return ch2 == -1;
}
private void assertAddMergeAttributeUnset(String branch, String fileName)
throws IllegalStateException, IOException {
checkoutBranch(branch);
try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
treeWaklEnableChecked.addTree(new FileTreeIterator(db));
treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
assertTrue(treeWaklEnableChecked.next());
Attributes attributes = treeWaklEnableChecked.getAttributes();
Attribute mergeAttribute = attributes.get("merge");
assertNotNull(mergeAttribute);
assertEquals(Attribute.State.UNSET, mergeAttribute.getState());
}
}
private void assertAddMergeAttributeSet(String branch, String fileName)
throws IllegalStateException, IOException {
checkoutBranch(branch);
try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
treeWaklEnableChecked.addTree(new FileTreeIterator(db));
treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
assertTrue(treeWaklEnableChecked.next());
Attributes attributes = treeWaklEnableChecked.getAttributes();
Attribute mergeAttribute = attributes.get("merge");
assertNotNull(mergeAttribute);
assertEquals(Attribute.State.SET, mergeAttribute.getState());
}
}
private void assertAddMergeAttributeUndefined(String branch,
String fileName) throws IllegalStateException, IOException {
checkoutBranch(branch);
try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
treeWaklEnableChecked.addTree(new FileTreeIterator(db));
treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
assertTrue(treeWaklEnableChecked.next());
Attributes attributes = treeWaklEnableChecked.getAttributes();
Attribute mergeAttribute = attributes.get("merge");
assertNull(mergeAttribute);
}
}
private void assertAddMergeAttributeCustom(String branch, String fileName,
String value) throws IllegalStateException, IOException {
checkoutBranch(branch);
try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
treeWaklEnableChecked.addTree(new FileTreeIterator(db));
treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
assertTrue(treeWaklEnableChecked.next());
Attributes attributes = treeWaklEnableChecked.getAttributes();
Attribute mergeAttribute = attributes.get("merge");
assertNotNull(mergeAttribute);
assertEquals(Attribute.State.CUSTOM, mergeAttribute.getState());
assertEquals(value, mergeAttribute.getValue());
}
}
private void copy(String resourcePath, String resourceNewName,
String pathInRepo) throws IOException {
InputStream input = getClass().getResourceAsStream(resourcePath);
Files.copy(input, db.getWorkTree().toPath().resolve(pathInRepo)
.resolve(resourceNewName));
}
}

8
org.eclipse.jgit/.settings/.api_filters

@ -24,4 +24,12 @@
</message_arguments> </message_arguments>
</filter> </filter>
</resource> </resource>
<resource path="src/org/eclipse/jgit/merge/ResolveMerger.java" type="org.eclipse.jgit.merge.ResolveMerger">
<filter comment="OSGi semantic versioning allows breaking implementors of an API in a minor version" id="338792546">
<message_arguments>
<message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
<message_argument value="processEntry(CanonicalTreeParser, CanonicalTreeParser, CanonicalTreeParser, DirCacheBuildIterator, WorkingTreeIterator, boolean)"/>
</message_arguments>
</filter>
</resource>
</component> </component>

24
org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java

@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com> * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>,
* Copyright (C) 2017, Obeo (mathieu.cartaud@obeo.fr)
* *
* This program and the accompanying materials are made available * This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which * under the terms of the Eclipse Distribution License v1.0 which
@ -48,6 +49,7 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import org.eclipse.jgit.attributes.Attribute.State; import org.eclipse.jgit.attributes.Attribute.State;
import org.eclipse.jgit.lib.Constants;
/** /**
* Represents a set of attributes for a path * Represents a set of attributes for a path
@ -170,6 +172,26 @@ public final class Attributes {
return a != null ? a.getValue() : null; return a != null ? a.getValue() : null;
} }
/**
* Test if the given attributes implies to handle the related entry as a
* binary file (i.e. if the entry has an -merge or a merge=binary attribute)
* or if it can be content merged.
*
* @return <code>true</code> if the entry can be content merged,
* <code>false</code> otherwise
* @since 4.9
*/
public boolean canBeContentMerged() {
if (isUnset(Constants.ATTR_MERGE)) {
return false;
} else if (isCustom(Constants.ATTR_MERGE)
&& getValue(Constants.ATTR_MERGE)
.equals(Constants.ATTR_BUILTIN_BINARY_MERGER)) {
return false;
}
return true;
}
@Override @Override
public String toString() { public String toString() {
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();

16
org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java

@ -1,7 +1,7 @@
/* /*
* Copyright (C) 2008, Google Inc. * Copyright (C) 2008, Google Inc.
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2006-2012, Shawn O. Pearce <spearce@spearce.org> * Copyright (C) 2006-2017, Shawn O. Pearce <spearce@spearce.org>
* and other copyright owners as documented in the project's IP log. * and other copyright owners as documented in the project's IP log.
* *
* This program and the accompanying materials are made available * This program and the accompanying materials are made available
@ -428,6 +428,20 @@ public final class Constants {
*/ */
public static final String HOOKS = "hooks"; public static final String HOOKS = "hooks";
/**
* Merge attribute.
*
* @since 4.9
*/
public static final String ATTR_MERGE = "merge"; //$NON-NLS-1$
/**
* Binary value for custom merger.
*
* @since 4.9
*/
public static final String ATTR_BUILTIN_BINARY_MERGER = "binary"; //$NON-NLS-1$
/** /**
* Create a new digest function for objects. * Create a new digest function for objects.
* *

28
org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java

@ -2,6 +2,7 @@
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>, * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>,
* Copyright (C) 2010-2012, Matthias Sohn <matthias.sohn@sap.com> * Copyright (C) 2010-2012, Matthias Sohn <matthias.sohn@sap.com>
* Copyright (C) 2012, Research In Motion Limited * Copyright (C) 2012, Research In Motion Limited
* Copyright (C) 2017, Obeo (mathieu.cartaud@obeo.fr)
* and other copyright owners as documented in the project's IP log. * and other copyright owners as documented in the project's IP log.
* *
* This program and the accompanying materials are made available * This program and the accompanying materials are made available
@ -67,6 +68,7 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.eclipse.jgit.attributes.Attributes;
import org.eclipse.jgit.diff.DiffAlgorithm; import org.eclipse.jgit.diff.DiffAlgorithm;
import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm; import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm;
import org.eclipse.jgit.diff.RawText; import org.eclipse.jgit.diff.RawText;
@ -429,9 +431,10 @@ public class ResolveMerger extends ThreeWayMerger {
} }
/** /**
* Processes one path and tries to merge. This method will do all do all * Processes one path and tries to merge taking git attributes in account.
* trivial (not content) merges and will also detect if a merge will fail. * This method will do all trivial (not content) merges and will also detect
* The merge will fail when one of the following is true * if a merge will fail. The merge will fail when one of the following is
* true
* <ul> * <ul>
* <li>the index entry does not match the entry in ours. When merging one * <li>the index entry does not match the entry in ours. When merging one
* branch into the current HEAD, ours will point to HEAD and theirs will * branch into the current HEAD, ours will point to HEAD and theirs will
@ -463,6 +466,8 @@ public class ResolveMerger extends ThreeWayMerger {
* @param ignoreConflicts * @param ignoreConflicts
* see * see
* {@link ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, RevTree, boolean)} * {@link ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, RevTree, boolean)}
* @param attributes
* the attributes defined for this entry
* @return <code>false</code> if the merge will fail because the index entry * @return <code>false</code> if the merge will fail because the index entry
* didn't match ours or the working-dir file was dirty and a * didn't match ours or the working-dir file was dirty and a
* conflict occurred * conflict occurred
@ -470,12 +475,12 @@ public class ResolveMerger extends ThreeWayMerger {
* @throws IncorrectObjectTypeException * @throws IncorrectObjectTypeException
* @throws CorruptObjectException * @throws CorruptObjectException
* @throws IOException * @throws IOException
* @since 3.5 * @since 4.9
*/ */
protected boolean processEntry(CanonicalTreeParser base, protected boolean processEntry(CanonicalTreeParser base,
CanonicalTreeParser ours, CanonicalTreeParser theirs, CanonicalTreeParser ours, CanonicalTreeParser theirs,
DirCacheBuildIterator index, WorkingTreeIterator work, DirCacheBuildIterator index, WorkingTreeIterator work,
boolean ignoreConflicts) boolean ignoreConflicts, Attributes attributes)
throws MissingObjectException, IncorrectObjectTypeException, throws MissingObjectException, IncorrectObjectTypeException,
CorruptObjectException, IOException { CorruptObjectException, IOException {
enterSubtree = true; enterSubtree = true;
@ -627,7 +632,8 @@ public class ResolveMerger extends ThreeWayMerger {
return false; return false;
// Don't attempt to resolve submodule link conflicts // Don't attempt to resolve submodule link conflicts
if (isGitLink(modeO) || isGitLink(modeT)) { if (isGitLink(modeO) || isGitLink(modeT)
|| !attributes.canBeContentMerged()) {
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0); add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0);
@ -636,8 +642,9 @@ public class ResolveMerger extends ThreeWayMerger {
} }
MergeResult<RawText> result = contentMerge(base, ours, theirs); MergeResult<RawText> result = contentMerge(base, ours, theirs);
if (ignoreConflicts) if (ignoreConflicts) {
result.setContainsConflicts(false); result.setContainsConflicts(false);
}
updateIndex(base, ours, theirs, result); updateIndex(base, ours, theirs, result);
if (result.containsConflicts() && !ignoreConflicts) if (result.containsConflicts() && !ignoreConflicts)
unmergedPaths.add(tw.getPathString()); unmergedPaths.add(tw.getPathString());
@ -760,6 +767,7 @@ public class ResolveMerger extends ThreeWayMerger {
MergeResult<RawText> result) throws FileNotFoundException, MergeResult<RawText> result) throws FileNotFoundException,
IOException { IOException {
File mergedFile = !inCore ? writeMergedFile(result) : null; File mergedFile = !inCore ? writeMergedFile(result) : null;
if (result.containsConflicts()) { if (result.containsConflicts()) {
// A conflict occurred, the file will contain conflict markers // A conflict occurred, the file will contain conflict markers
// the index will be populated with the three stages and the // the index will be populated with the three stages and the
@ -1091,6 +1099,8 @@ public class ResolveMerger extends ThreeWayMerger {
protected boolean mergeTreeWalk(TreeWalk treeWalk, boolean ignoreConflicts) protected boolean mergeTreeWalk(TreeWalk treeWalk, boolean ignoreConflicts)
throws IOException { throws IOException {
boolean hasWorkingTreeIterator = tw.getTreeCount() > T_FILE; boolean hasWorkingTreeIterator = tw.getTreeCount() > T_FILE;
boolean hasAttributeNodeProvider = treeWalk
.getAttributesNodeProvider() != null;
while (treeWalk.next()) { while (treeWalk.next()) {
if (!processEntry( if (!processEntry(
treeWalk.getTree(T_BASE, CanonicalTreeParser.class), treeWalk.getTree(T_BASE, CanonicalTreeParser.class),
@ -1098,7 +1108,9 @@ public class ResolveMerger extends ThreeWayMerger {
treeWalk.getTree(T_THEIRS, CanonicalTreeParser.class), treeWalk.getTree(T_THEIRS, CanonicalTreeParser.class),
treeWalk.getTree(T_INDEX, DirCacheBuildIterator.class), treeWalk.getTree(T_INDEX, DirCacheBuildIterator.class),
hasWorkingTreeIterator ? treeWalk.getTree(T_FILE, hasWorkingTreeIterator ? treeWalk.getTree(T_FILE,
WorkingTreeIterator.class) : null, ignoreConflicts)) { WorkingTreeIterator.class) : null,
ignoreConflicts, hasAttributeNodeProvider
? treeWalk.getAttributes() : new Attributes())) {
cleanUp(); cleanUp();
return false; return false;
} }

Loading…
Cancel
Save