Java tutorial
// Copyright (C) 2010 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.git; import static com.google.gerrit.server.git.GitRepositoryManager.REFS_NOTES_REVIEW; import com.google.gerrit.common.data.ApprovalType; import com.google.gerrit.common.data.ApprovalTypes; import com.google.gerrit.reviewdb.ApprovalCategory; import com.google.gerrit.reviewdb.Change; import com.google.gerrit.reviewdb.PatchSetApproval; import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.config.CanonicalWebUrl; import com.google.gwtorm.client.OrmException; import com.google.gwtorm.client.ResultSet; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.notes.Note; import org.eclipse.jgit.notes.NoteMap; import org.eclipse.jgit.notes.NoteMapMerger; import org.eclipse.jgit.notes.NoteMerger; import org.eclipse.jgit.revwalk.FooterKey; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import java.io.IOException; import java.util.List; import javax.annotation.Nullable; /** * This class create code review notes for given {@link CodeReviewCommit}s. * <p> * After the {@link #create(List, PersonIdent)} method is invoked once this * instance must not be reused. Create a new instance of this class if needed. */ public class CreateCodeReviewNotes { public interface Factory { CreateCodeReviewNotes create(ReviewDb reviewDb, Repository db); } private static final int MAX_LOCK_FAILURE_CALLS = 10; private static final int SLEEP_ON_LOCK_FAILURE_MS = 25; private static final FooterKey CHANGE_ID = new FooterKey("Change-Id"); private final ReviewDb schema; private final PersonIdent gerritIdent; private final AccountCache accountCache; private final ApprovalTypes approvalTypes; private final String canonicalWebUrl; private final Repository db; private final RevWalk revWalk; private final ObjectInserter inserter; private final ObjectReader reader; private RevCommit baseCommit; private NoteMap base; private RevCommit oursCommit; private NoteMap ours; private List<CodeReviewCommit> commits; private PersonIdent author; @Inject CreateCodeReviewNotes(@GerritPersonIdent final PersonIdent gerritIdent, final AccountCache accountCache, final ApprovalTypes approvalTypes, @Nullable @CanonicalWebUrl final String canonicalWebUrl, @Assisted ReviewDb reviewDb, @Assisted final Repository db) { schema = reviewDb; this.author = gerritIdent; this.gerritIdent = gerritIdent; this.accountCache = accountCache; this.approvalTypes = approvalTypes; this.canonicalWebUrl = canonicalWebUrl; this.db = db; revWalk = new RevWalk(db); inserter = db.newObjectInserter(); reader = db.newObjectReader(); } public void create(List<CodeReviewCommit> commits, PersonIdent author) throws CodeReviewNoteCreationException { try { this.commits = commits; this.author = author; loadBase(); applyNotes(); updateRef(); } catch (IOException e) { throw new CodeReviewNoteCreationException(e); } catch (InterruptedException e) { throw new CodeReviewNoteCreationException(e); } finally { release(); } } public void loadBase() throws IOException { Ref notesBranch = db.getRef(REFS_NOTES_REVIEW); if (notesBranch != null) { baseCommit = revWalk.parseCommit(notesBranch.getObjectId()); base = NoteMap.read(revWalk.getObjectReader(), baseCommit); } if (baseCommit != null) { ours = NoteMap.read(db.newObjectReader(), baseCommit); } else { ours = NoteMap.newEmptyMap(); } } private void applyNotes() throws IOException, CodeReviewNoteCreationException { StringBuilder message = new StringBuilder("Update notes for submitted changes\n\n"); for (CodeReviewCommit c : commits) { add(c.change, c); message.append("* ").append(c.getShortMessage()).append("\n"); } commit(message.toString()); } public void commit(String message) throws IOException { if (baseCommit != null) { oursCommit = createCommit(ours, author, message, baseCommit); } else { oursCommit = createCommit(ours, author, message); } } public void add(Change change, ObjectId commit) throws MissingObjectException, IncorrectObjectTypeException, IOException, CodeReviewNoteCreationException { if (!(commit instanceof RevCommit)) { commit = revWalk.parseCommit(commit); } RevCommit c = (RevCommit) commit; ObjectId noteContent = createNoteContent(change, c); if (ours.contains(c)) { // merge the existing and the new note as if they are both new // means: base == null // there is not really a common ancestry for these two note revisions // use the same NoteMerger that is used from the NoteMapMerger NoteMerger noteMerger = new ReviewNoteMerger(); Note newNote = new Note(c, noteContent); noteContent = noteMerger.merge(null, newNote, ours.getNote(c), reader, inserter).getData(); } ours.set(c, noteContent); } private ObjectId createNoteContent(Change change, RevCommit commit) throws CodeReviewNoteCreationException, IOException { try { ReviewNoteHeaderFormatter formatter = new ReviewNoteHeaderFormatter(author.getTimeZone()); final List<String> idList = commit.getFooterLines(CHANGE_ID); if (idList.isEmpty()) formatter.appendChangeId(change.getKey()); ResultSet<PatchSetApproval> approvals = schema.patchSetApprovals() .byPatchSet(change.currentPatchSetId()); PatchSetApproval submit = null; for (PatchSetApproval a : approvals) { if (a.getValue() == 0) { // Ignore 0 values. } else if (ApprovalCategory.SUBMIT.equals(a.getCategoryId())) { submit = a; } else { ApprovalType type = approvalTypes.byId(a.getCategoryId()); if (type != null) { formatter.appendApproval(type.getCategory(), a.getValue(), accountCache.get(a.getAccountId()).getAccount()); } } } if (submit != null) { formatter.appendSubmittedBy(accountCache.get(submit.getAccountId()).getAccount()); formatter.appendSubmittedAt(submit.getGranted()); } if (canonicalWebUrl != null) { formatter.appendReviewedOn(canonicalWebUrl, change.getId()); } formatter.appendProject(change.getProject().get()); formatter.appendBranch(change.getDest()); return inserter.insert(Constants.OBJ_BLOB, formatter.toString().getBytes("UTF-8")); } catch (OrmException e) { throw new CodeReviewNoteCreationException(commit, e); } } public void updateRef() throws IOException, InterruptedException, CodeReviewNoteCreationException, MissingObjectException, IncorrectObjectTypeException, CorruptObjectException { if (baseCommit != null && oursCommit.getTree().equals(baseCommit.getTree())) { // If the trees are identical, there is no change in the notes. // Avoid saving this commit as it has no new information. return; } int remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS; RefUpdate refUpdate = createRefUpdate(oursCommit, baseCommit); for (;;) { Result result = refUpdate.update(); if (result == Result.LOCK_FAILURE) { if (--remainingLockFailureCalls > 0) { Thread.sleep(SLEEP_ON_LOCK_FAILURE_MS); } else { throw new CodeReviewNoteCreationException("Failed to lock the ref: " + REFS_NOTES_REVIEW); } } else if (result == Result.REJECTED) { RevCommit theirsCommit = revWalk.parseCommit(refUpdate.getOldObjectId()); NoteMap theirs = NoteMap.read(revWalk.getObjectReader(), theirsCommit); NoteMapMerger merger = new NoteMapMerger(db); NoteMap merged = merger.merge(base, ours, theirs); RevCommit mergeCommit = createCommit(merged, gerritIdent, "Merged note commits\n", theirsCommit, oursCommit); refUpdate = createRefUpdate(mergeCommit, theirsCommit); remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS; } else if (result == Result.IO_FAILURE) { throw new CodeReviewNoteCreationException( "Couldn't create code review notes because of IO_FAILURE"); } else { break; } } } public void release() { reader.release(); inserter.release(); revWalk.release(); } private RevCommit createCommit(NoteMap map, PersonIdent author, String message, RevCommit... parents) throws IOException { CommitBuilder b = new CommitBuilder(); b.setTreeId(map.writeTree(inserter)); b.setAuthor(author != null ? author : gerritIdent); b.setCommitter(gerritIdent); if (parents.length > 0) { b.setParentIds(parents); } b.setMessage(message); ObjectId commitId = inserter.insert(b); inserter.flush(); return revWalk.parseCommit(commitId); } private RefUpdate createRefUpdate(ObjectId newObjectId, ObjectId expectedOldObjectId) throws IOException { RefUpdate refUpdate = db.updateRef(REFS_NOTES_REVIEW); refUpdate.setNewObjectId(newObjectId); if (expectedOldObjectId == null) { refUpdate.setExpectedOldObjectId(ObjectId.zeroId()); } else { refUpdate.setExpectedOldObjectId(expectedOldObjectId); } return refUpdate; } }