package com.atlassian.stash.internal.scm.git.command.log;

import com.atlassian.bitbucket.commit.Commit;
import com.atlassian.bitbucket.commit.MinimalCommit;
import com.atlassian.bitbucket.commit.NoSuchCommitException;
import com.atlassian.bitbucket.content.NoSuchPathException;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.Command;
import com.atlassian.bitbucket.scm.CommandUsageException;
import com.atlassian.bitbucket.scm.CommitsCommandParameters;
import com.atlassian.bitbucket.scm.annotation.DirtiesRepo;
import com.atlassian.bitbucket.scm.annotation.Repo;
import com.atlassian.bitbucket.scm.git.GitScmConfig;
import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory;
import com.atlassian.bitbucket.scm.git.command.log.GitLogBuilder;
import com.atlassian.bitbucket.scm.git.command.revlist.GitRevCoreBuilder;
import com.atlassian.bitbucket.scm.git.command.revlist.GitRevListMerges;
import com.atlassian.bitbucket.scm.git.command.revlist.GitRevListWalk;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageUtils;
import com.atlassian.bitbucket.util.Version;
import com.atlassian.stash.internal.repository.InternalRepository;
import com.atlassian.stash.internal.scm.git.AbstractGitTest;
import com.atlassian.stash.internal.scm.git.GitFactoriesTestHelper;
import com.atlassian.stash.internal.scm.git.command.revlist.*;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Queue;
import java.util.concurrent.TimeUnit;

import static org.junit.Assume.assumeFalse;

/**
 * Generally parallels {@link RevListCommandTest}, since {@code git log} covers most of {@code git rev-list}'s
 * functionality. In addition to testing {@code log}-specific functionality, like the ability to follow renames,
 * the {@link GitLogBuilder#format formats} used are different.
 * <p>
 * Why test all of this essentially twice? Because {@link GitLogBuilder} is a {@link GitRevCoreBuilder}, so it
 * exposes all of this functionality. This test verifies it does so correctly.
 */
public class LogCommandTest extends AbstractGitTest {

    @Autowired
    private GitCommandBuilderFactory builderFactory;
    @Autowired
    private GitScmConfig config;
    @Autowired
    private I18nService i18nService;
    @Repo("classpath:git/merge-commits.zip")
    private InternalRepository mergeCommits;
    @Repo
    private Repository repository;

    /**
     * Ensures {@link GitLogBuilder#rev} can still be used with {@link GitLogBuilder#all}. In this case we're
     * ensuring we can specify an exclude, but there are reasons you might want to include things even if
     * {@code --all} is being passed. One example might be to include one or more unreachable SHAs while also
     * including all refs as well.
     */
    @Test
    public void testAllWithRevs() {
        RevListOutputHandler<Commit> outputHandler = new SingleCommitRevListOutputHandler(i18nService, repository, "d6edcbf", null);

        Commit commit = createBuilder()
                .all(true)
                .format(revListToLogFormat(outputHandler))
                .rev("^master")
                .build(outputHandler)
                .call();
        assertNotNull(commit);
        assertEquals("d6edcbf924697ab811a867421dab60d954ccad99", commit.getId());
    }

    @Test(expected = NoSuchCommitException.class)
    public void testAmbiguousCommit() {
        createBuilder()
                .rev("FOO")
                .build(new SingleCommitRevListOutputHandler(i18nService, repository, "FOO", null))
                .call();
    }

    @Test
    public void testBranches() throws Exception {
        CollectingCommitCallback callback = new CollectingCommitCallback();
        CallbackRevListOutputHandler outputHandler = new CallbackRevListOutputHandler(repository, callback);

        createBuilder()
                .branches(true)
                .format(revListToLogFormat(outputHandler))
                .walk(GitRevListWalk.NO_WALK)
                .build(outputHandler)
                .call();
        assertEquals(4, callback.commits.size());
        assertEquals("0a943a29376f2336b78312d99e65da17048951db", callback.commits.get(0).getId());
        assertEquals("6053a1eaa1c009dd11092d09a72f3c41af1b59ad", callback.commits.get(1).getId());
        assertEquals("d6edcbf924697ab811a867421dab60d954ccad99", callback.commits.get(2).getId());
        assertEquals("2e10dd2d1d5eea9291b296e78312e8a703964a95", callback.commits.get(3).getId());
    }

