1# Copyright (C) 2007, 2009-2012 Canonical Ltd
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17import os
18import smtplib
19
20from breezy import (
21    gpg,
22    merge_directive,
23    tests,
24    workingtree,
25    )
26
27
28EMAIL1 = """From: "J. Random Hacker" <jrandom@example.com>
29Subject: bar
30To: pqm@example.com
31User-Agent: Bazaar \\(.*\\)
32
33# Bazaar merge directive format 2 \\(Bazaar 0.90\\)
34# revision_id: bar-id
35# target_branch: ../tree2
36# testament_sha1: .*
37# timestamp: .*
38# source_branch: .
39#"""
40
41
42class TestMergeDirective(tests.TestCaseWithTransport):
43
44    def prepare_merge_directive(self):
45        self.tree1 = self.make_branch_and_tree('tree1')
46        self.build_tree_contents([('tree1/file', b'a\nb\nc\nd\n')])
47        self.tree1.branch.get_config_stack().set(
48            'email', 'J. Random Hacker <jrandom@example.com>')
49        self.tree1.add('file')
50        self.tree1.commit('foo', rev_id=b'foo-id')
51        self.tree2 = self.tree1.controldir.sprout('tree2').open_workingtree()
52        self.build_tree_contents([('tree1/file', b'a\nb\nc\nd\ne\n')])
53        self.tree1.commit('bar', rev_id=b'bar-id')
54        os.chdir('tree1')
55        return self.tree1, self.tree2
56
57    def test_merge_directive(self):
58        self.prepare_merge_directive()
59        md_text = self.run_bzr('merge-directive ../tree2')[0]
60        self.assertContainsRe(md_text, "\\+e")
61        md_text = self.run_bzr('merge-directive -r -2 ../tree2')[0]
62        self.assertNotContainsRe(md_text, "\\+e")
63        md_text = self.run_bzr(
64            'merge-directive -r -1..-2 ../tree2')[0].encode('utf-8')
65        md2 = merge_directive.MergeDirective.from_lines(
66            md_text.splitlines(True))
67        self.assertEqual(b'foo-id', md2.revision_id)
68        self.assertEqual(b'bar-id', md2.base_revision_id)
69
70    def test_submit_branch(self):
71        self.prepare_merge_directive()
72        self.run_bzr_error(('No submit branch',), 'merge-directive', retcode=3)
73        self.run_bzr('merge-directive ../tree2')
74
75    def test_public_branch(self):
76        self.prepare_merge_directive()
77        self.run_bzr_error(('No public branch',),
78                           'merge-directive --diff ../tree2', retcode=3)
79        md_text = self.run_bzr('merge-directive ../tree2')[0]
80        self.assertNotContainsRe(md_text, 'source_branch:')
81        self.run_bzr('merge-directive --diff ../tree2 .')
82        self.run_bzr('merge-directive --diff')[0]
83        self.assertNotContainsRe(md_text, 'source_branch:')
84
85    def test_patch_types(self):
86        self.prepare_merge_directive()
87        md_text = self.run_bzr('merge-directive ../tree2')[0]
88        self.assertContainsRe(md_text, "# Begin bundle")
89        self.assertContainsRe(md_text, "\\+e")
90        md_text = self.run_bzr('merge-directive ../tree2 --diff .')[0]
91        self.assertNotContainsRe(md_text, "# Begin bundle")
92        self.assertContainsRe(md_text, "\\+e")
93        md_text = self.run_bzr('merge-directive --plain')[0]
94        self.assertNotContainsRe(md_text, "\\+e")
95
96    def test_message(self):
97        self.prepare_merge_directive()
98        md_text = self.run_bzr('merge-directive ../tree2')[0]
99        self.assertNotContainsRe(md_text, 'message: Message for merge')
100        md_text = self.run_bzr('merge-directive -m Message_for_merge')[0]
101        self.assertContainsRe(md_text, 'message: Message_for_merge')
102
103    def test_signing(self):
104        self.prepare_merge_directive()
105        old_strategy = gpg.GPGStrategy
106        gpg.GPGStrategy = gpg.LoopbackGPGStrategy
107        try:
108            md_text = self.run_bzr('merge-directive --sign ../tree2')[0]
109        finally:
110            gpg.GPGStrategy = old_strategy
111        self.assertContainsRe(md_text, '^-----BEGIN PSEUDO-SIGNED CONTENT')
112
113    def run_bzr_fakemail(self, *args, **kwargs):
114        sendmail_calls = []
115
116        def sendmail(self, from_, to, message):
117            sendmail_calls.append((self, from_, to, message))
118        connect_calls = []
119
120        def connect(self, host='localhost', port=0):
121            connect_calls.append((self, host, port))
122
123        def has_extn(self, extension):
124            return False
125
126        def ehlo(self):
127            return (200, 'Ok')
128        old_sendmail = smtplib.SMTP.sendmail
129        smtplib.SMTP.sendmail = sendmail
130        old_connect = smtplib.SMTP.connect
131        smtplib.SMTP.connect = connect
132        old_ehlo = smtplib.SMTP.ehlo
133        smtplib.SMTP.ehlo = ehlo
134        old_has_extn = smtplib.SMTP.has_extn
135        smtplib.SMTP.has_extn = has_extn
136        try:
137            result = self.run_bzr(*args, **kwargs)
138        finally:
139            smtplib.SMTP.sendmail = old_sendmail
140            smtplib.SMTP.connect = old_connect
141            smtplib.SMTP.ehlo = old_ehlo
142            smtplib.SMTP.has_extn = old_has_extn
143        return result + (connect_calls, sendmail_calls)
144
145    def test_mail_default(self):
146        tree1, tree2 = self.prepare_merge_directive()
147        md_text, errr, connect_calls, sendmail_calls =\
148            self.run_bzr_fakemail(['merge-directive', '--mail-to',
149                                   'pqm@example.com', '--plain', '../tree2',
150                                   '.'])
151        self.assertEqual('', md_text)
152        self.assertEqual(1, len(connect_calls))
153        call = connect_calls[0]
154        self.assertEqual(('localhost', 0), call[1:3])
155        self.assertEqual(1, len(sendmail_calls))
156        call = sendmail_calls[0]
157        self.assertEqual(('jrandom@example.com', ['pqm@example.com']),
158                         call[1:3])
159        self.assertContainsRe(call[3], EMAIL1)
160
161    def test_pull_raw(self):
162        self.prepare_merge_directive()
163        self.tree1.commit('baz', rev_id=b'baz-id')
164        md_text = self.run_bzr(['merge-directive', self.tree2.basedir,
165                                '-r', '2', self.tree1.basedir, '--plain'])[0]
166        self.build_tree_contents([('../directive', md_text)])
167        os.chdir('../tree2')
168        self.run_bzr('pull ../directive')
169        wt = workingtree.WorkingTree.open('.')
170        self.assertEqual(b'bar-id', wt.last_revision())
171
172    def test_pull_user_r(self):
173        """If the user supplies -r, an error is emitted"""
174        self.prepare_merge_directive()
175        self.tree1.commit('baz', rev_id=b'baz-id')
176        md_text = self.run_bzr(['merge-directive', self.tree2.basedir,
177                                self.tree1.basedir, '--plain'])[0]
178        self.build_tree_contents([('../directive', md_text)])
179        os.chdir('../tree2')
180        self.run_bzr_error(
181            ('Cannot use -r with merge directives or bundles',),
182            'pull -r 2 ../directive')
183
184    def test_pull_bundle(self):
185        self.prepare_merge_directive()
186        self.tree1.commit('baz', rev_id=b'baz-id')
187        md_text = self.run_bzr(['merge-directive', self.tree2.basedir,
188                                '-r', '2', '/dev/null', '--bundle'])[0]
189        self.build_tree_contents([('../directive', md_text)])
190        os.chdir('../tree2')
191        self.run_bzr('pull ../directive')
192        wt = workingtree.WorkingTree.open('.')
193        self.assertEqual(b'bar-id', wt.last_revision())
194
195    def test_merge_raw(self):
196        self.prepare_merge_directive()
197        self.tree1.commit('baz', rev_id=b'baz-id')
198        md_text = self.run_bzr(['merge-directive', self.tree2.basedir,
199                                '-r', '2', self.tree1.basedir, '--plain'])[0]
200        self.build_tree_contents([('../directive', md_text)])
201        os.chdir('../tree2')
202        self.run_bzr('merge ../directive')
203        wt = workingtree.WorkingTree.open('.')
204        self.assertEqual(b'bar-id', wt.get_parent_ids()[1])
205
206    def test_merge_user_r(self):
207        """If the user supplies -r, an error is emitted"""
208        self.prepare_merge_directive()
209        self.tree1.commit('baz', rev_id=b'baz-id')
210        md_text = self.run_bzr(['merge-directive', self.tree2.basedir,
211                                self.tree1.basedir, '--plain'])[0]
212        self.build_tree_contents([('../directive', md_text)])
213        os.chdir('../tree2')
214        self.run_bzr_error(
215            ('Cannot use -r with merge directives or bundles',),
216            'merge -r 2 ../directive')
217
218    def test_merge_bundle(self):
219        self.prepare_merge_directive()
220        self.tree1.commit('baz', rev_id=b'baz-id')
221        md_text = self.run_bzr(['merge-directive', self.tree2.basedir,
222                                '-r', '2', '/dev/null', '--bundle'])[0]
223        self.build_tree_contents([('../directive', md_text)])
224        os.chdir('../tree2')
225        self.run_bzr('merge ../directive')
226        wt = workingtree.WorkingTree.open('.')
227        self.assertEqual(b'bar-id', wt.get_parent_ids()[1])
228
229    def test_mail_uses_config(self):
230        tree1, tree2 = self.prepare_merge_directive()
231        br = tree1.branch
232        br.get_config_stack().set('smtp_server', 'bogushost')
233        md_text, errr, connect_calls, sendmail_calls =\
234            self.run_bzr_fakemail('merge-directive --mail-to'
235                                  ' pqm@example.com --plain ../tree2 .')
236        call = connect_calls[0]
237        self.assertEqual(('bogushost', 0), call[1:3])
238
239    def test_no_common_ancestor(self):
240        foo = self.make_branch_and_tree('foo')
241        foo.commit('rev1')
242        bar = self.make_branch_and_tree('bar')
243        self.run_bzr('merge-directive ../bar', working_dir='foo')
244
245    def test_no_commits(self):
246        foo = self.make_branch_and_tree('foo')
247        bar = self.make_branch_and_tree('bar')
248        self.run_bzr_error(('No revisions to bundle.', ),
249                           'merge-directive ../bar', working_dir='foo')
250
251    def test_encoding_exact(self):
252        tree1, tree2 = self.prepare_merge_directive()
253        tree1.commit(u'messag\xe9')
254        self.run_bzr('merge-directive ../tree2')  # no exception raised
255
256    def test_merge_directive_directory(self):
257        """Test --directory option"""
258        import re
259        re_timestamp = re.compile(r'^# timestamp: .*', re.M)
260        self.prepare_merge_directive()
261        md1 = self.run_bzr('merge-directive ../tree2')[0]
262        md1 = re_timestamp.sub('# timestamp: XXX', md1)
263        os.chdir('..')
264        md2 = self.run_bzr('merge-directive --directory tree1 tree2')[0]
265        md2 = re_timestamp.sub('# timestamp: XXX', md2)
266        self.assertEqualDiff(md1.replace('../tree2', 'tree2'), md2)
267