package com.atlassian.bitbucket.mesh.git.gc;

import com.atlassian.bitbucket.mesh.git.GitRepositories;
import com.atlassian.bitbucket.mesh.git.gc.DefaultGarbageCollectionManager.GcBootstrapper;
import com.atlassian.bitbucket.mesh.io.MoreFiles;
import com.atlassian.bitbucket.mesh.process.handler.LoggingStdioHandler;
import com.atlassian.bitbucket.mesh.repository.*;
import com.atlassian.bitbucket.mesh.test.annotation.DirtiesRepo;
import com.atlassian.bitbucket.mesh.test.annotation.Repo;
import com.google.common.collect.ImmutableSet;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import static java.util.Collections.singleton;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.*;

public class DefaultGarbageCollectionManagerForManagedTest extends BaseGarbageCollectionManagerWiredTest {

    @Captor
    private ArgumentCaptor<GcBootstrapper> gcTaskCaptor;
    @Repo(value = GitRepositories.GARBAGE)
    private ManagedRepository managed;

    @Test
    public void testOnObjectsUploadedToSharedObjectsMarksRepositoryDirty() {
        garbageCollectionManager.onObjectsUploadedToSharedObjects(
                new ObjectsUploadedToSharedObjectsEvent(managed));

        Path dirtyMarker = managed.getDirtyMarker();
        assertTrue(Files.isRegularFile(dirtyMarker));
        MoreFiles.deleteQuietly(dirtyMarker);
    }

    @Test
    public void testOnRefsUpdatedEventMarksRepositoryDirty() {
        //Disable the automatic running of the scheduled tasks so we can verify that the repository was marked dirty
        when(executorService.schedule(any(Runnable.class), anyLong(), any(TimeUnit.class)))
                .thenAnswer(invocation -> mock(ScheduledFuture.class));
        garbageCollectionManager.onRefsUpdated(new RefsUpdatedEvent(managed));

        Path dirtyMarker = managed.getDirtyMarker();
        assertTrue(Files.isRegularFile(dirtyMarker));
        MoreFiles.deleteQuietly(dirtyMarker);
    }

    @DirtiesRepo
    @Test
    public void testVacuum() throws IOException {
        //Create some loose objects and packs in the repository
        createFakePacks(managed, 97, false); // 97 regular packs
        createFakePacks(managed, 3, true); // 3 packs protected by .keep files
        boolean createdCruftPack = maybeCreateCruftPack(managed);
        createLooseObjects(managed);
        //Create cruft pack again from cache, while creating the loose objects, the cruft pack was removed from the repo
        maybeCreateCruftPack(managed);
        createStaleTmpPackFile(managed);

        SharedObjectsRepository sharedObjects = managed.getSharedObjects();
        //Prepare some ref logs for testing various states of
        //- reflog exists in shared-objects, missing in repository: dark-refs/pull-requests/100/from
        //- reflog identical between shared-objects and repository: dark-refs/pull-requests/120/from
        //- reflog exists in repository, missing in shared-objects: refs/heads/master
        //- reflog exists in both, but updated in repository: 1234567890/dark-refs/pull-requests/120/merge
        String reflog120From = "0000000000000000000000000000000000000000 d36984a542c2cddb3c29797a11aec1199c7d75d2 " +
                "repo-120-from <repo-120-from@localhost> 1606356625 +0000";
        String reflog120MergeOld = "0000000000000000000000000000000000000000 54ff66a5d38dd99607207883cc3189b6cc17ad47 " +
                "shared-120-merge <shared-120-merge@localhost> 1606356625 +0000";
        String reflog120Merge = reflog120MergeOld +
                "\n54ff66a5d38dd99607207883cc3189b6cc17ad47 d0a1815035030ff854404f0ad99721206846d10b " +
                "repo-120-merge <repo-120-merge@localhost> 1606358926 +0000";
        String reflogMaster = "d0a1815035030ff854404f0ad99721206846d10b d36984a542c2cddb3c29797a11aec1199c7d75d2 " +
                "master <master@localhost> 1606356625 +0000";
        String reflog100From = "0000000000000000000000000000000000000000 d0a1815035030ff854404f0ad99721206846d10b" +
                " repo-100-from <repo-100-from@localhost> 1606356625 +0000";

        createSharedReflog(managed, "dark-refs/pull-requests/100/from", reflog100From);
        createSharedReflog(managed, "dark-refs/pull-requests/120/from", reflog120From);
        createReflog(managed, "dark-refs/pull-requests/120/from", reflog120From);
        createSharedReflog(managed, "dark-refs/pull-requests/120/merge", reflog120MergeOld);
        createReflog(managed, "dark-refs/pull-requests/120/merge", reflog120Merge);
        createReflog(managed, "refs/heads/master", reflogMaster);

        int managedPackFileCountBefore = countPackFiles(managed);
        int sharedObjectsPackFileCountBefore = countPackFiles(managed.getSharedObjects());

        collectGarbage(managed);

        assertEquals(0, countLooseObjects(managed));
        assertEquals(3, countPackFiles(managed));
        maybeVerifyCommitGraph(managed, false);
        assertTrue(hasPackRefsRun(managed));
        assertTrue(hasVacuumingRun(managed));

        //Verify that the pack files that were not protected by .keep files have been vacuumed
        //Account for the extra cruft pack when it was created for Git 2.41+
        assertEquals(sharedObjectsPackFileCountBefore + managedPackFileCountBefore - (createdCruftPack ? 4 : 3),
                countPackFiles(managed.getSharedObjects()));

        //Validate that the repository and the shared-objects repositories are both valid
        builderFactory.builder(managed).command("fsck").build(new LoggingStdioHandler()).run();
        builderFactory.builder(sharedObjects).command("fsck").build(new LoggingStdioHandler()).run();

        //Validate that the refs have been synced to shared-objects
        String repositoryNs = "refs/r/" + managed.getLocalId();
        assertEquals(ImmutableSet.of(repositoryNs + "/refs/heads/master"), getRefs(sharedObjects));

        //Verify that the ref logs have been vacuumed correctly. This will be a full vacuum so ref logs for closed
        //pull requests will be synced and then removed from the managed repository
        assertNull(getReflog(managed, "dark-refs/pull-requests/120/from"));
        assertNull(getReflog(managed, "dark-refs/pull-requests/120/merge"));
        assertEquals(reflogMaster, getReflog(managed, "refs/heads/master"));
        //shared-objects should now contain all ref logs previously in managed.
        assertEquals(reflog100From, getSharedRefLog(managed, "dark-refs/pull-requests/100/from"));
        assertEquals(reflog120From, getSharedRefLog(managed, "dark-refs/pull-requests/120/from"));
        assertEquals(reflog120Merge, getSharedRefLog(managed, "dark-refs/pull-requests/120/merge"));
        assertEquals(reflogMaster, getSharedRefLog(managed, "refs/heads/master"));
    }

    @DirtiesRepo
    @Test
    public void testVacuumConvertsStashRefsReflogsInSharedRepository() throws IOException {
        String reflog120From = "0000000000000000000000000000000000000000 d36984a542c2cddb3c29797a11aec1199c7d75d2 " +
                "repo-120-from <repo-120-from@localhost> 1606356625 +0000";
        String reflog100From = "0000000000000000000000000000000000000000 d0a1815035030ff854404f0ad99721206846d10b" +
                " repo-100-from <repo-100-from@localhost> 1606356625 +0000";

        createSharedReflog(managed, "dark-refs/pull-requests/100/from", reflog100From);
        //This one should get converted to dark-refs
        createSharedReflog(managed, "stash-refs/pull-requests/120/from", reflog120From);

        collectGarbage(managed);

        assertTrue(hasPackRefsRun(managed));
        assertTrue(hasVacuumingRun(managed));

        assertEquals(reflog100From, getSharedRefLog(managed, "dark-refs/pull-requests/100/from"));
        assertEquals(reflog120From, getSharedRefLog(managed, "dark-refs/pull-requests/120/from"));
    }

