Line data Source code
1 : // Copyright (C) 2020 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.patch; 16 : 17 : import com.google.auto.value.AutoValue; 18 : import com.google.common.base.CharMatcher; 19 : import com.google.gerrit.git.ObjectIds; 20 : import java.io.IOException; 21 : import java.text.SimpleDateFormat; 22 : import org.eclipse.jgit.lib.AnyObjectId; 23 : import org.eclipse.jgit.lib.ObjectReader; 24 : import org.eclipse.jgit.lib.PersonIdent; 25 : import org.eclipse.jgit.revwalk.RevCommit; 26 : import org.eclipse.jgit.revwalk.RevWalk; 27 : 28 : /** Representation of a magic file which appears as a file with content to Gerrit users. */ 29 : @AutoValue 30 104 : public abstract class MagicFile { 31 : 32 : public static MagicFile forCommitMessage(ObjectReader reader, AnyObjectId commitId) 33 : throws IOException { 34 104 : try (RevWalk rw = new RevWalk(reader)) { 35 : RevCommit c; 36 104 : if (commitId instanceof RevCommit) { 37 104 : c = (RevCommit) commitId; 38 : } else { 39 3 : c = rw.parseCommit(commitId); 40 : } 41 : 42 104 : String header = createCommitMessageHeader(reader, rw, c); 43 104 : String message = c.getFullMessage(); 44 104 : return MagicFile.builder().generatedContent(header).modifiableContent(message).build(); 45 : } 46 : } 47 : 48 : private static String createCommitMessageHeader(ObjectReader reader, RevWalk rw, RevCommit c) 49 : throws IOException { 50 104 : StringBuilder b = new StringBuilder(); 51 104 : switch (c.getParentCount()) { 52 : case 0: 53 33 : break; 54 : case 1: 55 : { 56 100 : RevCommit p = c.getParent(0); 57 100 : rw.parseBody(p); 58 100 : b.append("Parent: "); 59 100 : b.append(abbreviateName(p, reader)); 60 100 : b.append(" ("); 61 100 : b.append(p.getShortMessage()); 62 100 : b.append(")\n"); 63 100 : break; 64 : } 65 : default: 66 31 : for (int i = 0; i < c.getParentCount(); i++) { 67 31 : RevCommit p = c.getParent(i); 68 31 : rw.parseBody(p); 69 31 : b.append(i == 0 ? "Merge Of: " : " "); 70 31 : b.append(abbreviateName(p, reader)); 71 31 : b.append(" ("); 72 31 : b.append(p.getShortMessage()); 73 31 : b.append(")\n"); 74 : } 75 : } 76 104 : appendPersonIdent(b, "Author", c.getAuthorIdent()); 77 104 : appendPersonIdent(b, "Commit", c.getCommitterIdent()); 78 104 : b.append("\n"); 79 104 : return b.toString(); 80 : } 81 : 82 : public static MagicFile forMergeList( 83 : ComparisonType comparisonType, ObjectReader reader, AnyObjectId commitId) throws IOException { 84 31 : try (RevWalk rw = new RevWalk(reader)) { 85 31 : RevCommit c = rw.parseCommit(commitId); 86 31 : StringBuilder b = new StringBuilder(); 87 31 : switch (c.getParentCount()) { 88 : case 0: 89 1 : break; 90 : case 1: 91 : { 92 1 : break; 93 : } 94 : default: 95 : int uninterestingParent = 96 31 : comparisonType.isAgainstParent() ? comparisonType.getParentNum().get() : 1; 97 : 98 31 : b.append("Merge List:\n\n"); 99 31 : for (RevCommit commit : MergeListBuilder.build(rw, c, uninterestingParent)) { 100 31 : b.append("* "); 101 31 : b.append(abbreviateName(commit, reader)); 102 31 : b.append(" "); 103 31 : b.append(commit.getShortMessage()); 104 31 : b.append("\n"); 105 31 : } 106 : } 107 31 : return MagicFile.builder().generatedContent(b.toString()).build(); 108 : } 109 : } 110 : 111 : private static String abbreviateName(RevCommit p, ObjectReader reader) throws IOException { 112 100 : return ObjectIds.abbreviateName(p, 8, reader); 113 : } 114 : 115 : private static void appendPersonIdent(StringBuilder b, String field, PersonIdent person) { 116 104 : if (person != null) { 117 104 : b.append(field).append(": "); 118 104 : if (person.getName() != null) { 119 104 : b.append(" "); 120 104 : b.append(person.getName()); 121 : } 122 104 : if (person.getEmailAddress() != null) { 123 104 : b.append(" <"); 124 104 : b.append(person.getEmailAddress()); 125 104 : b.append(">"); 126 : } 127 104 : b.append("\n"); 128 : 129 104 : SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss ZZZ"); 130 104 : sdf.setTimeZone(person.getTimeZone()); 131 104 : b.append(field).append("Date: "); 132 104 : b.append(sdf.format(person.getWhen())); 133 104 : b.append("\n"); 134 : } 135 104 : } 136 : 137 : /** Generated part of the file. Any generated contents should go here. Can be empty. */ 138 : public abstract String generatedContent(); 139 : 140 : /** 141 : * Non-generated part of the file. This should correspond to some actual content derived from 142 : * somewhere else which can also be modified (e.g. by suggested fixes). Can be empty. 143 : */ 144 : public abstract String modifiableContent(); 145 : 146 : /** Whole content of the file as it appears to users. */ 147 : public String getFileContent() { 148 104 : return generatedContent() + modifiableContent(); 149 : } 150 : 151 : /** Returns the start line of the modifiable content. Assumes that line counting starts at 1. */ 152 : public int getStartLineOfModifiableContent() { 153 3 : int numHeaderLines = CharMatcher.is('\n').countIn(generatedContent()); 154 : // Lines start at 1 and not 0. -> Add 1. 155 3 : return 1 + numHeaderLines; 156 : } 157 : 158 : static Builder builder() { 159 104 : return new AutoValue_MagicFile.Builder().generatedContent("").modifiableContent(""); 160 : } 161 : 162 : @AutoValue.Builder 163 104 : abstract static class Builder { 164 : 165 : /** See {@link #generatedContent()}. Use an empty string to denote no such content. */ 166 : public abstract Builder generatedContent(String content); 167 : 168 : /** See {@link #modifiableContent()}. Use an empty string to denote no such content. */ 169 : public abstract Builder modifiableContent(String content); 170 : 171 : abstract String generatedContent(); 172 : 173 : abstract String modifiableContent(); 174 : 175 : abstract MagicFile autoBuild(); 176 : 177 : public MagicFile build() { 178 : // Normalize each content part to end with a newline character, which simplifies further 179 : // handling. 180 104 : if (!generatedContent().isEmpty() && !generatedContent().endsWith("\n")) { 181 1 : generatedContent(generatedContent() + "\n"); 182 : } 183 104 : if (!modifiableContent().isEmpty() && !modifiableContent().endsWith("\n")) { 184 14 : modifiableContent(modifiableContent() + "\n"); 185 : } 186 104 : return autoBuild(); 187 : } 188 : } 189 : }