Package com.google.gerrit.server.git

Source Code of com.google.gerrit.server.git.CreateCodeReviewNotes$Factory

// 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.client.ApprovalCategory;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
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.gwtorm.server.OrmException;
import com.google.gwtorm.server.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 String anonymousCowardName;
  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,
      final @Nullable @CanonicalWebUrl String canonicalWebUrl,
      final @AnonymousCowardName String anonymousCowardName,
      final @Assisted  ReviewDb reviewDb,
      final @Assisted  Repository db) {
    schema = reviewDb;
    this.author = gerritIdent;
    this.gerritIdent = gerritIdent;
    this.accountCache = accountCache;
    this.approvalTypes = approvalTypes;
    this.canonicalWebUrl = canonicalWebUrl;
    this.anonymousCowardName = anonymousCowardName;
    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(),
              anonymousCowardName);
      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;
  }
}
TOP

Related Classes of com.google.gerrit.server.git.CreateCodeReviewNotes$Factory

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.