# Copyright (C) 2005-2010 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 of bound branches (binding, unbinding, commit, etc) command."""

import os

from bzrlib import (
    errors,
    tests,
    )
from bzrlib.branch import Branch
from bzrlib.bzrdir import BzrDir
from bzrlib.tests import script


class TestBoundBranches(tests.TestCaseWithTransport):

    def create_branches(self):
        base_tree = self.make_branch_and_tree('base')
        base_tree.lock_write()
        self.build_tree(['base/a', 'base/b'])
        base_tree.add(['a', 'b'])
        base_tree.commit('init')
        base_tree.unlock()
        branch = base_tree.branch

        child_tree = branch.create_checkout('child')

        self.check_revno(1, 'child')
        d = BzrDir.open('child')
        self.assertNotEqual(None, d.open_branch().get_master_branch())

        return base_tree, child_tree

    def check_revno(self, val, loc='.'):
        self.assertEqual(
            val, len(BzrDir.open(loc).open_branch().revision_history()))

    def test_simple_binding(self):
        tree = self.make_branch_and_tree('base')
        self.build_tree(['base/a', 'base/b'])
        tree.add('a', 'b')
        tree.commit(message='init')
        branch = tree.branch

        tree.bzrdir.sprout('child')

        os.chdir('child')
        self.run_bzr('bind ../base')

        d = BzrDir.open('')
        self.assertNotEqual(None, d.open_branch().get_master_branch())

        self.run_bzr('unbind')
        self.assertEqual(None, d.open_branch().get_master_branch())

        self.run_bzr('unbind', retcode=3)

    def test_bind_branch6(self):
        branch1 = self.make_branch('branch1', format='dirstate-tags')
        os.chdir('branch1')
        error = self.run_bzr('bind', retcode=3)[1]
        self.assertContainsRe(error, 'no previous location known')

    def setup_rebind(self, format):
        branch1 = self.make_branch('branch1')
        branch2 = self.make_branch('branch2', format=format)
        branch2.bind(branch1)
        branch2.unbind()

    def test_rebind_branch6(self):
        self.setup_rebind('dirstate-tags')
        os.chdir('branch2')
        self.run_bzr('bind')
        b = Branch.open('.')
        self.assertContainsRe(b.get_bound_location(), '\/branch1\/$')

    def test_rebind_branch5(self):
        self.setup_rebind('knit')
        os.chdir('branch2')
        error = self.run_bzr('bind', retcode=3)[1]
        self.assertContainsRe(error, 'old locations')

    def test_bound_commit(self):
        child_tree = self.create_branches()[1]

        self.build_tree_contents([('child/a', 'new contents')])
        child_tree.commit(message='child')

        self.check_revno(2, 'child')

        # Make sure it committed on the parent
        self.check_revno(2, 'base')

    def test_bound_fail(self):
        # Make sure commit fails if out of date.
        base_tree, child_tree = self.create_branches()

        self.build_tree_contents([
            ('base/a',  'new base contents\n'   ),
            ('child/b', 'new b child contents\n')])
        base_tree.commit(message='base')
        self.check_revno(2, 'base')

        self.check_revno(1, 'child')
        self.assertRaises(errors.BoundBranchOutOfDate, child_tree.commit,
                                                            message='child')
        self.check_revno(1, 'child')

        child_tree.update()
        self.check_revno(2, 'child')

        child_tree.commit(message='child')
        self.check_revno(3, 'child')
        self.check_revno(3, 'base')

    def test_double_binding(self):
        child_tree = self.create_branches()[1]

        child2_tree = child_tree.bzrdir.sprout('child2').open_workingtree()

        os.chdir('child2')
        # Double binding succeeds, but committing to child2 should fail
        self.run_bzr('bind ../child')

        self.assertRaises(errors.CommitToDoubleBoundBranch,
                child2_tree.commit, message='child2', allow_pointless=True)

    def test_unbinding(self):
        base_tree, child_tree = self.create_branches()

        self.build_tree_contents([
            ('base/a',  'new base contents\n'   ),
            ('child/b', 'new b child contents\n')])

        base_tree.commit(message='base')
        self.check_revno(2, 'base')

        self.check_revno(1, 'child')
        os.chdir('child')
        self.run_bzr("commit -m child", retcode=3)
        self.check_revno(1)
        self.run_bzr('unbind')
        child_tree.commit(message='child')
        self.check_revno(2)

    def test_commit_remote_bound(self):
        # It is not possible to commit to a branch
        # which is bound to a branch which is bound
        base_tree, child_tree = self.create_branches()
        base_tree.bzrdir.sprout('newbase')

        os.chdir('base')
        # There is no way to know that B has already
        # been bound by someone else, otherwise it
        # might be nice if this would fail
        self.run_bzr('bind ../newbase')

        os.chdir('../child')
        self.run_bzr('commit -m failure --unchanged', retcode=3)

    def test_pull_updates_both(self):
        base_tree = self.create_branches()[0]
        newchild_tree = base_tree.bzrdir.sprout('newchild').open_workingtree()
        self.build_tree_contents([('newchild/b', 'newchild b contents\n')])
        newchild_tree.commit(message='newchild')
        self.check_revno(2, 'newchild')

        os.chdir('child')
        # The pull should succeed, and update
        # the bound parent branch
        self.run_bzr('pull ../newchild')
        self.check_revno(2)

        self.check_revno(2, '../base')

    def test_pull_local_updates_local(self):
        base_tree = self.create_branches()[0]
        newchild_tree = base_tree.bzrdir.sprout('newchild').open_workingtree()
        self.build_tree_contents([('newchild/b', 'newchild b contents\n')])
        newchild_tree.commit(message='newchild')
        self.check_revno(2, 'newchild')

        os.chdir('child')
        # The pull should succeed, and update
        # the bound parent branch
        self.run_bzr('pull ../newchild --local')
        self.check_revno(2)

        self.check_revno(1, '../base')

    def test_bind_diverged(self):
        base_tree, child_tree = self.create_branches()
        base_branch = base_tree.branch
        child_branch = child_tree.branch

        os.chdir('child')
        self.run_bzr('unbind')

        child_tree.commit(message='child', allow_pointless=True)
        self.check_revno(2)

        os.chdir('..')
        self.check_revno(1, 'base')
        base_tree.commit(message='base', allow_pointless=True)
        self.check_revno(2, 'base')

        os.chdir('child')
        # These branches have diverged, but bind should succeed anyway
        self.run_bzr('bind ../base')

        # This should turn the local commit into a merge
        child_tree.update()
        child_tree.commit(message='merged')
        self.check_revno(3)

        # After binding, the revision history should be unaltered
        # take a copy before
        base_history = base_branch.revision_history()
        child_history = child_branch.revision_history()

    def test_bind_parent_ahead(self):
        base_tree = self.create_branches()[0]

        os.chdir('child')
        self.run_bzr('unbind')

        base_tree.commit(message='base', allow_pointless=True)

        self.check_revno(1)
        self.run_bzr('bind ../base')

        # binding does not pull data:
        self.check_revno(1)
        self.run_bzr('unbind')

        # Check and make sure it also works if parent is ahead multiple
        base_tree.commit(message='base 3', allow_pointless=True)
        base_tree.commit(message='base 4', allow_pointless=True)
        base_tree.commit(message='base 5', allow_pointless=True)
        self.check_revno(5, '../base')

        self.check_revno(1)
        self.run_bzr('bind ../base')
        self.check_revno(1)

    def test_bind_child_ahead(self):
        # test binding when the master branches history is a prefix of the
        # childs - it should bind ok but the revision histories should not
        # be altered
        child_tree = self.create_branches()[1]

        os.chdir('child')
        self.run_bzr('unbind')
        child_tree.commit(message='child', allow_pointless=True)
        self.check_revno(2)
        self.check_revno(1, '../base')

        self.run_bzr('bind ../base')
        self.check_revno(1, '../base')

        # Check and make sure it also works if child is ahead multiple
        self.run_bzr('unbind')
        child_tree.commit(message='child 3', allow_pointless=True)
        child_tree.commit(message='child 4', allow_pointless=True)
        child_tree.commit(message='child 5', allow_pointless=True)
        self.check_revno(5)

        self.check_revno(1, '../base')
        self.run_bzr('bind ../base')
        self.check_revno(1, '../base')

    def test_bind_fail_if_missing(self):
        """We should not be able to bind to a missing branch."""
        tree = self.make_branch_and_tree('tree_1')
        tree.commit('dummy commit')
        self.run_bzr_error(['Not a branch.*no-such-branch/'], ['bind', '../no-such-branch'],
                            working_dir='tree_1')
        self.assertIs(None, tree.branch.get_bound_location())

    def test_bind_nick(self):
        """Bind should not update implicit nick."""
        base = self.make_branch_and_tree('base')
        child = self.make_branch_and_tree('child')
        os.chdir('child')
        self.assertEqual(child.branch.nick, 'child')
        self.assertEqual(child.branch.get_config().has_explicit_nickname(),
            False)
        self.run_bzr('bind ../base')
        self.assertEqual(child.branch.nick, base.branch.nick)
        self.assertEqual(child.branch.get_config().has_explicit_nickname(),
            False)

    def test_bind_explicit_nick(self):
        """Bind should update explicit nick."""
        base = self.make_branch_and_tree('base')
        child = self.make_branch_and_tree('child')
        os.chdir('child')
        child.branch.nick = "explicit_nick"
        self.assertEqual(child.branch.nick, "explicit_nick")
        self.assertEqual(child.branch.get_config()._get_explicit_nickname(),
            "explicit_nick")
        self.run_bzr('bind ../base')
        self.assertEqual(child.branch.nick, base.branch.nick)
        self.assertEqual(child.branch.get_config()._get_explicit_nickname(),
            base.branch.nick)

    def test_commit_after_merge(self):
        base_tree, child_tree = self.create_branches()

        # We want merge to be able to be a local only
        # operation, because it can be without violating
        # the binding invariants.
        # But we can't fail afterwards
        other_tree = child_tree.bzrdir.sprout('other').open_workingtree()
        other_branch = other_tree.branch

        self.build_tree_contents([('other/c', 'file c\n')])
        other_tree.add('c')
        other_tree.commit(message='adding c')
        new_rev_id = other_branch.revision_history()[-1]

        child_tree.merge_from_branch(other_branch)

        self.assertPathExists('child/c')
        self.assertEqual([new_rev_id], child_tree.get_parent_ids()[1:])

        # Make sure the local branch has the installed revision
        self.assertTrue(child_tree.branch.repository.has_revision(new_rev_id))

        # And make sure that the base tree does not
        self.assertFalse(base_tree.branch.repository.has_revision(new_rev_id))

        # Commit should succeed, and cause merged revisions to
        # be pulled into base
        os.chdir('child')
        self.run_bzr(['commit', '-m', 'merge other'])

        self.check_revno(2)

        self.check_revno(2, '../base')

        self.assertTrue(base_tree.branch.repository.has_revision(new_rev_id))

    def test_pull_overwrite(self):
        # XXX: This test should be moved to branch-implemenations/test_pull
        child_tree = self.create_branches()[1]

        other_tree = child_tree.bzrdir.sprout('other').open_workingtree()

        self.build_tree_contents([('other/a', 'new contents\n')])
        other_tree.commit(message='changed a')
        self.check_revno(2, 'other')
        self.build_tree_contents([
            ('other/a', 'new contents\nand then some\n')])
        other_tree.commit(message='another a')
        self.check_revno(3, 'other')
        self.build_tree_contents([
            ('other/a', 'new contents\nand then some\nand some more\n')])
        other_tree.commit('yet another a')
        self.check_revno(4, 'other')

        self.build_tree_contents([('child/a', 'also changed a\n')])
        child_tree.commit(message='child modified a')

        self.check_revno(2, 'child')
        self.check_revno(2, 'base')

        os.chdir('child')
        self.run_bzr('pull --overwrite ../other')

        # both the local and master should have been updated.
        self.check_revno(4)
        self.check_revno(4, '../base')

    def test_bind_directory(self):
        """Test --directory option"""
        tree = self.make_branch_and_tree('base')
        self.build_tree(['base/a', 'base/b'])
        tree.add('a', 'b')
        tree.commit(message='init')
        branch = tree.branch
        tree.bzrdir.sprout('child')
        self.run_bzr('bind --directory=child base')
        d = BzrDir.open('child')
        self.assertNotEqual(None, d.open_branch().get_master_branch())
        self.run_bzr('unbind -d child')
        self.assertEqual(None, d.open_branch().get_master_branch())
        self.run_bzr('unbind --directory child', retcode=3)


class TestBind(script.TestCaseWithTransportAndScript):

    def test_bind_when_bound(self):
        self.run_script("""
$ bzr init trunk
...
$ bzr init copy
...
$ cd copy
$ bzr bind ../trunk
$ bzr bind
2>bzr: ERROR: Branch is already bound
""")

    def test_bind_before_bound(self):
        self.run_script("""
$ bzr init trunk
...
$ cd trunk
$ bzr bind
2>bzr: ERROR: No location supplied and no previous location known
""")