    @DirtiesRepo
    @Test
    public void testBranchesDoesNotIncludeHead() {
        //First, set HEAD to a nonexistent ref. If HEAD is included in the rev-list arguments by the builder,
        //this will cause the command to fail
        builderFactory.builder(repository).symbolicRef()
                .set("HEAD", "refs/heads/nonexistent")
                .build()
                .call();

        Queue<String> commitIds = createBuilder()
                .branches(true)
                .format("%H")
                .build(new GitFactoriesTestHelper.QueueHandler())
                .call();
        assertNotNull(commitIds);
        assertTrue(commitIds.contains("d6edcbf924697ab811a867421dab60d954ccad99"));  //basic_branching
        assertTrue(commitIds.contains("6053a1eaa1c009dd11092d09a72f3c41af1b59ad"));  //branch_mod_merge
        assertTrue(commitIds.contains("0a943a29376f2336b78312d99e65da17048951db"));  //master
        assertTrue(commitIds.contains("2e10dd2d1d5eea9291b296e78312e8a703964a95"));  //out_of_order_branch
        assertEquals(21, commitIds.size());
    }

    /**
     * When no refs are specified, the builder should automatically add "HEAD"
     */
    @Test
    public void testBuilderWithNoRefs() {
        RevListOutputHandler<Commit> outputHandler = new SingleCommitRevListOutputHandler(i18nService, repository, "HEAD", null);

        Commit commit = createBuilder()
                .format(revListToLogFormat(outputHandler))
                .limit(1)
                .build(outputHandler)
                .call();
        assertNotNull(commit);
        assertEquals("0a943a29376f2336b78312d99e65da17048951db", commit.getId());
    }

    @Test
    public void testCommitsBetween() {
        CollectingCommitCallback callback = new CollectingCommitCallback();
        RevListOutputHandler<Void> outputHandler = new CallbackRevListOutputHandler(repository, callback);

        Command<Void> command = createBuilder()
                .format(revListToLogFormat(outputHandler))
                .inputHandler(RevListInputHandler.forRefs(
                        Collections.singleton("2d8897c9ac29ce42c3442cf80ac977057045e7f6"),
                        Collections.singleton("6053a1eaa1c009dd11092d09a72f3c41af1b59ad")))
                .ignoreMissing(true)
                .build(outputHandler);
        command.call();
        assertEquals(2, callback.commits.size());

        // test 1 commit fully to ensure that output parsing and commit construction is correct
        Commit commit = callback.commits.get(0);
        assertCommit(commit, "Anna Buttfield", "abuttfield@cascade.sydney.atlassian.com",
                new Date(1296709182000L), "2d8897c9ac2", "2d8897c9ac29ce42c3442cf80ac977057045e7f6",
                "Rename A.zip to B.zip", "9c05f43f859", "9c05f43f859375e392d90d23a13717c16d0fdcda");
        assertEquals(repository, commit.getRepository());

        // now check the ids on all the others
        assertEquals("9c05f43f859375e392d90d23a13717c16d0fdcda", callback.commits.get(1).getId());
    }

    @Test
    public void testCommitsBetweenWithoutMessages() {
        CollectingCommitCallback callback = new CollectingCommitCallback();
        CommitsCommandParameters parameters = new CommitsCommandParameters.Builder().withMessages(false).build();
        RevListOutputHandler<Void> outputHandler = new CallbackRevListOutputHandler(repository, callback, parameters);

        Command<Void> command = createBuilder()
                .format(revListToLogFormat(outputHandler))
                .inputHandler(RevListInputHandler.forRefs(
                        Collections.singleton("2d8897c9ac29ce42c3442cf80ac977057045e7f6"),
                        Collections.singleton("6053a1eaa1c009dd11092d09a72f3c41af1b59ad")))
                .ignoreMissing(true)
                .build(outputHandler);
        command.call();
        assertEquals(2, callback.commits.size());

        // test 1 commit fully to ensure that output parsing and commit construction is correct
        Commit commit = callback.commits.get(0);
        assertCommit(commit, "Anna Buttfield", "abuttfield@cascade.sydney.atlassian.com",
                new Date(1296709182000L), "2d8897c9ac2", "2d8897c9ac29ce42c3442cf80ac977057045e7f6",
                null, "9c05f43f859", "9c05f43f859375e392d90d23a13717c16d0fdcda");
        assertEquals(repository, commit.getRepository());

        // now check the ids on all the others
        assertEquals("9c05f43f859375e392d90d23a13717c16d0fdcda", callback.commits.get(1).getId());
    }

    @Test
    public void testCommitsForPath() {
        GitLogBuilder builder = createBuilder().file("modification/mod_file.txt").rev("HEAD");
        CommitsCommandParameters parameters = new CommitsCommandParameters.Builder().withMessages(true).build();
        RevListOutputHandler<Page<Commit>> outputHandler =
                new PagedCommitRevListOutputHandler(repository, parameters, PageUtils.newRequest(0, 1));

        Page<Commit> page = builder
                .format(revListToLogFormat(outputHandler))
                .build(outputHandler)
                .call();
        assertNotNull(page);
        assertEquals(1, page.getSize());
        assertFalse(page.getIsLastPage());

        assertCommit(page.getValues().iterator().next(), "Michael Heemskerk", "mheemskerk@atlassian.com",
                new Date(TimeUnit.SECONDS.toMillis(1292816091)), "bf3346620ec",
                "bf3346620ec9161977fc23dee1240a7991e64ca1", "edited modification/mod_file.txt", "e26cab95a50",
                "e26cab95a50f6e99d2c46c498a23cee307cc8e27");


        page = builder.build(new PagedCommitRevListOutputHandler(repository, parameters, page.getNextPageRequest())).call();
        assertNotNull(page);
        assertEquals(1, page.getSize());
        assertTrue(page.getIsLastPage());

        assertCommit(page.getValues().iterator().next(), "Michael Heemskerk", "mheemskerk@atlassian.com",
                new Date(TimeUnit.SECONDS.toMillis(1292816091)), "e26cab95a50",
                "e26cab95a50f6e99d2c46c498a23cee307cc8e27", "plain modification, initial add", "2e10dd2d1d5",
                "2e10dd2d1d5eea9291b296e78312e8a703964a95");
    }

    @Test
    public void testCommitsForPathWithoutMessages() {
        GitLogBuilder builder = createBuilder().file("modification/mod_file.txt").rev("HEAD");
        CommitsCommandParameters parameters = new CommitsCommandParameters.Builder().withMessages(false).build();
        RevListOutputHandler<Page<Commit>> outputHandler =
                new PagedCommitRevListOutputHandler(repository, parameters, PageUtils.newRequest(0, 1));

        Page<Commit> page = builder
                .format(revListToLogFormat(outputHandler))
                .build(outputHandler)
                .call();
        assertNotNull(page);
        assertEquals(1, page.getSize());
        assertFalse(page.getIsLastPage());

        assertCommit(page.getValues().iterator().next(), "Michael Heemskerk", "mheemskerk@atlassian.com",
                new Date(TimeUnit.SECONDS.toMillis(1292816091)), "bf3346620ec",
                "bf3346620ec9161977fc23dee1240a7991e64ca1", null, "e26cab95a50", "e26cab95a50f6e99d2c46c498a23cee307cc8e27");


        page = builder.build(new PagedCommitRevListOutputHandler(repository, parameters, page.getNextPageRequest())).call();
        assertNotNull(page);
        assertEquals(1, page.getSize());
        assertTrue(page.getIsLastPage());

        assertCommit(page.getValues().iterator().next(), "Michael Heemskerk", "mheemskerk@atlassian.com",
                new Date(TimeUnit.SECONDS.toMillis(1292816091)), "e26cab95a50",
                "e26cab95a50f6e99d2c46c498a23cee307cc8e27", null, "2e10dd2d1d5", "2e10dd2d1d5eea9291b296e78312e8a703964a95");
    }

    @Test
    public void testCommitsSince() {
        CollectingCommitCallback callback = new CollectingCommitCallback();
        RevListOutputHandler<Void> outputHandler = new CallbackRevListOutputHandler(repository, callback);

        Command<Void> command = createBuilder()
                .all(true)
                .format(revListToLogFormat(outputHandler))
                .ignoreMissing(true)
                .inputHandler(RevListInputHandler.excludingRefs(Collections.singleton("6053a1eaa1c009dd11092d09a72f3c41af1b59ad")))
                .build(outputHandler);
        command.call();
        assertEquals(5, callback.commits.size());

        Commit commit = callback.commits.get(0);
        assertCommit(commit, "Anna Buttfield", "abuttfield@cascade.sydney.atlassian.com",
                new Date(1296709267000L), "0a943a29376", "0a943a29376f2336b78312d99e65da17048951db",
                "Copy C.zip as D.zip", "e0a3f6d8c80", "e0a3f6d8c804c375f49d7609328111e8dc75a4bc");
        assertEquals(repository, commit.getRepository());

        // now check the ids on all the others
        assertEquals("e0a3f6d8c804c375f49d7609328111e8dc75a4bc", callback.commits.get(1).getId());
        assertEquals("2d8897c9ac29ce42c3442cf80ac977057045e7f6", callback.commits.get(2).getId());
        assertEquals("9c05f43f859375e392d90d23a13717c16d0fdcda", callback.commits.get(3).getId());
        assertEquals("d6edcbf924697ab811a867421dab60d954ccad99", callback.commits.get(4).getId());
    }

    @Test
    public void testExcludeMergeCommits() {
        CollectingCommitCallback callback = new CollectingCommitCallback();
        CallbackRevListOutputHandler outputHandler = new CallbackRevListOutputHandler(repository, callback);

        builderFactory.builder(mergeCommits).log()
                .all(true)
                .format(revListToLogFormat(outputHandler))
                .merges(GitRevListMerges.EXCLUDE)
                .build(outputHandler)
                .call();
        assertEquals(7, callback.commits.size());
        assertEquals("8dd91d1f6d846ec80045699d7ad1ee85395e22ef", callback.commits.get(0).getId());
        assertEquals("2061acf612f3b1f4ac6d50e99688f41fbbb6c59c", callback.commits.get(1).getId());
        assertEquals("88221b3b195a998a4bf3aecbdb02f9efd69103ae", callback.commits.get(2).getId());
        assertEquals("2d8b41bf8c4a216177b5303f845c226232f454f0", callback.commits.get(3).getId());
        assertEquals("af7ad1f16d32ad11f44bbad1236808d405eb48f9", callback.commits.get(4).getId());
        assertEquals("29646867dfdedb2b139c13b1f383d8f6134a3624", callback.commits.get(5).getId());
        assertEquals("274e479b517365266ed43bf83678b28c2862f2b5", callback.commits.get(6).getId());
    }

    @Test
    public void testFirstCommit() {
        RevListOutputHandler<Commit> outputHandler = new SingleCommitRevListOutputHandler(i18nService,
                repository, "042fb0cc198e7320b59a025689b45a2cb52f4684", null);

        Command<Commit> command = createBuilder()
                .format(revListToLogFormat(outputHandler))
                .limit(1)
                .rev("042fb0cc198e7320b59a025689b45a2cb52f4684")
                .build(outputHandler);
        Commit commit = command.call();
        assertNotNull(commit);
        assertCommit(commit, "Michael Heemskerk", "mheemskerk@atlassian.com",
                new Date(TimeUnit.SECONDS.toMillis(1292816475)), "042fb0cc198", "042fb0cc198e7320b59a025689b45a2cb52f4684",
                "branch, mod and merge scenario", "8fb8538799b", "8fb8538799b2481750eac52716931d895d029b7c");
    }

    @Test(expected = NoSuchCommitException.class)
    public void testFirstCommitForNonexistentCommitId() {
        createBuilder()
                .limit(1)
                .rev("042fb0cc198e7320b59a025689b45a2cb52f4683")
                .build(new SingleCommitRevListOutputHandler(i18nService, repository, "042fb0cc198e7320b59a025689b45a2cb52f4683", null))
                .call();
    }

    @Test(expected = NoSuchPathException.class)
    public void testFirstCommitForNonexistentCommitIdIgnoreMissing() {
        createBuilder()
                .ignoreMissing(true)
                .limit(1)
                .rev("042fb0cc198e7320b59a025689b45a2cb52f4683")
                .build(new SingleCommitRevListOutputHandler(i18nService, repository, "042fb0cc198e7320b59a025689b45a2cb52f4683", null))
                .call();
    }