    @DirtiesRepo
    @Test
    public void testVacuumConvertsStashRefsReflogsInSharedRepositoryAndMergesConflicts() throws IOException {
        String reflog120FromStash = "0000000000000000000000000000000000000000 d36984a542c2cddb3c29797a11aec1199c7d75d2 " +
                "repo-120-from <repo-120-from@localhost> 1606356625 +0000";
        String reflog120FromDark = "0000000000000000000000000000000000000000 d0a1815035030ff854404f0ad99721206846d10b" +
                " repo-120-from <repo-120-from@localhost> 1606356625 +0000";

        //This one should get converted to dark-refs
        createSharedReflog(managed, "stash-refs/pull-requests/120/from", reflog120FromStash);
        createSharedReflog(managed, "dark-refs/pull-requests/120/from", reflog120FromDark);

        collectGarbage(managed);

        assertTrue(hasPackRefsRun(managed));
        assertTrue(hasVacuumingRun(managed));

        assertEquals(reflog120FromDark + "\n" + reflog120FromStash,
                getSharedRefLog(managed, "dark-refs/pull-requests/120/from"));
    }

    @DirtiesRepo
    @Test
    public void testVacuumConvertsStashRefsReflogsWhileMovingThemToSharedRepo() throws IOException {
        String reflog120From = "0000000000000000000000000000000000000000 d36984a542c2cddb3c29797a11aec1199c7d75d2 " +
                "repo-120-from <repo-120-from@localhost> 1606356625 +0000";
        String reflog100From = "0000000000000000000000000000000000000000 d0a1815035030ff854404f0ad99721206846d10b" +
                " repo-100-from <repo-100-from@localhost> 1606356625 +0000";

        //This one should get converted to dark-refs
        createReflog(managed, "stash-refs/pull-requests/120/from", reflog120From);
        createReflog(managed, "stash-refs/pull-requests/100/from", reflog100From);

        collectGarbage(managed);

        assertTrue(hasPackRefsRun(managed));
        assertTrue(hasVacuumingRun(managed));

        assertNull(getReflog(managed, "dark-refs/pull-requests/100/from"));
        assertNull(getReflog(managed, "dark-refs/pull-requests/120/from"));
        assertEquals(reflog100From, getSharedRefLog(managed, "dark-refs/pull-requests/100/from"));
        assertEquals(reflog120From, getSharedRefLog(managed, "dark-refs/pull-requests/120/from"));
    }

    @DirtiesRepo
    @Test
    public void testVacuumLeavesOpenPullRequestReflogsInRepository() throws IOException {
        String reflog120From = "0000000000000000000000000000000000000000 d36984a542c2cddb3c29797a11aec1199c7d75d2 " +
                               "repo-120-from <repo-120-from@localhost> 1606356625 +0000";
        String reflog100From = "0000000000000000000000000000000000000000 d0a1815035030ff854404f0ad99721206846d10b" +
                               " repo-100-from <repo-100-from@localhost> 1606356625 +0000";
        createSharedReflog(managed, "dark-refs/pull-requests/100/from", reflog100From);
        createSharedReflog(managed, "dark-refs/pull-requests/120/from", reflog120From);
        createReflog(managed, "dark-refs/pull-requests/100/from", reflog100From);
        createReflog(managed, "dark-refs/pull-requests/120/from", reflog120From);
        //This pull request ref should keep the associated reflog from being deleted
        createRef(managed, "refs/pull-requests/120/from", "d36984a542c2cddb3c29797a11aec1199c7d75d2");

        collectGarbage(managed);

        assertTrue(hasPackRefsRun(managed));
        assertTrue(hasVacuumingRun(managed));
        //This reflog didn't have an open pull request -> deleted
        assertNull(getReflog(managed, "dark-refs/pull-requests/100/from"));
        //This reflog has an open pull request ref -> kept
        assertEquals(reflog120From, getReflog(managed, "dark-refs/pull-requests/120/from"));
        assertEquals(reflog100From, getSharedRefLog(managed, "dark-refs/pull-requests/100/from"));
        assertEquals(reflog120From, getSharedRefLog(managed, "dark-refs/pull-requests/120/from"));
    }

