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

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

There is a newer version: 3.10.0-rc6
Show newest version
// Copyright (C) 2020 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.collect.ImmutableList.toImmutableList;
import static com.google.gerrit.entities.RefNames.REFS_DRAFT_COMMENTS;
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.CHANGE_MODIFICATION;
import static org.eclipse.jgit.lib.Constants.EMPTY_TREE_ID;

import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.HumanComment;
import com.google.gerrit.git.RefUpdateUtil;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.DeleteZombieComments;
import com.google.gerrit.server.DraftCommentsReader;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.update.context.RefUpdateContext;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.ReceiveCommand;

/**
 * This class can be used to clean zombie draft comments from NoteDB.
 *
 * 

See {@link DeleteZombieComments} for more info. */ public class DeleteZombieCommentsRefs extends DeleteZombieComments { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); // Number of refs deleted at once in a batch ref-update. // Log progress after deleting every CHUNK_SIZE refs private static final int CHUNK_SIZE = 3000; private final GitRepositoryManager repoManager; private final AllUsersName allUsers; @Nullable private final ChangeUpdate.Factory changeUpdateFactory; @Nullable private final IdentifiedUser.GenericFactory userFactory; private Repository allUsersRepo; public interface Factory { DeleteZombieCommentsRefs create(int cleanupPercentage); DeleteZombieCommentsRefs create(int cleanupPercentage, boolean dryRun); } @AssistedInject public DeleteZombieCommentsRefs( @Assisted Integer cleanupPercentage, GitRepositoryManager repoManager, AllUsersName allUsers, DraftCommentsReader draftCommentsReader, ChangeNotes.Factory changeNotesFactory, CommentsUtil commentsUtil, ChangeUpdate.Factory changeUpdateFactory, IdentifiedUser.GenericFactory userFactory) { this( cleanupPercentage, /* dryRun= */ true, (msg) -> {}, repoManager, allUsers, draftCommentsReader, changeNotesFactory, commentsUtil, changeUpdateFactory, userFactory); } @AssistedInject public DeleteZombieCommentsRefs( @Assisted Integer cleanupPercentage, @Assisted boolean dryRun, GitRepositoryManager repoManager, AllUsersName allUsers, DraftCommentsReader draftCommentsReader, ChangeNotes.Factory changeNotesFactory, CommentsUtil commentsUtil, ChangeUpdate.Factory changeUpdateFactory, IdentifiedUser.GenericFactory userFactory) { this( cleanupPercentage, dryRun, (msg) -> {}, repoManager, allUsers, draftCommentsReader, changeNotesFactory, commentsUtil, changeUpdateFactory, userFactory); } public DeleteZombieCommentsRefs( AllUsersName allUsers, GitRepositoryManager repoManager, Integer cleanupPercentage, Consumer uiConsumer) { this( cleanupPercentage, /* dryRun= */ false, uiConsumer, repoManager, allUsers, null, null, null, null, null); } private DeleteZombieCommentsRefs( Integer cleanupPercentage, boolean dryRun, Consumer uiConsumer, GitRepositoryManager repoManager, AllUsersName allUsers, @Nullable DraftCommentsReader draftCommentsReader, @Nullable ChangeNotes.Factory changeNotesFactory, @Nullable CommentsUtil commentsUtil, @Nullable ChangeUpdate.Factory changeUpdateFactory, @Nullable IdentifiedUser.GenericFactory userFactory) { super( cleanupPercentage, dryRun, uiConsumer, repoManager, draftCommentsReader, changeNotesFactory, commentsUtil); this.allUsers = allUsers; this.repoManager = repoManager; this.changeUpdateFactory = changeUpdateFactory; this.userFactory = userFactory; } @Override public void setup() throws IOException { allUsersRepo = repoManager.openRepository(allUsers); } @Override public void close() throws IOException { allUsersRepo.close(); } @Override protected List listAllDrafts() throws IOException { return allUsersRepo.getRefDatabase().getRefsByPrefix(REFS_DRAFT_COMMENTS); } @Override protected List listEmptyDrafts() throws IOException { List zombieRefs = filterZombieRefs(allUsersRepo, listAllDrafts()); logInfo( String.format( "Found a total of %d zombie draft refs in %s repo.", zombieRefs.size(), allUsers.get())); return zombieRefs; } /** * An earlier bug in the deletion of draft comments {@code * refs/draft-comments/$change_id_short/$change_id/$user_id} caused some draft refs to remain in * Git and not get deleted. These refs point to an empty tree. We delete such refs. */ @Override protected void deleteEmptyDraftsByKey(Collection refs) throws IOException { long zombieRefsCnt = refs.size(); long deletedRefsCnt = 0; long startTime = System.currentTimeMillis(); for (List refsBatch : Iterables.partition(refs, CHUNK_SIZE)) { deleteZombieDraftsBatch(refsBatch); long elapsed = (System.currentTimeMillis() - startTime) / 1000; deletedRefsCnt += refsBatch.size(); logProgress(deletedRefsCnt, zombieRefsCnt, elapsed); } } @Override protected void deleteZombieDrafts(ListMultimap drafts) throws IOException { for (Map.Entry> e : drafts.asMap().entrySet()) { deleteZombieDraftsForChange( getAccountId(e.getKey()), getChangeNotes(getChangeId(e.getKey())), e.getValue()); } } @Override protected Change.Id getChangeId(Ref ref) { return Change.Id.fromAllUsersRef(ref.getName()); } @Override protected Account.Id getAccountId(Ref ref) { return Account.Id.fromRef(ref.getName()); } @Override protected String loggable(Ref ref) { return ref.getName(); } private List filterZombieRefs(Repository allUsersRepo, List allDraftRefs) throws IOException { List zombieRefs = new ArrayList<>((int) (allDraftRefs.size() * 0.5)); for (Ref ref : allDraftRefs) { if (isZombieRef(allUsersRepo, ref)) { zombieRefs.add(ref); } } return zombieRefs; } private boolean isZombieRef(Repository allUsersRepo, Ref ref) throws IOException { return allUsersRepo.parseCommit(ref.getObjectId()).getTree().getId().equals(EMPTY_TREE_ID); } private void logProgress(long deletedRefsCount, long allRefsCount, long elapsed) { logInfo( String.format( "Deleted %d/%d zombie draft refs (%d seconds)", deletedRefsCount, allRefsCount, elapsed)); } private void deleteZombieDraftsBatch(Collection refsBatch) throws IOException { try (RefUpdateContext ctx = RefUpdateContext.open(CHANGE_MODIFICATION)) { List deleteCommands = refsBatch.stream() .map( zombieRef -> new ReceiveCommand( zombieRef.getObjectId(), ObjectId.zeroId(), zombieRef.getName())) .collect(toImmutableList()); BatchRefUpdate bru = allUsersRepo.getRefDatabase().newBatchUpdate(); bru.setAtomic(true); bru.addCommand(deleteCommands); RefUpdateUtil.executeChecked(bru, allUsersRepo); } } /** * Accepts a list of draft (zombie) comments for the same change and delete them by executing a * {@link ChangeUpdate} on NoteDb. The update is executed using the user account who created this * draft. */ private void deleteZombieDraftsForChange( Account.Id accountId, ChangeNotes changeNotes, Collection draftsToDelete) throws IOException { if (changeUpdateFactory == null || userFactory == null) { return; } ChangeUpdate changeUpdate = changeUpdateFactory.create(changeNotes, userFactory.create(accountId), TimeUtil.now()); draftsToDelete.forEach(c -> changeUpdate.deleteComment(c)); changeUpdate.commit(); logger.atInfo().log( "Deleted zombie draft comments with UUIDs %s", draftsToDelete.stream().map(d -> d.key.uuid).collect(toImmutableList())); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy