All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.server.notedb.DraftCommentNotes Maven / Gradle / Ivy

There is a newer version: 3.10.0-rc4
Show newest version
// Copyright (C) 2014 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 static com.google.common.base.Preconditions.checkArgument;
import static com.google.gerrit.reviewdb.client.RefNames.refsDraftComments;
import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
import static java.util.Objects.requireNonNull;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.metrics.Timer1;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Comment;
import com.google.gerrit.reviewdb.client.PatchLineComment;
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.git.RepoRefCache;
import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
import com.google.gerrit.server.notedb.NoteDbUpdateManager.StagedResult;
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
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.transport.ReceiveCommand;

/** View of the draft comments for a single {@link Change} based on the log of its drafts branch. */
public class DraftCommentNotes extends AbstractChangeNotes {
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();

  public interface Factory {
    DraftCommentNotes create(Change change, Account.Id accountId);

    DraftCommentNotes createWithAutoRebuildingDisabled(Change.Id changeId, Account.Id accountId);
  }

  private final Change change;
  private final Account.Id author;
  private final NoteDbUpdateManager.Result rebuildResult;
  private final Ref ref;

  private ImmutableListMultimap comments;
  private RevisionNoteMap revisionNoteMap;

  @AssistedInject
  DraftCommentNotes(Args args, @Assisted Change change, @Assisted Account.Id author) {
    this(args, change, author, true, null, null);
  }

  @AssistedInject
  DraftCommentNotes(Args args, @Assisted Change.Id changeId, @Assisted Account.Id author) {
    // PrimaryStorage is unknown; this should only called by
    // PatchLineCommentsUtil#draftByAuthor, which can live with this.
    super(args, changeId, null, false);
    this.change = null;
    this.author = author;
    this.rebuildResult = null;
    this.ref = null;
  }

  DraftCommentNotes(
      Args args,
      Change change,
      Account.Id author,
      boolean autoRebuild,
      @Nullable NoteDbUpdateManager.Result rebuildResult,
      @Nullable Ref ref) {
    super(args, change.getId(), PrimaryStorage.of(change), autoRebuild);
    this.change = change;
    this.author = author;
    this.rebuildResult = rebuildResult;
    this.ref = ref;
    if (ref != null) {
      checkArgument(
          ref.getName().equals(getRefName()),
          "draft ref not for change %s and account %s: %s",
          getChangeId(),
          author,
          ref.getName());
    }
  }

  RevisionNoteMap getRevisionNoteMap() {
    return revisionNoteMap;
  }

  public Account.Id getAuthor() {
    return author;
  }

  public ImmutableListMultimap getComments() {
    return comments;
  }

  public boolean containsComment(Comment c) {
    for (Comment existing : comments.values()) {
      if (c.key.equals(existing.key)) {
        return true;
      }
    }
    return false;
  }

  @Override
  protected String getRefName() {
    return refsDraftComments(getChangeId(), author);
  }

  @Override
  protected ObjectId readRef(Repository repo) throws IOException {
    if (ref != null) {
      return ref.getObjectId();
    }
    return super.readRef(repo);
  }

  @Override
  protected void onLoad(LoadHandle handle) throws IOException, ConfigInvalidException {
    ObjectId rev = handle.id();
    if (rev == null) {
      loadDefaults();
      return;
    }

    logger.atFine().log(
        "Load draft comment notes for change %s of project %s", getChangeId(), getProjectName());
    RevCommit tipCommit = handle.walk().parseCommit(rev);
    ObjectReader reader = handle.walk().getObjectReader();
    revisionNoteMap =
        RevisionNoteMap.parse(
            args.changeNoteJson,
            args.legacyChangeNoteRead,
            getChangeId(),
            reader,
            NoteMap.read(reader, tipCommit),
            PatchLineComment.Status.DRAFT);
    ListMultimap cs = MultimapBuilder.hashKeys().arrayListValues().build();
    for (ChangeRevisionNote rn : revisionNoteMap.revisionNotes.values()) {
      for (Comment c : rn.getComments()) {
        cs.put(new RevId(c.revId), c);
      }
    }
    comments = ImmutableListMultimap.copyOf(cs);
  }

  @Override
  protected void loadDefaults() {
    comments = ImmutableListMultimap.of();
  }

  @Override
  public Project.NameKey getProjectName() {
    return args.allUsers;
  }

  @Override
  protected LoadHandle openHandle(Repository repo) throws NoSuchChangeException, IOException {
    if (rebuildResult != null) {
      StagedResult sr = requireNonNull(rebuildResult.staged());
      return LoadHandle.create(
          ChangeNotesCommit.newStagedRevWalk(repo, sr.allUsersObjects()),
          findNewId(sr.allUsersCommands(), getRefName()));
    } else if (change != null && autoRebuild) {
      NoteDbChangeState state = NoteDbChangeState.parse(change);
      // Only check if this particular user's drafts are up to date, to avoid
      // reading unnecessary refs.
      if (!NoteDbChangeState.areDraftsUpToDate(
          state, new RepoRefCache(repo), getChangeId(), author)) {
        return rebuildAndOpen(repo);
      }
    }
    return super.openHandle(repo);
  }

  private static ObjectId findNewId(Iterable cmds, String refName) {
    for (ReceiveCommand cmd : cmds) {
      if (cmd.getRefName().equals(refName)) {
        return cmd.getNewId();
      }
    }
    return null;
  }

  private LoadHandle rebuildAndOpen(Repository repo) throws NoSuchChangeException, IOException {
    Timer1.Context timer = args.metrics.autoRebuildLatency.start(CHANGES);
    try {
      Change.Id cid = getChangeId();
      ReviewDb db = args.db.get();
      ChangeRebuilder rebuilder = args.rebuilder.get();
      NoteDbUpdateManager.Result r;
      try (NoteDbUpdateManager manager = rebuilder.stage(db, cid)) {
        if (manager == null) {
          return super.openHandle(repo); // May be null in tests.
        }
        r = manager.stageAndApplyDelta(change);
        try {
          rebuilder.execute(db, cid, manager);
          repo.scanForRepoChanges();
        } catch (OrmException | IOException e) {
          // See ChangeNotes#rebuildAndOpen.
          logger.atFine().log(
              "Rebuilding change %s via drafts failed: %s", getChangeId(), e.getMessage());
          args.metrics.autoRebuildFailureCount.increment(CHANGES);
          requireNonNull(r.staged());
          return LoadHandle.create(
              ChangeNotesCommit.newStagedRevWalk(repo, r.staged().allUsersObjects()), draftsId(r));
        }
      }
      return LoadHandle.create(ChangeNotesCommit.newRevWalk(repo), draftsId(r));
    } catch (NoSuchChangeException e) {
      return super.openHandle(repo);
    } catch (OrmException e) {
      throw new IOException(e);
    } finally {
      logger.atFine().log(
          "Rebuilt change %s in %s in %s ms via drafts",
          getChangeId(),
          change != null ? "project " + change.getProject() : "unknown project",
          TimeUnit.MILLISECONDS.convert(timer.stop(), TimeUnit.NANOSECONDS));
    }
  }

  private ObjectId draftsId(NoteDbUpdateManager.Result r) {
    requireNonNull(r);
    requireNonNull(r.newState());
    return r.newState().getDraftIds().get(author);
  }

  @VisibleForTesting
  NoteMap getNoteMap() {
    return revisionNoteMap != null ? revisionNoteMap.noteMap : null;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy