Browse Source

Do not add a newline at the end if neither merged side had one

Bug: 390833
Change-Id: I29f7b79b241929877c93ac485c677487a91bb77b
Signed-off-by: André de Oliveira <andre.oliveira@liferay.com>
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
stable-4.1
André de Oliveira 10 years ago committed by Matthias Sohn
parent
commit
3cd7d0d85a
  1. 60
      org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java
  2. 6
      org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java
  3. 87
      org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java
  4. 43
      org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
  5. 146
      org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java

60
org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java

@ -51,11 +51,25 @@ import java.io.IOException;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.lib.Constants;
import org.junit.Assume;
import org.junit.Test;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.runner.RunWith;
@RunWith(Theories.class)
public class MergeAlgorithmTest {
MergeFormatter fmt=new MergeFormatter();
private final boolean newlineAtEnd;
@DataPoints
public static boolean[] newlineAtEndDataPoints = { false, true };
public MergeAlgorithmTest(boolean newlineAtEnd) {
this.newlineAtEnd = newlineAtEnd;
}
/**
* Check for a conflict where the second text was changed similar to the
* first one, but the second texts modification covers one more line.
@ -174,28 +188,55 @@ public class MergeAlgorithmTest {
}
@Test
public void testSeperateModifications() throws IOException {
public void testSeparateModifications() throws IOException {
assertEquals(t("aZcYe"), merge("abcde", "aZcde", "abcYe"));
}
@Test
public void testBlankLines() throws IOException {
assertEquals(t("aZc\nYe"), merge("abc\nde", "aZc\nde", "abc\nYe"));
}
/**
* Test merging two contents which do one similar modification and one
* insertion is only done by one side. Between modification and insertion is
* a block which is common between the two contents and the common base
* insertion is only done by one side, in the middle. Between modification
* and insertion is a block which is common between the two contents and the
* common base
*
* @throws IOException
*/
@Test
public void testTwoSimilarModsAndOneInsert() throws IOException {
assertEquals(t("IAAJ"), merge("iA", "IA", "IAAJ"));
assertEquals(t("aBcDde"), merge("abcde", "aBcde", "aBcDde"));
assertEquals(t("IAJ"), merge("iA", "IA", "IAJ"));
assertEquals(t("IAAAJ"), merge("iA", "IA", "IAAAJ"));
assertEquals(t("IAAAJCAB"), merge("iACAB", "IACAB", "IAAAJCAB"));
assertEquals(t("HIAAAJCAB"), merge("HiACAB", "HIACAB", "HIAAAJCAB"));
assertEquals(t("AGADEFHIAAAJCAB"),
merge("AGADEFHiACAB", "AGADEFHIACAB", "AGADEFHIAAAJCAB"));
}
/**
* Test merging two contents which do one similar modification and one
* insertion is only done by one side, at the end. Between modification and
* insertion is a block which is common between the two contents and the
* common base
*
* @throws IOException
*/
@Test
public void testTwoSimilarModsAndOneInsertAtEnd() throws IOException {
Assume.assumeTrue(newlineAtEnd);
assertEquals(t("IAAJ"), merge("iA", "IA", "IAAJ"));
assertEquals(t("IAJ"), merge("iA", "IA", "IAJ"));
assertEquals(t("IAAAJ"), merge("iA", "IA", "IAAAJ"));
}
@Test
public void testTwoSimilarModsAndOneInsertAtEndNoNewlineAtEnd()
throws IOException {
Assume.assumeFalse(newlineAtEnd);
assertEquals(t("I<A=AAJ>"), merge("iA", "IA", "IAAJ"));
assertEquals(t("I<A=AJ>"), merge("iA", "IA", "IAJ"));
assertEquals(t("I<A=AAAJ>"), merge("iA", "IA", "IAAAJ"));
}
/**
@ -225,7 +266,7 @@ public class MergeAlgorithmTest {
return new String(bo.toByteArray(), Constants.CHARACTER_ENCODING);
}
public static String t(String text) {
public String t(String text) {
StringBuilder r = new StringBuilder();
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
@ -241,13 +282,14 @@ public class MergeAlgorithmTest {
break;
default:
r.append(c);
r.append('\n');
if (newlineAtEnd || i < text.length() - 1)
r.append('\n');
}
}
return r.toString();
}
public static RawText T(String text) {
public RawText T(String text) {
return new RawText(Constants.encode(t(text)));
}
}

6
org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java

@ -184,7 +184,7 @@ public class ResolveMergerTest extends RepositoryTestCase {
MergeResult mergeRes = git.merge().setStrategy(strategy)
.include(masterCommit).call();
assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
assertEquals("[d/1, mode:100644, content:1master\n2\n3side\n]",
assertEquals("[d/1, mode:100644, content:1master\n2\n3side]",
indexState(CONTENT));
}
@ -561,7 +561,7 @@ public class ResolveMergerTest extends RepositoryTestCase {
assertEquals(MergeStrategy.RECURSIVE, strategy);
assertEquals(MergeResult.MergeStatus.MERGED,
mergeResult.getMergeStatus());
assertEquals("1master2\n2\n3side2\n", read("1"));
assertEquals("1master2\n2\n3side2", read("1"));
} catch (JGitInternalException e) {
assertEquals(MergeStrategy.RESOLVE, strategy);
assertTrue(e.getCause() instanceof NoMergeBaseException);
@ -697,7 +697,7 @@ public class ResolveMergerTest extends RepositoryTestCase {
assertEquals(
"[0, mode:100644, content:master]" //
+ "[1, mode:100644, content:side]" //
+ "[2, mode:100644, content:1master\n2\n3side\n]" //
+ "[2, mode:100644, content:1master\n2\n3side]" //
+ "[3, mode:100644, stage:1, content:orig][3, mode:100644, stage:2, content:side][3, mode:100644, stage:3, content:master]" //
+ "[4, mode:100644, content:orig]", //
indexState(CONTENT));

87
org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java

@ -0,0 +1,87 @@
/*
* Copyright (C) 2014, André de Oliveira <andre.oliveira@liferay.com>
*
* 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.merge;
import java.io.IOException;
import java.io.OutputStream;
/**
* An output stream which is aware of newlines and can be asked to begin a new
* line if not already in one.
*/
class EolAwareOutputStream extends OutputStream {
private final OutputStream out;
private boolean bol = true;
/**
* Initialize a new EOL aware stream.
*
* @param out
* stream to output all writes to.
*/
EolAwareOutputStream(OutputStream out) {
this.out = out;
}
/**
* Begin a new line if not already in one.
*
* @exception IOException
* if an I/O error occurs.
*/
void beginln() throws IOException {
if (!bol)
write('\n');
}
/** @return true if a new line has just begun. */
boolean isBeginln() {
return bol;
}
@Override
public void write(int val) throws IOException {
out.write(val);
bol = (val == '\n');
}
}

43
org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java

@ -49,7 +49,6 @@ import java.util.ArrayList;
import java.util.List;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.merge.MergeChunk.ConflictState;
/**
* A class to convert merge results into a Git conformant textual presentation
@ -78,47 +77,7 @@ public class MergeFormatter {
*/
public void formatMerge(OutputStream out, MergeResult<RawText> res,
List<String> seqName, String charsetName) throws IOException {
String lastConflictingName = null; // is set to non-null whenever we are
// in a conflict
boolean threeWayMerge = (res.getSequences().size() == 3);
for (MergeChunk chunk : res) {
RawText seq = res.getSequences().get(chunk.getSequenceIndex());
if (lastConflictingName != null
&& chunk.getConflictState() != ConflictState.NEXT_CONFLICTING_RANGE) {
// found the end of an conflict
out.write((">>>>>>> " + lastConflictingName + "\n").getBytes(charsetName)); //$NON-NLS-1$ //$NON-NLS-2$
lastConflictingName = null;
}
if (chunk.getConflictState() == ConflictState.FIRST_CONFLICTING_RANGE) {
// found the start of an conflict
out.write(("<<<<<<< " + seqName.get(chunk.getSequenceIndex()) + //$NON-NLS-1$
"\n").getBytes(charsetName)); //$NON-NLS-1$
lastConflictingName = seqName.get(chunk.getSequenceIndex());
} else if (chunk.getConflictState() == ConflictState.NEXT_CONFLICTING_RANGE) {
// found another conflicting chunk
/*
* In case of a non-three-way merge I'll add the name of the
* conflicting chunk behind the equal signs. I also append the
* name of the last conflicting chunk after the ending
* greater-than signs. If somebody knows a better notation to
* present non-three-way merges - feel free to correct here.
*/
lastConflictingName = seqName.get(chunk.getSequenceIndex());
out.write((threeWayMerge ? "=======\n" : "======= " //$NON-NLS-1$ //$NON-NLS-2$
+ lastConflictingName + "\n").getBytes(charsetName)); //$NON-NLS-1$
}
// the lines with conflict-metadata are written. Now write the chunk
for (int i = chunk.getBegin(); i < chunk.getEnd(); i++) {
seq.writeLine(out, i);
out.write('\n');
}
}
// one possible leftover: if the merge result ended with a conflict we
// have to close the last conflict here
if (lastConflictingName != null) {
out.write((">>>>>>> " + lastConflictingName + "\n").getBytes(charsetName)); //$NON-NLS-1$ //$NON-NLS-2$
}
new MergeFormatterPass(out, res, seqName, charsetName).formatMerge();
}
/**

146
org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java

@ -0,0 +1,146 @@
/*
* Copyright (C) 2009, Christian Halstrick <christian.halstrick@sap.com>
* Copyright (C) 2014, André de Oliveira <andre.oliveira@liferay.com>
* 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.merge;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.merge.MergeChunk.ConflictState;
class MergeFormatterPass {
private final EolAwareOutputStream out;
private final MergeResult<RawText> res;
private final List<String> seqName;
private final String charsetName;
private final boolean threeWayMerge;
private String lastConflictingName; // is set to non-null whenever we are in
// a conflict
MergeFormatterPass(OutputStream out, MergeResult<RawText> res, List<String> seqName,
String charsetName) {
this.out = new EolAwareOutputStream(out);
this.res = res;
this.seqName = seqName;
this.charsetName = charsetName;
this.threeWayMerge = (res.getSequences().size() == 3);
}
void formatMerge() throws IOException {
boolean missingNewlineAtEnd = false;
for (MergeChunk chunk : res) {
RawText seq = res.getSequences().get(chunk.getSequenceIndex());
writeConflictMetadata(chunk);
// the lines with conflict-metadata are written. Now write the chunk
for (int i = chunk.getBegin(); i < chunk.getEnd(); i++)
writeLine(seq, i);
missingNewlineAtEnd = seq.isMissingNewlineAtEnd();
}
// one possible leftover: if the merge result ended with a conflict we
// have to close the last conflict here
if (lastConflictingName != null)
writeConflictEnd();
if (!missingNewlineAtEnd)
out.beginln();
}
private void writeConflictMetadata(MergeChunk chunk) throws IOException {
if (lastConflictingName != null
&& chunk.getConflictState() != ConflictState.NEXT_CONFLICTING_RANGE) {
// found the end of an conflict
writeConflictEnd();
}
if (chunk.getConflictState() == ConflictState.FIRST_CONFLICTING_RANGE) {
// found the start of an conflict
writeConflictStart(chunk);
} else if (chunk.getConflictState() == ConflictState.NEXT_CONFLICTING_RANGE) {
// found another conflicting chunk
writeConflictChange(chunk);
}
}
private void writeConflictEnd() throws IOException {
writeln(">>>>>>> " + lastConflictingName); //$NON-NLS-1$
lastConflictingName = null;
}
private void writeConflictStart(MergeChunk chunk) throws IOException {
lastConflictingName = seqName.get(chunk.getSequenceIndex());
writeln("<<<<<<< " + lastConflictingName); //$NON-NLS-1$
}
private void writeConflictChange(MergeChunk chunk) throws IOException {
/*
* In case of a non-three-way merge I'll add the name of the conflicting
* chunk behind the equal signs. I also append the name of the last
* conflicting chunk after the ending greater-than signs. If somebody
* knows a better notation to present non-three-way merges - feel free
* to correct here.
*/
lastConflictingName = seqName.get(chunk.getSequenceIndex());
writeln(threeWayMerge ? "=======" : "======= " //$NON-NLS-1$ //$NON-NLS-2$
+ lastConflictingName);
}
private void writeln(String s) throws IOException {
out.beginln();
out.write((s + "\n").getBytes(charsetName)); //$NON-NLS-1$
}
private void writeLine(RawText seq, int i) throws IOException {
out.beginln();
seq.writeLine(out, i);
// still BOL? It was a blank line. But writeLine won't lf, so we do.
if (out.isBeginln())
out.write('\n');
}
}
Loading…
Cancel
Save