blob: 86b6ed78343fe5c09141f92bee36c4edaebf1a36 [file] [log] [blame]
// Copyright (C) 2013 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.notedb;
import com.google.auto.value.AutoValue;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AttentionSetUpdate;
import com.google.gerrit.json.OutputFormat;
import com.google.gerrit.server.config.GerritServerId;
import com.google.gson.Gson;
import com.google.inject.Inject;
import java.time.Instant;
import java.util.Date;
import java.util.Optional;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.FooterKey;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.util.RawParseUtils;
public class ChangeNoteUtil {
static final FooterKey FOOTER_ATTENTION = new FooterKey("Attention");
static final FooterKey FOOTER_ASSIGNEE = new FooterKey("Assignee");
static final FooterKey FOOTER_BRANCH = new FooterKey("Branch");
static final FooterKey FOOTER_CHANGE_ID = new FooterKey("Change-id");
static final FooterKey FOOTER_COMMIT = new FooterKey("Commit");
static final FooterKey FOOTER_CURRENT = new FooterKey("Current");
static final FooterKey FOOTER_GROUPS = new FooterKey("Groups");
static final FooterKey FOOTER_HASHTAGS = new FooterKey("Hashtags");
static final FooterKey FOOTER_LABEL = new FooterKey("Label");
static final FooterKey FOOTER_PATCH_SET = new FooterKey("Patch-set");
static final FooterKey FOOTER_PATCH_SET_DESCRIPTION = new FooterKey("Patch-set-description");
static final FooterKey FOOTER_PRIVATE = new FooterKey("Private");
static final FooterKey FOOTER_REAL_USER = new FooterKey("Real-user");
static final FooterKey FOOTER_STATUS = new FooterKey("Status");
static final FooterKey FOOTER_SUBJECT = new FooterKey("Subject");
static final FooterKey FOOTER_SUBMISSION_ID = new FooterKey("Submission-id");
static final FooterKey FOOTER_SUBMITTED_WITH = new FooterKey("Submitted-with");
static final FooterKey FOOTER_TOPIC = new FooterKey("Topic");
static final FooterKey FOOTER_TAG = new FooterKey("Tag");
static final FooterKey FOOTER_WORK_IN_PROGRESS = new FooterKey("Work-in-progress");
static final FooterKey FOOTER_REVERT_OF = new FooterKey("Revert-of");
static final FooterKey FOOTER_CHERRY_PICK_OF = new FooterKey("Cherry-pick-of");
private static final Gson gson = OutputFormat.JSON_COMPACT.newGson();
private final ChangeNoteJson changeNoteJson;
private final String serverId;
@Inject
public ChangeNoteUtil(ChangeNoteJson changeNoteJson, @GerritServerId String serverId) {
this.serverId = serverId;
this.changeNoteJson = changeNoteJson;
}
public ChangeNoteJson getChangeNoteJson() {
return changeNoteJson;
}
public PersonIdent newIdent(Account.Id accountId, Date when, PersonIdent serverIdent) {
return new PersonIdent(
getUsername(accountId), getEmailAddress(accountId), when, serverIdent.getTimeZone());
}
private static String getUsername(Account.Id accountId) {
return "Gerrit User " + accountId.toString();
}
private String getEmailAddress(Account.Id accountId) {
return accountId.get() + "@" + serverId;
}
public static Optional<CommitMessageRange> parseCommitMessageRange(RevCommit commit) {
byte[] raw = commit.getRawBuffer();
int size = raw.length;
int subjectStart = RawParseUtils.commitMessage(raw, 0);
if (subjectStart < 0 || subjectStart >= size) {
return Optional.empty();
}
int subjectEnd = RawParseUtils.endOfParagraph(raw, subjectStart);
if (subjectEnd == size) {
return Optional.empty();
}
int changeMessageStart;
if (raw[subjectEnd] == '\n') {
changeMessageStart = subjectEnd + 2; // \n\n ends paragraph
} else if (raw[subjectEnd] == '\r') {
changeMessageStart = subjectEnd + 4; // \r\n\r\n ends paragraph
} else {
return Optional.empty();
}
int ptr = size - 1;
int changeMessageEnd = -1;
while (ptr > changeMessageStart) {
ptr = RawParseUtils.prevLF(raw, ptr, '\r');
if (ptr == -1) {
break;
}
if (raw[ptr] == '\n') {
changeMessageEnd = ptr - 1;
break;
} else if (raw[ptr] == '\r') {
changeMessageEnd = ptr - 3;
break;
}
}
if (ptr <= changeMessageStart) {
return Optional.empty();
}
CommitMessageRange range =
CommitMessageRange.builder()
.subjectStart(subjectStart)
.subjectEnd(subjectEnd)
.changeMessageStart(changeMessageStart)
.changeMessageEnd(changeMessageEnd)
.build();
return Optional.of(range);
}
@AutoValue
public abstract static class CommitMessageRange {
public abstract int subjectStart();
public abstract int subjectEnd();
public abstract int changeMessageStart();
public abstract int changeMessageEnd();
public static Builder builder() {
return new AutoValue_ChangeNoteUtil_CommitMessageRange.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
abstract Builder subjectStart(int subjectStart);
abstract Builder subjectEnd(int subjectEnd);
abstract Builder changeMessageStart(int changeMessageStart);
abstract Builder changeMessageEnd(int changeMessageEnd);
abstract CommitMessageRange build();
}
}
/** Helper class for JSON serialization. Timestamp is taken from the commit. */
private static class AttentionStatusInNoteDb {
final String personIdent;
final AttentionSetUpdate.Operation operation;
final String reason;
AttentionStatusInNoteDb(
String personIndent, AttentionSetUpdate.Operation operation, String reason) {
this.personIdent = personIndent;
this.operation = operation;
this.reason = reason;
}
}
/** The returned {@link Optional} holds the parsed entity or is empty if parsing failed. */
static Optional<AttentionSetUpdate> attentionStatusFromJson(
Instant timestamp, String attentionString) {
AttentionStatusInNoteDb inNoteDb =
gson.fromJson(attentionString, AttentionStatusInNoteDb.class);
PersonIdent personIdent = RawParseUtils.parsePersonIdent(inNoteDb.personIdent);
if (personIdent == null) {
return Optional.empty();
}
Optional<Account.Id> account = NoteDbUtil.parseIdent(personIdent);
return account.map(
id ->
AttentionSetUpdate.createFromRead(timestamp, id, inNoteDb.operation, inNoteDb.reason));
}
String attentionSetUpdateToJson(AttentionSetUpdate attentionSetUpdate) {
PersonIdent personIdent =
new PersonIdent(
getUsername(attentionSetUpdate.account()),
getEmailAddress(attentionSetUpdate.account()));
StringBuilder stringBuilder = new StringBuilder();
appendIdentString(stringBuilder, personIdent.getName(), personIdent.getEmailAddress());
return gson.toJson(
new AttentionStatusInNoteDb(
stringBuilder.toString(), attentionSetUpdate.operation(), attentionSetUpdate.reason()));
}
static void appendIdentString(StringBuilder stringBuilder, String name, String emailAddress) {
PersonIdent.appendSanitized(stringBuilder, name);
stringBuilder.append(" <");
PersonIdent.appendSanitized(stringBuilder, emailAddress);
stringBuilder.append('>');
}
}