]> source.dussan.org Git - jgit.git/commitdiff
Do not add a newline at the end if neither merged side had one 19/33219/6
authorAndré de Oliveira <andre.oliveira@liferay.com>
Wed, 10 Sep 2014 19:02:05 +0000 (16:02 -0300)
committerMatthias Sohn <matthias.sohn@sap.com>
Sat, 28 Mar 2015 00:42:49 +0000 (20:42 -0400)
Bug: 390833
Change-Id: I29f7b79b241929877c93ac485c677487a91bb77b
Signed-off-by: André de Oliveira <andre.oliveira@liferay.com>
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java
org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java [new file with mode: 0644]

index 8a33425b1dd09cc095ae77bba153ae5d33241145..d4a3d62dad2660199cabdd74deeb99c5294e73f8 100644 (file)
@@ -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)));
        }
 }
index dd06168c304136aaa554fa1c10a56279ecb9ace0..478a93b770196e6ffd8f6fb5ea62d063f46676ec 100644 (file)
@@ -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));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java
new file mode 100644 (file)
index 0000000..3fd2374
--- /dev/null
@@ -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');
+       }
+}
index eed1dcfa8887a4092304a397108249936a4b7393..977f95341fd3aff2e4cd50585c12879a64df00e4 100644 (file)
@@ -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();
        }
 
        /**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java
new file mode 100644 (file)
index 0000000..0345921
--- /dev/null
@@ -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');
+       }
+}
\ No newline at end of file