commit 22e6e6b8347b35ed0408417cd2d8e0fddd77b76d Author: Alice Kober-Sotzek Date: Thu Sep 24 11:05:05 2020 +0200 Extract and test code for magic file content Previously, the code was hidden in the fairly unrelated Text class. By extracting it, these magic files get a place for their own. In addition, we can unit test the content which is crucial as the magic file content must stay stable over time (due to the existing comments on them). Until now, there were some integration test in the context of diffs, which indirectly verified that the generated file content for the /COMMIT_MSG and /MERGE_LIST adhered to the desired format. With this change, we now have test coverage for all situations of the /COMMIT_MSG and /MERGE_LIST file we identified as being relevant. We also added a warning to the tests that the format must stay stable and hence that future developers don't simply adjust the tests when they adjust the implementation in a breaking way. The main motivation for this change is the follow-up change (I9658c0768), though. For I9658c0768, we need to know which part of the /COMMIT_MSG file is occupied by the header and which part represents the actual commit message. We still keep the factory methods in Text as we don't want to break callers with this change. Cleaning up those references could be done in future changes. Change-Id: I5f8b93673b11e2674ef5d46e4a1d89660af6ac9a diff --git a/java/com/google/gerrit/server/patch/MagicFile.java b/java/com/google/gerrit/server/patch/MagicFile.java new file mode 100644 index 0000000..aa6b11f --- /dev/null +++ b/java/com/google/gerrit/server/patch/MagicFile.java @@ -0,0 +1,189 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.patch; + +import com.google.auto.value.AutoValue; +import com.google.common.base.CharMatcher; +import com.google.gerrit.git.ObjectIds; +import java.io.IOException; +import java.text.SimpleDateFormat; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; + +/** Representation of a magic file which appears as a file with content to Gerrit users. */ +@AutoValue +public abstract class MagicFile { + + public static MagicFile forCommitMessage(ObjectReader reader, AnyObjectId commitId) + throws IOException { + try (RevWalk rw = new RevWalk(reader)) { + RevCommit c; + if (commitId instanceof RevCommit) { + c = (RevCommit) commitId; + } else { + c = rw.parseCommit(commitId); + } + + String header = createCommitMessageHeader(reader, rw, c); + String message = c.getFullMessage(); + return MagicFile.builder().generatedContent(header).modifiableContent(message).build(); + } + } + + private static String createCommitMessageHeader(ObjectReader reader, RevWalk rw, RevCommit c) + throws IOException { + StringBuilder b = new StringBuilder(); + switch (c.getParentCount()) { + case 0: + break; + case 1: + { + RevCommit p = c.getParent(0); + rw.parseBody(p); + b.append("Parent: "); + b.append(abbreviateName(p, reader)); + b.append(" ("); + b.append(p.getShortMessage()); + b.append(")\n"); + break; + } + default: + for (int i = 0; i < c.getParentCount(); i++) { + RevCommit p = c.getParent(i); + rw.parseBody(p); + b.append(i == 0 ? "Merge Of: " : " "); + b.append(abbreviateName(p, reader)); + b.append(" ("); + b.append(p.getShortMessage()); + b.append(")\n"); + } + } + appendPersonIdent(b, "Author", c.getAuthorIdent()); + appendPersonIdent(b, "Commit", c.getCommitterIdent()); + b.append("\n"); + return b.toString(); + } + + public static MagicFile forMergeList( + ComparisonType comparisonType, ObjectReader reader, AnyObjectId commitId) throws IOException { + try (RevWalk rw = new RevWalk(reader)) { + RevCommit c = rw.parseCommit(commitId); + StringBuilder b = new StringBuilder(); + switch (c.getParentCount()) { + case 0: + break; + case 1: + { + break; + } + default: + int uninterestingParent = + comparisonType.isAgainstParent() ? comparisonType.getParentNum() : 1; + + b.append("Merge List:\n\n"); + for (RevCommit commit : MergeListBuilder.build(rw, c, uninterestingParent)) { + b.append("* "); + b.append(abbreviateName(commit, reader)); + b.append(" "); + b.append(commit.getShortMessage()); + b.append("\n"); + } + } + return MagicFile.builder().generatedContent(b.toString()).build(); + } + } + + private static String abbreviateName(RevCommit p, ObjectReader reader) throws IOException { + return ObjectIds.abbreviateName(p, 8, reader); + } + + private static void appendPersonIdent(StringBuilder b, String field, PersonIdent person) { + if (person != null) { + b.append(field).append(": "); + if (person.getName() != null) { + b.append(" "); + b.append(person.getName()); + } + if (person.getEmailAddress() != null) { + b.append(" <"); + b.append(person.getEmailAddress()); + b.append(">"); + } + b.append("\n"); + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss ZZZ"); + sdf.setTimeZone(person.getTimeZone()); + b.append(field).append("Date: "); + b.append(sdf.format(person.getWhen())); + b.append("\n"); + } + } + + /** Generated part of the file. Any generated contents should go here. Can be empty. */ + public abstract String generatedContent(); + + /** + * Non-generated part of the file. This should correspond to some actual content derived from + * somewhere else which can also be modified (e.g. by suggested fixes). Can be empty. + */ + public abstract String modifiableContent(); + + /** Whole content of the file as it appears to users. */ + public String getFileContent() { + return generatedContent() + modifiableContent(); + } + + /** Returns the start line of the modifiable content. Assumes that line counting starts at 1. */ + public int getStartLineOfModifiableContent() { + int numHeaderLines = CharMatcher.is('\n').countIn(generatedContent()); + // Lines start at 1 and not 0. -> Add 1. + return 1 + numHeaderLines; + } + + static Builder builder() { + return new AutoValue_MagicFile.Builder().generatedContent("").modifiableContent(""); + } + + @AutoValue.Builder + abstract static class Builder { + + /** See {@link #generatedContent()}. Use an empty string to denote no such content. */ + public abstract Builder generatedContent(String content); + + /** See {@link #modifiableContent()}. Use an empty string to denote no such content. */ + public abstract Builder modifiableContent(String content); + + abstract String generatedContent(); + + abstract String modifiableContent(); + + abstract MagicFile autoBuild(); + + public MagicFile build() { + // Normalize each content part to end with a newline character, which simplifies further + // handling. + if (!generatedContent().isEmpty() && !generatedContent().endsWith("\n")) { + generatedContent(generatedContent() + "\n"); + } + if (!modifiableContent().isEmpty() && !modifiableContent().endsWith("\n")) { + modifiableContent(modifiableContent() + "\n"); + } + return autoBuild(); + } + } +} diff --git a/java/com/google/gerrit/server/patch/Text.java b/java/com/google/gerrit/server/patch/Text.java index cc0a5e4..0f69965 100644 --- a/java/com/google/gerrit/server/patch/Text.java +++ b/java/com/google/gerrit/server/patch/Text.java @@ -18,21 +18,16 @@ import static java.nio.charset.StandardCharsets.ISO_8859_1; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.flogger.FluentLogger; -import com.google.gerrit.git.ObjectIds; import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.UnsupportedCharsetException; -import java.text.SimpleDateFormat; import org.eclipse.jgit.diff.RawText; import org.eclipse.jgit.errors.LargeObjectException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; -import org.eclipse.jgit.lib.PersonIdent; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.pack.PackConfig; import org.eclipse.jgit.util.RawParseUtils; import org.mozilla.universalchardet.UniversalDetector; @@ -46,101 +41,14 @@ public class Text extends RawText { public static final Text EMPTY = new Text(NO_BYTES); public static Text forCommit(ObjectReader reader, AnyObjectId commitId) throws IOException { - try (RevWalk rw = new RevWalk(reader)) { - RevCommit c; - if (commitId instanceof RevCommit) { - c = (RevCommit) commitId; - } else { - c = rw.parseCommit(commitId); - } - - StringBuilder b = new StringBuilder(); - switch (c.getParentCount()) { - case 0: - break; - case 1: - { - RevCommit p = c.getParent(0); - rw.parseBody(p); - b.append("Parent: "); - b.append(abbreviateName(p, reader)); - b.append(" ("); - b.append(p.getShortMessage()); - b.append(")\n"); - break; - } - default: - for (int i = 0; i < c.getParentCount(); i++) { - RevCommit p = c.getParent(i); - rw.parseBody(p); - b.append(i == 0 ? "Merge Of: " : " "); - b.append(abbreviateName(p, reader)); - b.append(" ("); - b.append(p.getShortMessage()); - b.append(")\n"); - } - } - appendPersonIdent(b, "Author", c.getAuthorIdent()); - appendPersonIdent(b, "Commit", c.getCommitterIdent()); - b.append("\n"); - b.append(c.getFullMessage()); - return new Text(b.toString().getBytes(UTF_8)); - } + MagicFile commitMessageFile = MagicFile.forCommitMessage(reader, commitId); + return new Text(commitMessageFile.getFileContent().getBytes(UTF_8)); } public static Text forMergeList( ComparisonType comparisonType, ObjectReader reader, AnyObjectId commitId) throws IOException { - try (RevWalk rw = new RevWalk(reader)) { - RevCommit c = rw.parseCommit(commitId); - StringBuilder b = new StringBuilder(); - switch (c.getParentCount()) { - case 0: - break; - case 1: - { - break; - } - default: - int uniterestingParent = - comparisonType.isAgainstParent() ? comparisonType.getParentNum() : 1; - - b.append("Merge List:\n\n"); - for (RevCommit commit : MergeListBuilder.build(rw, c, uniterestingParent)) { - b.append("* "); - b.append(abbreviateName(commit, reader)); - b.append(" "); - b.append(commit.getShortMessage()); - b.append("\n"); - } - } - return new Text(b.toString().getBytes(UTF_8)); - } - } - - private static String abbreviateName(RevCommit p, ObjectReader reader) throws IOException { - return ObjectIds.abbreviateName(p, 8, reader); - } - - private static void appendPersonIdent(StringBuilder b, String field, PersonIdent person) { - if (person != null) { - b.append(field).append(": "); - if (person.getName() != null) { - b.append(" "); - b.append(person.getName()); - } - if (person.getEmailAddress() != null) { - b.append(" <"); - b.append(person.getEmailAddress()); - b.append(">"); - } - b.append("\n"); - - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss ZZZ"); - sdf.setTimeZone(person.getTimeZone()); - b.append(field).append("Date: "); - b.append(sdf.format(person.getWhen())); - b.append("\n"); - } + MagicFile mergeListFile = MagicFile.forMergeList(comparisonType, reader, commitId); + return new Text(mergeListFile.getFileContent().getBytes(UTF_8)); } public static byte[] asByteArray(ObjectLoader ldr) diff --git a/javatests/com/google/gerrit/server/patch/MagicFileTest.java b/javatests/com/google/gerrit/server/patch/MagicFileTest.java new file mode 100644 index 0000000..93928f0 --- /dev/null +++ b/javatests/com/google/gerrit/server/patch/MagicFileTest.java @@ -0,0 +1,395 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.patch; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.gerrit.entities.Project; +import com.google.gerrit.server.git.GitRepositoryManager; +import com.google.gerrit.testing.InMemoryRepositoryManager; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.Month; +import java.time.ZoneOffset; +import java.util.Date; +import java.util.TimeZone; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.Test; + +public class MagicFileTest { + + private final GitRepositoryManager repositoryManager = new InMemoryRepositoryManager(); + + @Test + public void magicFileContentIsBuiltCorrectly() { + MagicFile magicFile = + MagicFile.builder() + .generatedContent("Generated 1\n") + .modifiableContent("Modifiable 1\n") + .build(); + + assertThat(magicFile.getFileContent()).isEqualTo("Generated 1\nModifiable 1\n"); + } + + @Test + public void generatedContentMayBeEmpty() { + MagicFile magicFile = MagicFile.builder().modifiableContent("Modifiable 1\n").build(); + + assertThat(magicFile.getFileContent()).isEqualTo("Modifiable 1\n"); + } + + @Test + public void modifiableContentMayBeEmpty() { + MagicFile magicFile = MagicFile.builder().generatedContent("Generated 1\n").build(); + + assertThat(magicFile.getFileContent()).isEqualTo("Generated 1\n"); + } + + @Test + public void generatedContentAlwaysHasNewlineAtEnd() { + MagicFile magicFile = MagicFile.builder().generatedContent("Generated 1").build(); + + assertThat(magicFile.generatedContent()).isEqualTo("Generated 1\n"); + } + + @Test + public void modifiableContentAlwaysHasNewlineAtEnd() { + MagicFile magicFile = MagicFile.builder().modifiableContent("Modifiable 1").build(); + + assertThat(magicFile.modifiableContent()).isEqualTo("Modifiable 1\n"); + } + + @Test + public void startOfModifiableContentIsIndicatedCorrectlyWhenGeneratedContentIsPresent() { + MagicFile magicFile = + MagicFile.builder() + .generatedContent("Line 1\nLine2\n") + .modifiableContent("Line 3\n") + .build(); + + // Generated content. -> Modifiable content starts in line 3. + assertThat(magicFile.getStartLineOfModifiableContent()).isEqualTo(3); + } + + @Test + public void startOfModifiableContentIsIndicatedCorrectlyWhenGeneratedContentIsEmpty() { + MagicFile magicFile = MagicFile.builder().modifiableContent("Line 1\n").build(); + + assertThat(magicFile.getStartLineOfModifiableContent()).isEqualTo(1); + } + + @Test + public void commitMessageFileOfRootCommitContainsCorrectContent() throws Exception { + try (Repository repository = repositoryManager.createRepository(Project.nameKey("repo1")); + TestRepository testRepo = new TestRepository<>(repository); + ObjectReader objectReader = repository.newObjectReader()) { + + Instant authorTime = + LocalDateTime.of(2020, Month.APRIL, 23, 19, 30, 27).atZone(ZoneOffset.UTC).toInstant(); + PersonIdent author = + new PersonIdent( + "Alfred", + "alfred@example.com", + Date.from(authorTime), + TimeZone.getTimeZone(ZoneOffset.UTC)); + + Instant committerTime = + LocalDateTime.of(2021, Month.JANUARY, 6, 5, 12, 55).atZone(ZoneOffset.UTC).toInstant(); + PersonIdent committer = + new PersonIdent( + "Luise", + "luise@example.com", + Date.from(committerTime), + TimeZone.getTimeZone(ZoneOffset.UTC)); + + ObjectId commit = + testRepo + .commit() + .message("Subject line\n\nFurther explanations.\n") + .author(author) + .committer(committer) + .noParents() + .create(); + + MagicFile commitMessageFile = MagicFile.forCommitMessage(objectReader, commit); + + // The content of the commit message file must not change over time as existing comments + // would otherwise refer to different content than when they were originally left. + // -> Keep this format stable over time. + assertThat(commitMessageFile.getFileContent()) + .isEqualTo( + "Author: Alfred \n" + + "AuthorDate: 2020-04-23 19:30:27 +0000\n" + + "Commit: Luise \n" + + "CommitDate: 2021-01-06 05:12:55 +0000\n" + + "\n" + + "Subject line\n" + + "\n" + + "Further explanations.\n"); + } + } + + @Test + public void commitMessageFileOfNonMergeCommitContainsCorrectContent() throws Exception { + try (Repository repository = repositoryManager.createRepository(Project.nameKey("repo1")); + TestRepository testRepo = new TestRepository<>(repository); + ObjectReader objectReader = repository.newObjectReader()) { + + Instant authorTime = + LocalDateTime.of(2020, Month.APRIL, 23, 19, 30, 27).atZone(ZoneOffset.UTC).toInstant(); + PersonIdent author = + new PersonIdent( + "Alfred", + "alfred@example.com", + Date.from(authorTime), + TimeZone.getTimeZone(ZoneOffset.UTC)); + + Instant committerTime = + LocalDateTime.of(2021, Month.JANUARY, 6, 5, 12, 55).atZone(ZoneOffset.UTC).toInstant(); + PersonIdent committer = + new PersonIdent( + "Luise", + "luise@example.com", + Date.from(committerTime), + TimeZone.getTimeZone(ZoneOffset.UTC)); + + RevCommit parent = + testRepo.commit().message("Parent subject\n\nParent further details.").create(); + ObjectId commit = + testRepo + .commit() + .message("Subject line\n\nFurther explanations.\n") + .author(author) + .committer(committer) + .parent(parent) + .create(); + + MagicFile commitMessageFile = MagicFile.forCommitMessage(objectReader, commit); + + // The content of the commit message file must not change over time as existing comments + // would otherwise refer to different content than when they were originally left. + // -> Keep this format stable over time. + assertThat(commitMessageFile.getFileContent()) + .isEqualTo( + String.format( + "Parent: %s (Parent subject)\n" + + "Author: Alfred \n" + + "AuthorDate: 2020-04-23 19:30:27 +0000\n" + + "Commit: Luise \n" + + "CommitDate: 2021-01-06 05:12:55 +0000\n" + + "\n" + + "Subject line\n" + + "\n" + + "Further explanations.\n", + parent.name().substring(0, 8))); + } + } + + @Test + public void commitMessageFileOfMergeCommitContainsCorrectContent() throws Exception { + try (Repository repository = repositoryManager.createRepository(Project.nameKey("repo1")); + TestRepository testRepo = new TestRepository<>(repository); + ObjectReader objectReader = repository.newObjectReader()) { + + Instant authorTime = + LocalDateTime.of(2020, Month.APRIL, 23, 19, 30, 27).atZone(ZoneOffset.UTC).toInstant(); + PersonIdent author = + new PersonIdent( + "Alfred", + "alfred@example.com", + Date.from(authorTime), + TimeZone.getTimeZone(ZoneOffset.UTC)); + + Instant committerTime = + LocalDateTime.of(2021, Month.JANUARY, 6, 5, 12, 55).atZone(ZoneOffset.UTC).toInstant(); + PersonIdent committer = + new PersonIdent( + "Luise", + "luise@example.com", + Date.from(committerTime), + TimeZone.getTimeZone(ZoneOffset.UTC)); + + RevCommit parent1 = testRepo.commit().message("Parent 1\n\nExplanation 1.").create(); + RevCommit parent2 = testRepo.commit().message("Parent 2\n\nExplanation 2.").create(); + ObjectId commit = + testRepo + .commit() + .message("Subject line\n\nFurther explanations.\n") + .author(author) + .committer(committer) + .parent(parent1) + .parent(parent2) + .create(); + + MagicFile commitMessageFile = MagicFile.forCommitMessage(objectReader, commit); + + // The content of the commit message file must not change over time as existing comments + // would otherwise refer to different content than when they were originally left. + // -> Keep this format stable over time. + String expectedContent = + String.format( + "Merge Of: %s (Parent 1)\n" + + " %s (Parent 2)\n" + + "Author: Alfred \n" + + "AuthorDate: 2020-04-23 19:30:27 +0000\n" + + "Commit: Luise \n" + + "CommitDate: 2021-01-06 05:12:55 +0000\n" + + "\n" + + "Subject line\n" + + "\n" + + "Further explanations.\n", + parent1.name().substring(0, 8), parent2.name().substring(0, 8)); + assertThat(commitMessageFile.getFileContent()).isEqualTo(expectedContent); + } + } + + @Test + public void commitMessageFileEndsWithEmptyLineIfCommitMessageIsEmpty() throws Exception { + try (Repository repository = repositoryManager.createRepository(Project.nameKey("myRepo")); + TestRepository testRepo = new TestRepository<>(repository); + ObjectReader objectReader = repository.newObjectReader()) { + RevCommit commit = testRepo.commit().message("").create(); + + MagicFile commitMessageFile = MagicFile.forCommitMessage(objectReader, commit); + assertThat(commitMessageFile.getFileContent()).endsWith("\n\n"); + } + } + + @Test + public void commitMessageFileContainsFullCommitMessageAsModifiablePart() throws Exception { + try (Repository repository = repositoryManager.createRepository(Project.nameKey("myRepo")); + TestRepository testRepo = new TestRepository<>(repository); + ObjectReader objectReader = repository.newObjectReader()) { + RevCommit commit = + testRepo.commit().message("Subject line\n\nFurther explanations.\n").create(); + + MagicFile commitMessageFile = MagicFile.forCommitMessage(objectReader, commit); + assertThat(commitMessageFile.modifiableContent()) + .isEqualTo("Subject line\n\nFurther explanations.\n"); + } + } + + @Test + public void mergeListFileContainsCorrectContentForDiffAgainstFirstParent() throws Exception { + try (Repository repository = repositoryManager.createRepository(Project.nameKey("myRepo")); + TestRepository testRepo = new TestRepository<>(repository); + ObjectReader objectReader = repository.newObjectReader()) { + RevCommit parent1 = testRepo.commit().message("Parent 1\n\nExplanation 1.").create(); + RevCommit parent2 = testRepo.commit().message("Parent 2\n\nExplanation 2.").create(); + ObjectId commit = testRepo.commit().parent(parent1).parent(parent2).create(); + + MagicFile mergeListFile = + MagicFile.forMergeList(ComparisonType.againstParent(1), objectReader, commit); + + // The content of the merge list file must not change over time as existing comments + // would otherwise refer to different content than when they were originally left. + // -> Keep this format stable over time. + String expectedContent = + String.format("Merge List:\n\n* %s Parent 2\n", parent2.name().substring(0, 8)); + assertThat(mergeListFile.getFileContent()).isEqualTo(expectedContent); + } + } + + @Test + public void mergeListFileContainsCorrectContentForDiffAgainstSecondParent() throws Exception { + try (Repository repository = repositoryManager.createRepository(Project.nameKey("myRepo")); + TestRepository testRepo = new TestRepository<>(repository); + ObjectReader objectReader = repository.newObjectReader()) { + RevCommit parent1 = testRepo.commit().message("Parent 1\n\nExplanation 1.").create(); + RevCommit parent2 = testRepo.commit().message("Parent 2\n\nExplanation 2.").create(); + ObjectId commit = testRepo.commit().parent(parent1).parent(parent2).create(); + + MagicFile mergeListFile = + MagicFile.forMergeList(ComparisonType.againstParent(2), objectReader, commit); + + // The content of the merge list file must not change over time as existing comments + // would otherwise refer to different content than when they were originally left. + // -> Keep this format stable over time. + String expectedContent = + String.format("Merge List:\n\n* %s Parent 1\n", parent1.name().substring(0, 8)); + assertThat(mergeListFile.getFileContent()).isEqualTo(expectedContent); + } + } + + @Test + public void mergeListFileContainsCorrectContentForDiffAgainstAutoMerge() throws Exception { + try (Repository repository = repositoryManager.createRepository(Project.nameKey("myRepo")); + TestRepository testRepo = new TestRepository<>(repository); + ObjectReader objectReader = repository.newObjectReader()) { + RevCommit parent1 = testRepo.commit().message("Parent 1\n\nExplanation 1.").create(); + RevCommit parent2 = testRepo.commit().message("Parent 2\n\nExplanation 2.").create(); + ObjectId commit = testRepo.commit().parent(parent1).parent(parent2).create(); + + MagicFile mergeListFile = + MagicFile.forMergeList(ComparisonType.againstAutoMerge(), objectReader, commit); + + // When auto-merge is chosen, we fall back to the diff against the first parent. + String expectedContent = + String.format("Merge List:\n\n* %s Parent 2\n", parent2.name().substring(0, 8)); + assertThat(mergeListFile.getFileContent()).isEqualTo(expectedContent); + } + } + + @Test + public void mergeListFileIsEmptyForRootCommit() throws Exception { + try (Repository repository = repositoryManager.createRepository(Project.nameKey("myRepo")); + TestRepository testRepo = new TestRepository<>(repository); + ObjectReader objectReader = repository.newObjectReader()) { + ObjectId commit = testRepo.commit().noParents().create(); + + MagicFile mergeListFile = + MagicFile.forMergeList(ComparisonType.againstParent(1), objectReader, commit); + + assertThat(mergeListFile.getFileContent()).isEmpty(); + } + } + + @Test + public void mergeListFileIsEmptyForNonMergeCommit() throws Exception { + try (Repository repository = repositoryManager.createRepository(Project.nameKey("myRepo")); + TestRepository testRepo = new TestRepository<>(repository); + ObjectReader objectReader = repository.newObjectReader()) { + RevCommit parent = testRepo.commit().message("Parent 1\n").create(); + ObjectId commit = testRepo.commit().parent(parent).create(); + + MagicFile mergeListFile = + MagicFile.forMergeList(ComparisonType.againstParent(1), objectReader, commit); + + assertThat(mergeListFile.getFileContent()).isEmpty(); + } + } + + @Test + public void mergeListFileDoesNotHaveAModifiablePart() throws Exception { + try (Repository repository = repositoryManager.createRepository(Project.nameKey("myRepo")); + TestRepository testRepo = new TestRepository<>(repository); + ObjectReader objectReader = repository.newObjectReader()) { + RevCommit parent1 = testRepo.commit().message("Parent 1\n").create(); + RevCommit parent2 = testRepo.commit().message("Parent 2\n").create(); + ObjectId commit = testRepo.commit().parent(parent1).parent(parent2).create(); + + MagicFile mergeListFile = + MagicFile.forMergeList(ComparisonType.againstParent(1), objectReader, commit); + + // Nothing in the merge list file represents something users may modify. + assertThat(mergeListFile.modifiableContent()).isEmpty(); + } + } +}