# Copyright (C) 2011, 2016 Canonical Ltd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA


"""Tests for graph operations on stacked repositories."""


from breezy import tests, urlutils
from breezy.bzr import remote
from breezy.tests.per_repository import TestCaseWithRepository


class TestGraph(TestCaseWithRepository):

    def test_get_known_graph_ancestry_stacked(self):
        """get_known_graph_ancestry works correctly on stacking.

        See <https://bugs.launchpad.net/bugs/715000>.
        """
        branch_a, branch_b, branch_c, revid_1 = self.make_double_stacked_branches()
        for br in [branch_a, branch_b, branch_c]:
            self.assertEqual(
                [revid_1],
                br.repository.get_known_graph_ancestry([revid_1]).topo_sort())

    def make_double_stacked_branches(self):
        wt_a = self.make_branch_and_tree('a')
        branch_a = wt_a.branch
        branch_b = self.make_branch('b')
        branch_b.set_stacked_on_url(
            urlutils.relative_url(branch_b.base, branch_a.base))
        branch_c = self.make_branch('c')
        branch_c.set_stacked_on_url(
            urlutils.relative_url(branch_c.base, branch_b.base))
        revid_1 = wt_a.commit('first commit')
        return branch_a, branch_b, branch_c, revid_1

    def make_stacked_branch_with_long_history(self):
        builder = self.make_branch_builder('source')
        builder.start_series()
        builder.build_snapshot(None, [
            ('add', ('', b'root-id', 'directory', None))],
            revision_id=b'A')
        builder.build_snapshot([b'A'], [], revision_id=b'B')
        builder.build_snapshot([b'B'], [], revision_id=b'C')
        builder.build_snapshot([b'C'], [], revision_id=b'D')
        builder.build_snapshot([b'D'], [], revision_id=b'E')
        builder.build_snapshot([b'E'], [], revision_id=b'F')
        source_b = builder.get_branch()
        master_b = self.make_branch('master')
        master_b.pull(source_b, stop_revision=b'E')
        stacked_b = self.make_branch('stacked')
        stacked_b.set_stacked_on_url('../master')
        stacked_b.pull(source_b, stop_revision=b'F')
        builder.finish_series()
        return master_b, stacked_b

    def assertParentMapCalls(self, expected):
        """Check that self.hpss_calls has the expected get_parent_map calls."""
        get_parent_map_calls = []
        for c in self.hpss_calls:
            # Right now, the only RPCs that get called are get_parent_map. If
            # this changes in the future, we can change this to:
            # if c.call.method != 'Repository.get_parent_map':
            #    continue
            self.assertEqual(b'Repository.get_parent_map', c.call.method)
            args = c.call.args
            location = args[0]
            self.assertEqual(b'include-missing:', args[1])
            revisions = sorted(args[2:])
            get_parent_map_calls.append((location, revisions))
        self.assertEqual(expected, get_parent_map_calls)

    def test_doesnt_call_get_parent_map_on_all_fallback_revs(self):
        if not isinstance(self.repository_format,
                          remote.RemoteRepositoryFormat):
            raise tests.TestNotApplicable('only for RemoteRepository')
        # bug #388269
        master_b, stacked_b = self.make_stacked_branch_with_long_history()
        self.addCleanup(stacked_b.lock_read().unlock)
        self.make_repository('target_repo', shared=True)
        target_b = self.make_branch('target_repo/branch')
        self.addCleanup(target_b.lock_write().unlock)
        self.setup_smart_server_with_call_log()
        res = target_b.repository.search_missing_revision_ids(
            stacked_b.repository, revision_ids=[b'F'],
            find_ghosts=False)
        self.assertParentMapCalls([
            # One call to stacked to start, which returns F=>E, and that E
            # itself is missing, so when we step, we won't look for it.
            (b'extra/stacked/', [b'F']),
            # One fallback call to extra/master, which will return the rest of
            # the history.
            (b'extra/master/', [b'E']),
            # And then one get_parent_map call to the target, to see if it
            # already has any of these revisions.
            (b'extra/target_repo/branch/',
             [b'A', b'B', b'C', b'D', b'E', b'F']),
            ])
        # Before bug #388269 was fixed, there would be a bunch of extra calls
        # to 'extra/stacked', ['D'] then ['C'], then ['B'], then ['A'].
        # One-at-a-time for the rest of the ancestry.