    @Test(expected = NoSuchPathException.class)
    public void testFirstCommitForNonexistentCommitIdIgnoreMissingExitHandler() {
        createBuilder()
                .exitHandler(new LogCommandExitHandler(i18nService, repository, Collections.emptySet(),
                        Collections.emptySet(), true))
                .ignoreMissing(true)
                .limit(1)
                .rev("042fb0cc198e7320b59a025689b45a2cb52f4683")
                .build(new SingleCommitRevListOutputHandler(i18nService, repository,
                        "042fb0cc198e7320b59a025689b45a2cb52f4683", null))
                .call();
    }

    @Test(expected = NoSuchCommitException.class)
    public void testFirstCommitForNonexistentCommitIdExitHandler() {
        createBuilder()
                .exitHandler(new LogCommandExitHandler(i18nService, repository, Collections.emptySet(),
                        Collections.emptySet(), true))
                .limit(1)
                .rev("042fb0cc198e7320b59a025689b45a2cb52f4683")
                .build(new SingleCommitRevListOutputHandler(i18nService, repository,
                        "042fb0cc198e7320b59a025689b45a2cb52f4683", null))
                .call();
    }

    @Test(expected = NoSuchPathException.class)
    public void testFirstCommitForNonexistentPath() {
        createBuilder()
                .file("modification/mod_file2.txt")
                .limit(1)
                .rev("HEAD")
                .build(new SingleCommitRevListOutputHandler(i18nService, repository, "HEAD", "modification/mod_file2.txt"))
                .call();
    }

    @Test
    public void testFirstCommitForPath() {
        RevListOutputHandler<Commit> outputHandler = new SingleCommitRevListOutputHandler(i18nService, repository, "HEAD", "modification/mod_file.txt");
        Commit commit = createBuilder()
                .file("modification/mod_file.txt")
                .format(revListToLogFormat(outputHandler))
                .limit(1)
                .rev("HEAD")
                .build(outputHandler)
                .call();
        assertNotNull(commit);
        assertCommit(commit, "Michael Heemskerk", "mheemskerk@atlassian.com",
                new Date(TimeUnit.SECONDS.toMillis(1292816091)), "bf3346620ec", "bf3346620ec9161977fc23dee1240a7991e64ca1",
                "edited modification/mod_file.txt", "e26cab95a50", "e26cab95a50f6e99d2c46c498a23cee307cc8e27");
    }

    @Test(expected = NoSuchCommitException.class)
    public void testFirstCommitForPathWithInvalidCommitId() {
        createBuilder()
                .file("modification/mod_file.txt")
                .limit(1)
                .rev("invalid")
                .build(new SingleCommitRevListOutputHandler(i18nService, repository, "invalid", "modification/mod_file.txt"))
                .call();
    }

    @Test
    public void testFirstCommitForRootCommit() {
        RevListOutputHandler<Commit> outputHandler = new SingleCommitRevListOutputHandler(i18nService, repository, "409a2180fd95b744df8d817e770b7f8c853f78dc", null);
        Command<Commit> command = createBuilder()
                .format(revListToLogFormat(outputHandler))
                .rev("409a2180fd95b744df8d817e770b7f8c853f78dc")
                .build(outputHandler);
        Commit commit = command.call();
        assertNotNull(commit);
        assertCommit(commit, "Michael Heemskerk", "mheemskerk@atlassian.com",
                new Date(TimeUnit.SECONDS.toMillis(1263078610)), "409a2180fd9", "409a2180fd95b744df8d817e770b7f8c853f78dc",
                "out of order commits scenario, initial commit");

        Collection<MinimalCommit> parents = commit.getParents();
        assertNotNull(parents);
        assertTrue(parents.isEmpty());
    }

    @Test
    public void testFollowingCopy() {
        Queue<String> commitIds = createBuilder()
                .file("binary/D.zip")
                .follow(true)
                .format("%H")
                .build(new GitFactoriesTestHelper.QueueHandler())
                .call();
        assertNotNull(commitIds);
        assertEquals("0a943a29376f2336b78312d99e65da17048951db", commitIds.poll()); //Copy from C to D
        assertEquals("e0a3f6d8c804c375f49d7609328111e8dc75a4bc", commitIds.poll()); //Adding C
    }

