132 lines
5.1 KiB
Java
132 lines
5.1 KiB
Java
// 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 com.google.common.collect.Iterables;
|
|
import com.google.common.flogger.FluentLogger;
|
|
import com.google.gerrit.reviewdb.client.Change;
|
|
import com.google.gerrit.server.config.AllUsersName;
|
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
|
import com.google.gerrit.server.update.RefUpdateUtil;
|
|
import com.google.inject.Inject;
|
|
import com.google.inject.assistedinject.Assisted;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
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 refs. More context in <a
|
|
* href="https://gerrit-review.googlesource.com/c/gerrit/+/246233">
|
|
* https://gerrit-review.googlesource.com/c/gerrit/+/246233 </a>
|
|
*
|
|
* <p>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.
|
|
*/
|
|
public class DeleteZombieCommentsRefs {
|
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
|
|
|
private static final String EMPTY_TREE_ID = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
private static final String DRAFT_REFS_PREFIX = "refs/draft-comments";
|
|
private static final int CHUNK_SIZE = 100; // log progress after deleting every CHUNK_SIZE refs
|
|
|
|
private final GitRepositoryManager repoManager;
|
|
private final AllUsersName allUsers;
|
|
private final int cleanupPercentage;
|
|
private Repository allUsersRepo;
|
|
|
|
public interface Factory {
|
|
DeleteZombieCommentsRefs create(int cleanupPercentage);
|
|
}
|
|
|
|
@Inject
|
|
public DeleteZombieCommentsRefs(
|
|
AllUsersName allUsers,
|
|
GitRepositoryManager repoManager,
|
|
@Assisted Integer cleanupPercentage) {
|
|
this.allUsers = allUsers;
|
|
this.repoManager = repoManager;
|
|
this.cleanupPercentage = (cleanupPercentage == null) ? 100 : cleanupPercentage;
|
|
}
|
|
|
|
public void execute() throws IOException {
|
|
allUsersRepo = repoManager.openRepository(allUsers);
|
|
|
|
List<Ref> draftRefs = allUsersRepo.getRefDatabase().getRefsByPrefix(DRAFT_REFS_PREFIX);
|
|
List<Ref> zombieRefs = filterZombieRefs(draftRefs);
|
|
|
|
logger.atInfo().log(
|
|
"Found a total of %d zombie draft refs in %s repo.", zombieRefs.size(), allUsers.get());
|
|
|
|
logger.atInfo().log("Cleanup percentage = %d", cleanupPercentage);
|
|
zombieRefs =
|
|
zombieRefs.stream()
|
|
.filter(ref -> Change.Id.fromAllUsersRef(ref.getName()).get() % 100 < cleanupPercentage)
|
|
.collect(toImmutableList());
|
|
logger.atInfo().log("Number of zombie refs to be cleaned = %d", zombieRefs.size());
|
|
|
|
long zombieRefsCnt = zombieRefs.size();
|
|
long deletedRefsCnt = 0;
|
|
long startTime = System.currentTimeMillis();
|
|
|
|
for (List<Ref> refsBatch : Iterables.partition(zombieRefs, CHUNK_SIZE)) {
|
|
deleteBatchZombieRefs(refsBatch);
|
|
long elapsed = (System.currentTimeMillis() - startTime) / 1000;
|
|
deletedRefsCnt += refsBatch.size();
|
|
logProgress(deletedRefsCnt, zombieRefsCnt, elapsed);
|
|
}
|
|
}
|
|
|
|
private void deleteBatchZombieRefs(List<Ref> refsBatch) throws IOException {
|
|
List<ReceiveCommand> 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);
|
|
}
|
|
|
|
private List<Ref> filterZombieRefs(List<Ref> allDraftRefs) throws IOException {
|
|
List<Ref> zombieRefs = new ArrayList<>((int) (allDraftRefs.size() * 0.5));
|
|
for (Ref ref : allDraftRefs) {
|
|
if (isZombieRef(ref)) {
|
|
zombieRefs.add(ref);
|
|
}
|
|
}
|
|
return zombieRefs;
|
|
}
|
|
|
|
private boolean isZombieRef(Ref ref) throws IOException {
|
|
return allUsersRepo.parseCommit(ref.getObjectId()).getTree().getName().equals(EMPTY_TREE_ID);
|
|
}
|
|
|
|
private void logProgress(long deletedRefsCount, long allRefsCount, long elapsed) {
|
|
logger.atInfo().log(
|
|
"Deleted %d/%d zombie draft refs (%d seconds)\n", deletedRefsCount, allRefsCount, elapsed);
|
|
}
|
|
}
|