    @DirtiesRepo
    @Test
    public void testVacuumMergesComplexSortedReflogs() throws IOException {
        String reflog120From1 = "0000000000000000000000000000000000000000 d36984a542c2cddb3c29797a11aec1199c7d75d2 " +
                "repo-120-from <repo-120-from@localhost> 1606356625 +0000";
        String reflog120FromUpdated2 =
                "d36984a542c2cddb3c29797a11aec1199c7d75d2 d0a1815035030ff854404f0ad99721206846d10b " +
                        "repo-120-from <repo-120-from@localhost> 1606356627 +0000";
        String reflog120FromUpdated3 =
                "d0a1815035030ff854404f0ad99721206846d10b d36984a542c2cddb3c29797a11aec1199c7d75d2 " +
                        "repo-120-from <repo-120-from@localhost> 1606356629 +0000";
        String reflog120From4 = "d36984a542c2cddb3c29797a11aec1199c7d75d2 d0a1815035030ff854404f0ad99721206846d10b " +
                "repo-120-from <repo-120-from@localhost> 1606356631 +0000";
        String reflog120FromUpdated5 =
                "d0a1815035030ff854404f0ad99721206846d10b d36984a542c2cddb3c29797a11aec1199c7d75d2 " +
                        "repo-120-from <repo-120-from@localhost> 1606356634 +0000";
        String reflog120From6 = "d36984a542c2cddb3c29797a11aec1199c7d75d2 d0a1815035030ff854404f0ad99721206846d10b " +
                "repo-120-from <repo-120-from@localhost> 1606356640 +0000";

        //Combine shared reflog lines
        String sharedReflogLines = reflog120From1 + "\n" + reflog120From4 + "\n" + reflog120From6;
        createSharedReflog(managed, "dark-refs/pull-requests/120/from", sharedReflogLines);
        //Combine regular reflog lines, containing some of the shared reflog lines too
        String regularReflogLines = reflog120From1 + "\n" + reflog120FromUpdated2 + "\n" + reflog120FromUpdated3 + "\n"
                + reflog120From4 + "\n" + reflog120FromUpdated5;
        createReflog(managed, "dark-refs/pull-requests/120/from", regularReflogLines);

        //Reflog should get moved into shared objects
        collectGarbage(managed);

        assertTrue(hasPackRefsRun(managed));
        assertTrue(hasVacuumingRun(managed));
        String combinedReflogLines = reflog120From1 + "\n" + reflog120FromUpdated2 + "\n" + reflog120FromUpdated3 + "\n"
                + reflog120From4 + "\n" + reflog120FromUpdated5 + "\n" + reflog120From6;
        assertEquals(combinedReflogLines, getSharedRefLog(managed, "dark-refs/pull-requests/120/from"));
    }

    @DirtiesRepo
    @Test
    public void testVacuumMergesReflogOfDeclinedAndReopenedPullRequest() throws IOException {
        //These two lines intentionally have the same timestamp, while rare, it could occur and the first line should
        //come before the second line
        String reflog120From = "0000000000000000000000000000000000000000 d36984a542c2cddb3c29797a11aec1199c7d75d2 " +
                "repo-120-from <repo-120-from@localhost> 1606356625 +0000";
        String reflog120FromUpdated =
                "d36984a542c2cddb3c29797a11aec1199c7d75d2 d0a1815035030ff854404f0ad99721206846d10b " +
                        "repo-120-from <repo-120-from@localhost> 1606356625 +0000";

        //Simulate previously declined and vacuumed reflog
        createSharedReflog(managed, "dark-refs/pull-requests/120/from", reflog120From);
        //Reopen the pull request
        createRef(managed, "refs/pull-requests/120/from", "d0a1815035030ff854404f0ad99721206846d10b");
        //Recreate the reflog with the new update (only contains one refchange)
        createReflog(managed, "dark-refs/pull-requests/120/from", reflog120FromUpdated);

        //Reflog should get moved into shared objects
        collectGarbage(managed);

        assertTrue(hasPackRefsRun(managed));
        assertTrue(hasVacuumingRun(managed));
        //This reflog has an open pull request ref -> kept
        assertEquals(reflog120FromUpdated, getReflog(managed, "dark-refs/pull-requests/120/from"));
        assertEquals(reflog120From + "\n" +
                reflog120FromUpdated, getSharedRefLog(managed, "dark-refs/pull-requests/120/from"));
    }