    @Test
    public void testFollowingEnabledViaConfiguration() {
        assumeFalse("log.follow was added in Git 2.6.0", config.getVersion().compareTo(new Version(2, 6)) < 0);

        Queue<String> commitIds = createBuilder()
                .file("binary/B.zip")
                .format("%H")
                .withConfiguration("log.follow")
                .build(new GitFactoriesTestHelper.QueueHandler())
                .call();
        assertNotNull(commitIds);
        assertEquals("2d8897c9ac29ce42c3442cf80ac977057045e7f6", commitIds.poll()); //Rename from A to B
        assertEquals("9c05f43f859375e392d90d23a13717c16d0fdcda", commitIds.poll()); //Adding A
    }

    @Test
    public void testFollowingRename() {
        Queue<String> commitIds = createBuilder()
                .file("binary/B.zip")
                .follow(true)
                .format("%H")
                .build(new GitFactoriesTestHelper.QueueHandler())
                .call();
        assertNotNull(commitIds);
        assertEquals("2d8897c9ac29ce42c3442cf80ac977057045e7f6", commitIds.poll()); //Rename from A to B
        assertEquals("9c05f43f859375e392d90d23a13717c16d0fdcda", commitIds.poll()); //Adding A
    }

    @Test(expected = CommandUsageException.class)
    public void testFollowTwoPaths() {
        createBuilder()
                .files("binary/B.zip", "binary/D.zip")
                .follow(true)
                .build(new GitFactoriesTestHelper.NoOpHandler())
                .call();
    }

    @Test(expected = CommandUsageException.class)
    public void testFollowWithoutPath() {
        createBuilder()
                .follow(true)
                .build(new GitFactoriesTestHelper.NoOpHandler())
                .call();
    }

    @Test
    public void testIncludeMergeCommits() {
        CollectingCommitCallback callback = new CollectingCommitCallback();
        CallbackRevListOutputHandler outputHandler = new CallbackRevListOutputHandler(repository, callback);

        builderFactory.builder(mergeCommits).log()
                .all(true)
                .format(revListToLogFormat(outputHandler))
                .merges(GitRevListMerges.INCLUDE)
                .build(outputHandler)
                .call();
        assertEquals(12, callback.commits.size());
        assertEquals("e1c0ebbac1079a14b14fb05e67339ffbcca1894c", callback.commits.get(0).getId());
        assertEquals("8dd91d1f6d846ec80045699d7ad1ee85395e22ef", callback.commits.get(1).getId());
        assertEquals("2061acf612f3b1f4ac6d50e99688f41fbbb6c59c", callback.commits.get(2).getId());
        assertEquals("d62cf48ad69b3b6957ab4d5ffdb76a24a2e2d1e6", callback.commits.get(3).getId());
        assertEquals("6d7f1f78419d3dda0e5374d4d6b630e8fc7902ac", callback.commits.get(4).getId());
        assertEquals("4856ffa5289244f02c017100d0d24d92d8df7888", callback.commits.get(5).getId());
        assertEquals("d73fb0b2b117b3f92f9b0160d413b9d8299e2034", callback.commits.get(6).getId());
        assertEquals("88221b3b195a998a4bf3aecbdb02f9efd69103ae", callback.commits.get(7).getId());
        assertEquals("2d8b41bf8c4a216177b5303f845c226232f454f0", callback.commits.get(8).getId());
        assertEquals("af7ad1f16d32ad11f44bbad1236808d405eb48f9", callback.commits.get(9).getId());
        assertEquals("29646867dfdedb2b139c13b1f383d8f6134a3624", callback.commits.get(10).getId());
        assertEquals("274e479b517365266ed43bf83678b28c2862f2b5", callback.commits.get(11).getId());
    }

    @Test
    public void testNoWalk() {
        CollectingCommitCallback callback = new CollectingCommitCallback();
        CallbackRevListOutputHandler outputHandler = new CallbackRevListOutputHandler(repository, callback);

        createBuilder()
                .all(true)
                .format(revListToLogFormat(outputHandler))
                .walk(GitRevListWalk.NO_WALK)
                .build(outputHandler)
                .call();
        assertEquals(5, callback.commits.size());
        assertEquals("0a943a29376f2336b78312d99e65da17048951db", callback.commits.get(0).getId());
        assertEquals("6053a1eaa1c009dd11092d09a72f3c41af1b59ad", callback.commits.get(1).getId());
        assertEquals("d6edcbf924697ab811a867421dab60d954ccad99", callback.commits.get(2).getId());
        assertEquals("57150c54c38d6570b2fd5e6d6cfc19476de44e84", callback.commits.get(3).getId());
        assertEquals("2e10dd2d1d5eea9291b296e78312e8a703964a95", callback.commits.get(4).getId());
    }

