blob: e42dd8cc520d9038af54df1a28498fcab9a500f2 [file] [log] [blame]
// 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().get() : 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();
}
}
}