    @DirtiesRepo
    @Test
    public void testVacuumMergesReflogsNonStandardRefNames() throws IOException {
        String reflogFrom = "0000000000000000000000000000000000000000 d36984a542c2cddb3c29797a11aec1199c7d75d2 " +
                "repo-120-from <repo-120-from@localhost> 1606356625 +0000";
        String reflogFromUpdated = reflogFrom + "\n" +
                "d36984a542c2cddb3c29797a11aec1199c7d75d2 d0a1815035030ff854404f0ad99721206846d10b " +
                "repo-120-from <repo-120-from@localhost> 1606356627 +0000";

        createSharedReflog(managed, "dark-refs/pull-requests/archived", reflogFrom);
        createReflog(managed, "dark-refs/pull-requests/archived", reflogFromUpdated);

        collectGarbage(managed);

        assertTrue(hasPackRefsRun(managed));
        assertTrue(hasVacuumingRun(managed));
        assertEquals(reflogFromUpdated, getReflog(managed, "dark-refs/pull-requests/archived"));
        assertEquals(reflogFromUpdated, getSharedRefLog(managed, "dark-refs/pull-requests/archived"));
    }

    @DirtiesRepo
    @Test
    public void testVacuumMergesReflogsNonStandardRefNames_Long() throws IOException {
        String reflogFrom = "0000000000000000000000000000000000000000 d36984a542c2cddb3c29797a11aec1199c7d75d2 " +
                "repo-120-from <repo-120-from@localhost> 1606356625 +0000";
        String reflogFromUpdated = reflogFrom + "\n" +
                "d36984a542c2cddb3c29797a11aec1199c7d75d2 d0a1815035030ff854404f0ad99721206846d10b " +
                "repo-120-from <repo-120-from@localhost> 1606356627 +0000";

        createSharedReflog(managed, "refs/remotes/origin/release/5.13", reflogFrom);
        createReflog(managed, "refs/remotes/origin/release/5.13", reflogFromUpdated);

        collectGarbage(managed);

        assertTrue(hasPackRefsRun(managed));
        assertTrue(hasVacuumingRun(managed));
        assertEquals(reflogFromUpdated, getReflog(managed, "refs/remotes/origin/release/5.13"));
        assertEquals(reflogFromUpdated, getSharedRefLog(managed, "refs/remotes/origin/release/5.13"));
    }

    @DirtiesRepo
    @Test
    public void testVacuumMergesReflogsNonStandardRefNames_Short() throws IOException {
        String reflogFrom = "0000000000000000000000000000000000000000 d36984a542c2cddb3c29797a11aec1199c7d75d2 " +
                "repo-120-from <repo-120-from@localhost> 1606356625 +0000";
        String reflogFromUpdated = reflogFrom + "\n" +
                "d36984a542c2cddb3c29797a11aec1199c7d75d2 d0a1815035030ff854404f0ad99721206846d10b " +
                "repo-120-from <repo-120-from@localhost> 1606356627 +0000";

        createSharedReflog(managed, "HEAD", reflogFrom);
        createReflog(managed, "HEAD", reflogFromUpdated);

        collectGarbage(managed);

        assertTrue(hasPackRefsRun(managed));
        assertTrue(hasVacuumingRun(managed));
        assertEquals(reflogFromUpdated, getReflog(managed, "HEAD"));
        assertEquals(reflogFromUpdated, getSharedRefLog(managed, "HEAD"));
    }