    @Test
    public void testOnlyMergeCommits() {
        CollectingCommitCallback callback = new CollectingCommitCallback();
        CallbackRevListOutputHandler outputHandler = new CallbackRevListOutputHandler(repository, callback);

        builderFactory.builder(mergeCommits).log()
                .all(true)
                .format(revListToLogFormat(outputHandler))
                .merges(GitRevListMerges.ONLY)
                .build(outputHandler)
                .call();
        assertEquals(5, callback.commits.size());
        assertEquals("e1c0ebbac1079a14b14fb05e67339ffbcca1894c", callback.commits.get(0).getId());
        assertEquals("d62cf48ad69b3b6957ab4d5ffdb76a24a2e2d1e6", callback.commits.get(1).getId());
        assertEquals("6d7f1f78419d3dda0e5374d4d6b630e8fc7902ac", callback.commits.get(2).getId());
        assertEquals("4856ffa5289244f02c017100d0d24d92d8df7888", callback.commits.get(3).getId());
        assertEquals("d73fb0b2b117b3f92f9b0160d413b9d8299e2034", callback.commits.get(4).getId());
    }

    @Test
    public void testStdInWithRevs() {
        Queue<String> commitIds = createBuilder()
                .format("%H")
                .inputHandler(new RevListInputHandler(Collections.singleton("master")))
                .revs("basic_branching", "backdated_annotated_tag")
                .walk(GitRevListWalk.NO_WALK)
                .build(new GitFactoriesTestHelper.QueueHandler())
                .call();
        assertNotNull(commitIds);
        assertEquals("0a943a29376f2336b78312d99e65da17048951db", commitIds.poll()); //master, via stdin
        assertEquals("d6edcbf924697ab811a867421dab60d954ccad99", commitIds.poll()); //basic_branching, via cli
        assertEquals("57150c54c38d6570b2fd5e6d6cfc19476de44e84", commitIds.poll()); //backdated_annotated_tag, via cli
        assertTrue(commitIds.isEmpty());
    }

    @Test
    public void testTags() throws Exception {
        CollectingCommitCallback callback = new CollectingCommitCallback();
        CallbackRevListOutputHandler outputHandler = new CallbackRevListOutputHandler(repository, callback);

        createBuilder()
                .format(revListToLogFormat(outputHandler))
                .tags(true)
                .walk(GitRevListWalk.NO_WALK)
                .build(outputHandler)
                .call();
        assertEquals(2, callback.commits.size());
        assertEquals("0a943a29376f2336b78312d99e65da17048951db", callback.commits.get(0).getId());
        assertEquals("57150c54c38d6570b2fd5e6d6cfc19476de44e84", callback.commits.get(1).getId());
    }

    @DirtiesRepo
    @Test
    public void testTagsDoesNotIncludeHead() {
        //First, set HEAD to a nonexistent ref. If HEAD is included in the rev-list arguments by the builder,
        //this will cause the command to fail
        builderFactory.builder(repository).symbolicRef()
                .set("HEAD", "refs/heads/nonexistent")
                .build()
                .call();

        Queue<String> commitIds = createBuilder()
                .format("%H")
                .tags(true)
                .build(new GitFactoriesTestHelper.QueueHandler())
                .call();
        assertNotNull(commitIds);
        assertTrue(commitIds.contains("0a943a29376f2336b78312d99e65da17048951db"));  //signed_tag, retagged_signed_tag
        assertTrue(commitIds.contains("57150c54c38d6570b2fd5e6d6cfc19476de44e84"));  //backdated_annotated_tag
        assertEquals(20, commitIds.size());
    }

    private static String revListToLogFormat(RevListOutputHandler outputHandler) {
        return "commit %H%n" + outputHandler.getFormat();
    }

    private GitLogBuilder createBuilder() {
        return builderFactory.builder(repository).log();
    }
}
