blob: 8546bf0aa9d9ed8a719463602c483fa44b14d3ef [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.googlesource.gerrit.plugins.reviewnotes;
import java.io.IOException;
import java.util.List;
import javax.annotation.Nullable;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.notes.NoteMap;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.git.NotesBranchUtil;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
class CreateReviewNotes {
private static final Logger log =
LoggerFactory.getLogger(CreateReviewNotes.class);
interface Factory {
CreateReviewNotes create(ReviewDb reviewDb, Project.NameKey project,
Repository git);
}
private static final String REFS_NOTES_REVIEW = "refs/notes/review";
private final PersonIdent gerritServerIdent;
private final AccountCache accountCache;
private final String anonymousCowardName;
private final LabelTypes labelTypes;
private final NotesBranchUtil.Factory notesBranchUtilFactory;
private final String canonicalWebUrl;
private final ReviewDb reviewDb;
private final Project.NameKey project;
private final Repository git;
private ObjectInserter inserter;
private NoteMap reviewNotes;
private StringBuilder message;
@Inject
CreateReviewNotes(@GerritPersonIdent final PersonIdent gerritIdent,
final AccountCache accountCache,
final @AnonymousCowardName String anonymousCowardName,
LabelTypes approvalTypes,
final NotesBranchUtil.Factory notesBranchUtilFactory,
final @Nullable @CanonicalWebUrl String canonicalWebUrl,
final @Assisted ReviewDb reviewDb,
final @Assisted Project.NameKey project,
final @Assisted Repository git) {
this.gerritServerIdent = gerritIdent;
this.accountCache = accountCache;
this.anonymousCowardName = anonymousCowardName;
this.labelTypes = approvalTypes;
this.notesBranchUtilFactory = notesBranchUtilFactory;
this.canonicalWebUrl = canonicalWebUrl;
this.reviewDb = reviewDb;
this.project = project;
this.git = git;
}
void createNotes(String branch, ObjectId oldObjectId, ObjectId newObjectId,
ProgressMonitor monitor) throws OrmException, IOException {
RevWalk rw = new RevWalk(git);
try {
rw.markStart(rw.parseCommit(newObjectId));
markUninteresting(git, branch, rw, oldObjectId);
} catch (Exception e) {
log.error(e.getMessage(), e);
return;
}
if (monitor == null) {
monitor = NullProgressMonitor.INSTANCE;
}
try {
for (RevCommit c : rw) {
ObjectId content = createNoteContent(branch, c);
if (content != null) {
monitor.update(1);
getNotes().set(c, content);
getMessage().append("* ").append(c.getShortMessage()).append("\n");
}
}
} finally {
rw.release();
}
}
void createNotes(List<Change> changes, ProgressMonitor monitor)
throws OrmException, IOException {
RevWalk rw = new RevWalk(git);
try {
if (monitor == null) {
monitor = NullProgressMonitor.INSTANCE;
}
NoteMap notes = NoteMap.newEmptyMap();
for (Change c : changes) {
monitor.update(1);
PatchSet ps = reviewDb.patchSets().get(c.currentPatchSetId());
ObjectId commitId = ObjectId.fromString(ps.getRevision().get());
notes.set(commitId,
createNoteContent(c.getDest().get(), rw.parseCommit(commitId)));
}
} finally {
rw.release();
}
}
void commitNotes() throws IOException, ConcurrentRefUpdateException {
try {
if (reviewNotes == null) {
return;
}
message.insert(0, "Update notes for submitted changes\n\n");
notesBranchUtilFactory.create(project, git, inserter)
.commitAllNotes(reviewNotes, REFS_NOTES_REVIEW, gerritServerIdent,
message.toString());
} finally {
if (inserter != null) {
inserter.release();
}
}
}
private void markUninteresting(Repository git, String branch, RevWalk rw,
ObjectId oldObjectId) {
for (final Ref r : git.getAllRefs().values()) {
try {
if (r.getName().equals(branch)) {
if (!ObjectId.zeroId().equals(oldObjectId)) {
// For the updated branch the oldObjectId is the tip of uninteresting
// commit history
rw.markUninteresting(rw.parseCommit(oldObjectId));
}
} else if (r.getName().startsWith(Constants.R_HEADS)
|| r.getName().startsWith(Constants.R_TAGS)) {
rw.markUninteresting(rw.parseCommit(r.getObjectId()));
}
} catch (IncorrectObjectTypeException e) {
// skip if not parseable as a commit
} catch (MissingObjectException e) {
// skip if not parseable as a commit
} catch (IOException e) {
// skip if not parseable as a commit
}
}
}
private ObjectId createNoteContent(String branch, RevCommit c)
throws OrmException, IOException {
List<PatchSet> patches = reviewDb.patchSets().byRevision(new RevId(c.name()))
.toList();
HeaderFormatter fmt =
new HeaderFormatter(gerritServerIdent.getTimeZone(), anonymousCowardName);
if (patches.isEmpty()) {
return null; // TODO: createNoCodeReviewNote(branch, c, fmt);
} else if (patches.size() == 1) {
createCodeReviewNote(branch, patches.get(0), fmt);
} else {
log.error("Cannot create review note:"
+ " more than one patch set found for the commit " + c.name());
return null;
}
return getInseter().insert(Constants.OBJ_BLOB, fmt.toString().getBytes("UTF-8"));
}
private void createCodeReviewNote(String branch, PatchSet ps,
HeaderFormatter fmt) throws OrmException {
ResultSet<PatchSetApproval> approvals =
reviewDb.patchSetApprovals().byPatchSet(ps.getId());
PatchSetApproval submit = null;
for (PatchSetApproval a : approvals) {
if (a.getValue() == 0) {
// Ignore 0 values.
} else if (a.isSubmit()) {
submit = a;
} else {
LabelType type = labelTypes.byLabel(a.getLabelId());
if (type != null) {
fmt.appendApproval(type, a.getValue(),
accountCache.get(a.getAccountId()).getAccount());
}
}
}
if (submit != null) {
fmt.appendSubmittedBy(accountCache.get(submit.getAccountId()).getAccount());
fmt.appendSubmittedAt(submit.getGranted());
}
if (canonicalWebUrl != null) {
fmt.appendReviewedOn(canonicalWebUrl, ps.getId().getParentKey());
}
fmt.appendProject(project.get());
fmt.appendBranch(branch);
}
private ObjectInserter getInseter() {
if (inserter == null) {
inserter = git.newObjectInserter();
}
return inserter;
}
private NoteMap getNotes() {
if (reviewNotes == null) {
reviewNotes = NoteMap.newEmptyMap();
}
return reviewNotes;
}
private StringBuilder getMessage() {
if (message == null) {
message = new StringBuilder();
}
return message;
}
}