    @DirtiesRepo
    @Test
    public void testVacuumMergesReflogsOfOpenPullRequests() throws IOException {
        String reflog120From = "0000000000000000000000000000000000000000 d36984a542c2cddb3c29797a11aec1199c7d75d2 " +
                "repo-120-from <repo-120-from@localhost> 1606356625 +0000";
        String reflog120FromUpdated = reflog120From + "\n" +
                "d36984a542c2cddb3c29797a11aec1199c7d75d2 d0a1815035030ff854404f0ad99721206846d10b " +
                "repo-120-from <repo-120-from@localhost> 1606356627 +0000";

        createSharedReflog(managed, "dark-refs/pull-requests/120/from", reflog120From);
        createReflog(managed, "dark-refs/pull-requests/120/from", reflog120FromUpdated);
        //This pull request ref should keep the associated reflog from being deleted
        createRef(managed, "refs/pull-requests/120/from", "d36984a542c2cddb3c29797a11aec1199c7d75d2");

        collectGarbage(managed);

        assertTrue(hasPackRefsRun(managed));
        assertTrue(hasVacuumingRun(managed));
        //This reflog has an open pull request ref -> kept
        assertEquals(reflog120FromUpdated, getReflog(managed, "dark-refs/pull-requests/120/from"));
        assertEquals(reflog120FromUpdated, getSharedRefLog(managed, "dark-refs/pull-requests/120/from"));
    }

    @DirtiesRepo
    @Test
    public void testVacuumMergesUnsortedReflogs() throws IOException {
        String reflog120From1 = "0000000000000000000000000000000000000000 d36984a542c2cddb3c29797a11aec1199c7d75d2 " +
                "repo-120-from <repo-120-from@localhost> 1606356625 +0000";
        String reflog120FromUpdated2 =
                "d36984a542c2cddb3c29797a11aec1199c7d75d2 d0a1815035030ff854404f0ad99721206846d10b " +
                        "repo-120-from <repo-120-from@localhost> 1606356627 +0000";
        String reflog120FromUpdated3 =
                "d0a1815035030ff854404f0ad99721206846d10b d36984a542c2cddb3c29797a11aec1199c7d75d2 " +
                        "repo-120-from <repo-120-from@localhost> 1606356629 +0000";
        String reflog120From4 = "d36984a542c2cddb3c29797a11aec1199c7d75d2 d0a1815035030ff854404f0ad99721206846d10b " +
                "repo-120-from <repo-120-from@localhost> 1606356631 +0000";
        String reflog120FromUpdated5 =
                "d0a1815035030ff854404f0ad99721206846d10b d36984a542c2cddb3c29797a11aec1199c7d75d2 " +
                        "repo-120-from <repo-120-from@localhost> 1606356634 +0000";
        String reflog120From6 = "d36984a542c2cddb3c29797a11aec1199c7d75d2 d0a1815035030ff854404f0ad99721206846d10b " +
                "repo-120-from <repo-120-from@localhost> 1606356640 +0000";

        //Combine shared reflog lines
        String sharedReflogLines = reflog120From6 + "\n" + reflog120From1 + "\n" + reflog120From4 + "\n" + reflog120From4;
        createSharedReflog(managed, "dark-refs/pull-requests/120/from", sharedReflogLines);
        //Combine regular reflog lines, containing some of the shared reflog lines too
        String regularReflogLines = reflog120From6 + "\n" + reflog120FromUpdated3 + "\n" + reflog120FromUpdated2 + "\n"
                + reflog120From4 + "\n" + reflog120FromUpdated5;
        createReflog(managed, "dark-refs/pull-requests/120/from", regularReflogLines);

        //Reflog should get moved into shared objects
        collectGarbage(managed);

        assertTrue(hasPackRefsRun(managed));
        assertTrue(hasVacuumingRun(managed));
        String combinedReflogLines = reflog120From1 + "\n" + reflog120FromUpdated2 + "\n" + reflog120FromUpdated3 + "\n"
                + reflog120From4 + "\n" + reflog120FromUpdated5 + "\n" + reflog120From6;
        assertEquals(combinedReflogLines, getSharedRefLog(managed, "dark-refs/pull-requests/120/from"));
    }

    @Test
    public void testVacuumPrunesOnRepositoryDeletion() throws IOException {
        String fqHierarchy = "p/0012/h/abcdef1234567890abcd/r/";
        String originLocalId = "1111111111";
        String forkLocalId = "2222222222";
        String reflog120From = "0000000000000000000000000000000000000000 d36984a542c2cddb3c29797a11aec1199c7d75d2 " +
                               "repo-120-from <repo-120-from@localhost> 1606356625 +0000";
        String reflogMaster = "d0a1815035030ff854404f0ad99721206846d10b d36984a542c2cddb3c29797a11aec1199c7d75d2 " +
                              "master <master@localhost> 1606356625 +0000";

        //Create a repository and a fork in the same hierarchy and vacuum both
        ManagedRepository origin = createManagedRepository(fqHierarchy + originLocalId, managed);
        ManagedRepository fork = createManagedRepository(fqHierarchy + forkLocalId, origin);
        SharedObjectsRepository sharedObjects = origin.getSharedObjects();
        createReflog(origin, "dark-refs/pull-requests/120/from", reflog120From);
        createReflog(fork, "refs/heads/master", reflogMaster);

        collectGarbage(origin);

        //The refs and ref logs from origin and fork should now be vacuumed
        assertEquals(ImmutableSet.of(
                "refs/r/" + originLocalId + "/refs/heads/master",
                "refs/r/" + forkLocalId + "/refs/heads/master"),
                getRefs(sharedObjects));
        assertEquals(reflog120From, getSharedRefLog(origin, "dark-refs/pull-requests/120/from"));
        assertEquals(reflogMaster, getSharedRefLog(fork, "refs/heads/master"));

        repositoryManager.delete(new DeleteRepositoryParameters.Builder(origin)
                .forks(singleton(fork))
                .build());
        garbageCollectionManager.onRepositoryDeleted(new RepositoryDeletedEvent(origin));

        //Verify that the refs and ref logs from origin are now removed
        assertEquals(ImmutableSet.of("refs/r/" + forkLocalId + "/refs/heads/master"), getRefs(sharedObjects));
        assertNull(getSharedRefLog(origin, "dark-refs/pull-requests/120/from"));
        assertEquals(reflogMaster, getSharedRefLog(fork, "refs/heads/master"));

        //Validate that the repositories are still valid
        builderFactory.builder(fork).command("fsck").build(new LoggingStdioHandler()).run();
        builderFactory.builder(sharedObjects).command("fsck").build(new LoggingStdioHandler()).run();
    }

    @DirtiesRepo
    @Test
    public void testVacuumingRunsWhenPackRefsRecentlyRun() throws IOException {
        mockPackRefsRun(managed, System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(1L));

        garbageCollectionManager.collectGarbage(new GarbageCollectionParameters.Builder(repository)
                .added(1)
                .build()
        );

        //GC should be skipped for the managed repository, but still run for the shared-objects repository (vacuuming
        //followed by a standard gc)
        verify(executorService, atLeastOnce()).schedule(gcTaskCaptor.capture(), anyLong(), any());

        for (GcBootstrapper task : gcTaskCaptor.getAllValues()) {
            assertSame(managed.getSharedObjects(), task.getRepository());
        }
    }

    @DirtiesRepo
    @Test
    public void testVacuumingSkippedIfRecentlyRun() throws IOException {
        mockVacuumingRun(managed.getSharedObjects(), System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(1L));

        garbageCollectionManager.collectGarbage(new GarbageCollectionParameters.Builder(repository)
                .added(1)
                .build()
        );

        //GC should be scheduled for the managed repository, but not for the shared-objects repository
        verify(executorService, times(1)).schedule(gcTaskCaptor.capture(), anyLong(), any());

        GcBootstrapper task = gcTaskCaptor.getValue();
        assertSame(managed, task.getRepository());
    }

    @Override
    protected Repository getRepositoryThatShouldBeRepacked() {
        return managed.getSharedObjects();
    }

    @Override
    protected Repository getRepositoryUnderTest() {
        return managed;
    }

    private void mockVacuumingRun(SharedObjectsRepository repository, long timestamp) throws IOException {
        setLastModified(getVacuumMarker(repository), timestamp);
    }
}
