~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_commit.py

  • Committer: Robert Collins
  • Date: 2007-03-11 23:34:27 UTC
  • mto: This revision was merged to the branch mainline in revision 2442.
  • Revision ID: robertc@robertcollins.net-20070311233427-z8skxqrx8shqy1de
Fix building of C modules without pyrex installed.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 by Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
 
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
 
 
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
17
 
18
18
import os
19
19
 
20
 
from bzrlib.selftest import TestCaseInTempDir
 
20
import bzrlib
 
21
from bzrlib import (
 
22
    errors,
 
23
    lockdir,
 
24
    osutils,
 
25
    tests,
 
26
    )
21
27
from bzrlib.branch import Branch
22
 
from bzrlib.commit import Commit
23
 
from bzrlib.errors import PointlessCommit, BzrError
 
28
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
 
29
from bzrlib.commit import Commit, NullCommitReporter
 
30
from bzrlib.config import BranchConfig
 
31
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed, 
 
32
                           LockContention)
 
33
from bzrlib.tests import TestCaseWithTransport
 
34
from bzrlib.workingtree import WorkingTree
24
35
 
25
36
 
26
37
# TODO: Test commit with some added, and added-but-missing files
27
38
 
28
 
class TestCommit(TestCaseInTempDir):
 
39
class MustSignConfig(BranchConfig):
 
40
 
 
41
    def signature_needed(self):
 
42
        return True
 
43
 
 
44
    def gpg_signing_command(self):
 
45
        return ['cat', '-']
 
46
 
 
47
 
 
48
class BranchWithHooks(BranchConfig):
 
49
 
 
50
    def post_commit(self):
 
51
        return "bzrlib.ahook bzrlib.ahook"
 
52
 
 
53
 
 
54
class CapturingReporter(NullCommitReporter):
 
55
    """This reporter captures the calls made to it for evaluation later."""
 
56
 
 
57
    def __init__(self):
 
58
        # a list of the calls this received
 
59
        self.calls = []
 
60
 
 
61
    def snapshot_change(self, change, path):
 
62
        self.calls.append(('change', change, path))
 
63
 
 
64
    def deleted(self, file_id):
 
65
        self.calls.append(('deleted', file_id))
 
66
 
 
67
    def missing(self, path):
 
68
        self.calls.append(('missing', path))
 
69
 
 
70
    def renamed(self, change, old_path, new_path):
 
71
        self.calls.append(('renamed', change, old_path, new_path))
 
72
 
 
73
 
 
74
class TestCommit(TestCaseWithTransport):
29
75
 
30
76
    def test_simple_commit(self):
31
77
        """Commit and check two versions of a single file."""
32
 
        b = Branch.initialize('.')
 
78
        wt = self.make_branch_and_tree('.')
 
79
        b = wt.branch
33
80
        file('hello', 'w').write('hello world')
34
 
        b.add('hello')
35
 
        b.commit(message='add hello')
36
 
        file_id = b.working_tree().path2id('hello')
 
81
        wt.add('hello')
 
82
        wt.commit(message='add hello')
 
83
        file_id = wt.path2id('hello')
37
84
 
38
85
        file('hello', 'w').write('version 2')
39
 
        b.commit(message='commit 2')
 
86
        wt.commit(message='commit 2')
40
87
 
41
88
        eq = self.assertEquals
42
89
        eq(b.revno(), 2)
43
90
        rh = b.revision_history()
44
 
        rev = b.get_revision(rh[0])
 
91
        rev = b.repository.get_revision(rh[0])
45
92
        eq(rev.message, 'add hello')
46
93
 
47
 
        tree1 = b.revision_tree(rh[0])
 
94
        tree1 = b.repository.revision_tree(rh[0])
48
95
        text = tree1.get_file_text(file_id)
49
96
        eq(text, 'hello world')
50
97
 
51
 
        tree2 = b.revision_tree(rh[1])
 
98
        tree2 = b.repository.revision_tree(rh[1])
52
99
        eq(tree2.get_file_text(file_id), 'version 2')
53
100
 
54
 
 
55
101
    def test_delete_commit(self):
56
102
        """Test a commit with a deleted file"""
57
 
        b = Branch.initialize('.')
 
103
        wt = self.make_branch_and_tree('.')
 
104
        b = wt.branch
58
105
        file('hello', 'w').write('hello world')
59
 
        b.add(['hello'], ['hello-id'])
60
 
        b.commit(message='add hello')
 
106
        wt.add(['hello'], ['hello-id'])
 
107
        wt.commit(message='add hello')
61
108
 
62
109
        os.remove('hello')
63
 
        b.commit('removed hello', rev_id='rev2')
 
110
        wt.commit('removed hello', rev_id='rev2')
64
111
 
65
 
        tree = b.revision_tree('rev2')
 
112
        tree = b.repository.revision_tree('rev2')
66
113
        self.assertFalse(tree.has_id('hello-id'))
67
114
 
68
 
 
69
115
    def test_pointless_commit(self):
70
116
        """Commit refuses unless there are changes or it's forced."""
71
 
        b = Branch.initialize('.')
 
117
        wt = self.make_branch_and_tree('.')
 
118
        b = wt.branch
72
119
        file('hello', 'w').write('hello')
73
 
        b.add(['hello'])
74
 
        b.commit(message='add hello')
 
120
        wt.add(['hello'])
 
121
        wt.commit(message='add hello')
75
122
        self.assertEquals(b.revno(), 1)
76
123
        self.assertRaises(PointlessCommit,
77
 
                          b.commit,
 
124
                          wt.commit,
78
125
                          message='fails',
79
126
                          allow_pointless=False)
80
127
        self.assertEquals(b.revno(), 1)
81
128
        
82
 
 
83
 
 
84
129
    def test_commit_empty(self):
85
130
        """Commiting an empty tree works."""
86
 
        b = Branch.initialize('.')
87
 
        b.commit(message='empty tree', allow_pointless=True)
 
131
        wt = self.make_branch_and_tree('.')
 
132
        b = wt.branch
 
133
        wt.commit(message='empty tree', allow_pointless=True)
88
134
        self.assertRaises(PointlessCommit,
89
 
                          b.commit,
 
135
                          wt.commit,
90
136
                          message='empty tree',
91
137
                          allow_pointless=False)
92
 
        b.commit(message='empty tree', allow_pointless=True)
 
138
        wt.commit(message='empty tree', allow_pointless=True)
93
139
        self.assertEquals(b.revno(), 2)
94
140
 
95
 
 
96
141
    def test_selective_delete(self):
97
142
        """Selective commit in tree with deletions"""
98
 
        b = Branch.initialize('.')
 
143
        wt = self.make_branch_and_tree('.')
 
144
        b = wt.branch
99
145
        file('hello', 'w').write('hello')
100
146
        file('buongia', 'w').write('buongia')
101
 
        b.add(['hello', 'buongia'],
 
147
        wt.add(['hello', 'buongia'],
102
148
              ['hello-id', 'buongia-id'])
103
 
        b.commit(message='add files',
 
149
        wt.commit(message='add files',
104
150
                 rev_id='test@rev-1')
105
151
        
106
152
        os.remove('hello')
107
153
        file('buongia', 'w').write('new text')
108
 
        b.commit(message='update text',
 
154
        wt.commit(message='update text',
109
155
                 specific_files=['buongia'],
110
156
                 allow_pointless=False,
111
157
                 rev_id='test@rev-2')
112
158
 
113
 
        b.commit(message='remove hello',
 
159
        wt.commit(message='remove hello',
114
160
                 specific_files=['hello'],
115
161
                 allow_pointless=False,
116
162
                 rev_id='test@rev-3')
118
164
        eq = self.assertEquals
119
165
        eq(b.revno(), 3)
120
166
 
121
 
        tree2 = b.revision_tree('test@rev-2')
 
167
        tree2 = b.repository.revision_tree('test@rev-2')
122
168
        self.assertTrue(tree2.has_filename('hello'))
123
169
        self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
124
170
        self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
125
171
        
126
 
        tree3 = b.revision_tree('test@rev-3')
 
172
        tree3 = b.repository.revision_tree('test@rev-3')
127
173
        self.assertFalse(tree3.has_filename('hello'))
128
174
        self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
129
175
 
130
 
 
131
176
    def test_commit_rename(self):
132
177
        """Test commit of a revision where a file is renamed."""
133
 
        b = Branch.initialize('.')
134
 
        self.build_tree(['hello'])
135
 
        b.add(['hello'], ['hello-id'])
136
 
        b.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
 
178
        tree = self.make_branch_and_tree('.')
 
179
        b = tree.branch
 
180
        self.build_tree(['hello'], line_endings='binary')
 
181
        tree.add(['hello'], ['hello-id'])
 
182
        tree.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
137
183
 
138
 
        b.rename_one('hello', 'fruity')
139
 
        b.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
 
184
        tree.rename_one('hello', 'fruity')
 
185
        tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
140
186
 
141
187
        eq = self.assertEquals
142
 
        tree1 = b.revision_tree('test@rev-1')
 
188
        tree1 = b.repository.revision_tree('test@rev-1')
143
189
        eq(tree1.id2path('hello-id'), 'hello')
144
190
        eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
145
191
        self.assertFalse(tree1.has_filename('fruity'))
147
193
        ie = tree1.inventory['hello-id']
148
194
        eq(ie.revision, 'test@rev-1')
149
195
 
150
 
        tree2 = b.revision_tree('test@rev-2')
 
196
        tree2 = b.repository.revision_tree('test@rev-2')
151
197
        eq(tree2.id2path('hello-id'), 'fruity')
152
198
        eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
153
199
        self.check_inventory_shape(tree2.inventory, ['fruity'])
154
200
        ie = tree2.inventory['hello-id']
155
201
        eq(ie.revision, 'test@rev-2')
156
202
 
157
 
 
158
203
    def test_reused_rev_id(self):
159
204
        """Test that a revision id cannot be reused in a branch"""
160
 
        b = Branch.initialize('.')
161
 
        b.commit('initial', rev_id='test@rev-1', allow_pointless=True)
 
205
        wt = self.make_branch_and_tree('.')
 
206
        b = wt.branch
 
207
        wt.commit('initial', rev_id='test@rev-1', allow_pointless=True)
162
208
        self.assertRaises(Exception,
163
 
                          b.commit,
 
209
                          wt.commit,
164
210
                          message='reused id',
165
211
                          rev_id='test@rev-1',
166
212
                          allow_pointless=True)
167
 
                          
168
 
 
169
213
 
170
214
    def test_commit_move(self):
171
215
        """Test commit of revisions with moved files and directories"""
172
216
        eq = self.assertEquals
173
 
        b = Branch.initialize('.')
 
217
        wt = self.make_branch_and_tree('.')
 
218
        b = wt.branch
174
219
        r1 = 'test@rev-1'
175
220
        self.build_tree(['hello', 'a/', 'b/'])
176
 
        b.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
177
 
        b.commit('initial', rev_id=r1, allow_pointless=False)
178
 
 
179
 
        b.move(['hello'], 'a')
 
221
        wt.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
 
222
        wt.commit('initial', rev_id=r1, allow_pointless=False)
 
223
        wt.move(['hello'], 'a')
180
224
        r2 = 'test@rev-2'
181
 
        b.commit('two', rev_id=r2, allow_pointless=False)
182
 
        self.check_inventory_shape(b.inventory,
183
 
                                   ['a', 'a/hello', 'b'])
 
225
        wt.commit('two', rev_id=r2, allow_pointless=False)
 
226
        wt.lock_read()
 
227
        try:
 
228
            self.check_inventory_shape(wt.read_working_inventory(),
 
229
                                       ['a', 'a/hello', 'b'])
 
230
        finally:
 
231
            wt.unlock()
184
232
 
185
 
        b.move(['b'], 'a')
 
233
        wt.move(['b'], 'a')
186
234
        r3 = 'test@rev-3'
187
 
        b.commit('three', rev_id=r3, allow_pointless=False)
188
 
        self.check_inventory_shape(b.inventory,
189
 
                                   ['a', 'a/hello', 'a/b'])
190
 
        self.check_inventory_shape(b.get_revision_inventory(r3),
191
 
                                   ['a', 'a/hello', 'a/b'])
 
235
        wt.commit('three', rev_id=r3, allow_pointless=False)
 
236
        wt.lock_read()
 
237
        try:
 
238
            self.check_inventory_shape(wt.read_working_inventory(),
 
239
                                       ['a', 'a/hello', 'a/b'])
 
240
            self.check_inventory_shape(b.repository.get_revision_inventory(r3),
 
241
                                       ['a', 'a/hello', 'a/b'])
 
242
        finally:
 
243
            wt.unlock()
192
244
 
193
 
        b.move([os.sep.join(['a', 'hello'])],
194
 
               os.sep.join(['a', 'b']))
 
245
        wt.move(['a/hello'], 'a/b')
195
246
        r4 = 'test@rev-4'
196
 
        b.commit('four', rev_id=r4, allow_pointless=False)
197
 
        self.check_inventory_shape(b.inventory,
198
 
                                   ['a', 'a/b/hello', 'a/b'])
 
247
        wt.commit('four', rev_id=r4, allow_pointless=False)
 
248
        wt.lock_read()
 
249
        try:
 
250
            self.check_inventory_shape(wt.read_working_inventory(),
 
251
                                       ['a', 'a/b/hello', 'a/b'])
 
252
        finally:
 
253
            wt.unlock()
199
254
 
200
 
        inv = b.get_revision_inventory(r4)
 
255
        inv = b.repository.get_revision_inventory(r4)
201
256
        eq(inv['hello-id'].revision, r4)
202
257
        eq(inv['a-id'].revision, r1)
203
258
        eq(inv['b-id'].revision, r3)
204
259
 
205
 
        
206
260
    def test_removed_commit(self):
207
 
        """Test a commit with a removed file"""
208
 
        b = Branch.initialize('.')
 
261
        """Commit with a removed file"""
 
262
        wt = self.make_branch_and_tree('.')
 
263
        b = wt.branch
209
264
        file('hello', 'w').write('hello world')
210
 
        b.add(['hello'], ['hello-id'])
211
 
        b.commit(message='add hello')
212
 
 
213
 
        b.remove('hello')
214
 
        b.commit('removed hello', rev_id='rev2')
215
 
 
216
 
        tree = b.revision_tree('rev2')
 
265
        wt.add(['hello'], ['hello-id'])
 
266
        wt.commit(message='add hello')
 
267
        wt.remove('hello')
 
268
        wt.commit('removed hello', rev_id='rev2')
 
269
 
 
270
        tree = b.repository.revision_tree('rev2')
217
271
        self.assertFalse(tree.has_id('hello-id'))
218
272
 
219
 
 
220
273
    def test_committed_ancestry(self):
221
274
        """Test commit appends revisions to ancestry."""
222
 
        b = Branch.initialize('.')
 
275
        wt = self.make_branch_and_tree('.')
 
276
        b = wt.branch
223
277
        rev_ids = []
224
278
        for i in range(4):
225
279
            file('hello', 'w').write((str(i) * 4) + '\n')
226
280
            if i == 0:
227
 
                b.add(['hello'], ['hello-id'])
 
281
                wt.add(['hello'], ['hello-id'])
228
282
            rev_id = 'test@rev-%d' % (i+1)
229
283
            rev_ids.append(rev_id)
230
 
            b.commit(message='rev %d' % (i+1),
 
284
            wt.commit(message='rev %d' % (i+1),
231
285
                     rev_id=rev_id)
232
286
        eq = self.assertEquals
233
287
        eq(b.revision_history(), rev_ids)
234
288
        for i in range(4):
235
 
            anc = b.get_ancestry(rev_ids[i])
 
289
            anc = b.repository.get_ancestry(rev_ids[i])
236
290
            eq(anc, [None] + rev_ids[:i+1])
 
291
 
 
292
    def test_commit_new_subdir_child_selective(self):
 
293
        wt = self.make_branch_and_tree('.')
 
294
        b = wt.branch
 
295
        self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
 
296
        wt.add(['dir', 'dir/file1', 'dir/file2'],
 
297
              ['dirid', 'file1id', 'file2id'])
 
298
        wt.commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
 
299
        inv = b.repository.get_inventory('1')
 
300
        self.assertEqual('1', inv['dirid'].revision)
 
301
        self.assertEqual('1', inv['file1id'].revision)
 
302
        # FIXME: This should raise a KeyError I think, rbc20051006
 
303
        self.assertRaises(BzrError, inv.__getitem__, 'file2id')
 
304
 
 
305
    def test_strict_commit(self):
 
306
        """Try and commit with unknown files and strict = True, should fail."""
 
307
        from bzrlib.errors import StrictCommitFailed
 
308
        wt = self.make_branch_and_tree('.')
 
309
        b = wt.branch
 
310
        file('hello', 'w').write('hello world')
 
311
        wt.add('hello')
 
312
        file('goodbye', 'w').write('goodbye cruel world!')
 
313
        self.assertRaises(StrictCommitFailed, wt.commit,
 
314
            message='add hello but not goodbye', strict=True)
 
315
 
 
316
    def test_strict_commit_without_unknowns(self):
 
317
        """Try and commit with no unknown files and strict = True,
 
318
        should work."""
 
319
        from bzrlib.errors import StrictCommitFailed
 
320
        wt = self.make_branch_and_tree('.')
 
321
        b = wt.branch
 
322
        file('hello', 'w').write('hello world')
 
323
        wt.add('hello')
 
324
        wt.commit(message='add hello', strict=True)
 
325
 
 
326
    def test_nonstrict_commit(self):
 
327
        """Try and commit with unknown files and strict = False, should work."""
 
328
        wt = self.make_branch_and_tree('.')
 
329
        b = wt.branch
 
330
        file('hello', 'w').write('hello world')
 
331
        wt.add('hello')
 
332
        file('goodbye', 'w').write('goodbye cruel world!')
 
333
        wt.commit(message='add hello but not goodbye', strict=False)
 
334
 
 
335
    def test_nonstrict_commit_without_unknowns(self):
 
336
        """Try and commit with no unknown files and strict = False,
 
337
        should work."""
 
338
        wt = self.make_branch_and_tree('.')
 
339
        b = wt.branch
 
340
        file('hello', 'w').write('hello world')
 
341
        wt.add('hello')
 
342
        wt.commit(message='add hello', strict=False)
 
343
 
 
344
    def test_signed_commit(self):
 
345
        import bzrlib.gpg
 
346
        import bzrlib.commit as commit
 
347
        oldstrategy = bzrlib.gpg.GPGStrategy
 
348
        wt = self.make_branch_and_tree('.')
 
349
        branch = wt.branch
 
350
        wt.commit("base", allow_pointless=True, rev_id='A')
 
351
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
352
        try:
 
353
            from bzrlib.testament import Testament
 
354
            # monkey patch gpg signing mechanism
 
355
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
 
356
            commit.Commit(config=MustSignConfig(branch)).commit(message="base",
 
357
                                                      allow_pointless=True,
 
358
                                                      rev_id='B',
 
359
                                                      working_tree=wt)
 
360
            self.assertEqual(Testament.from_revision(branch.repository,
 
361
                             'B').as_short_text(),
 
362
                             branch.repository.get_signature_text('B'))
 
363
        finally:
 
364
            bzrlib.gpg.GPGStrategy = oldstrategy
 
365
 
 
366
    def test_commit_failed_signature(self):
 
367
        import bzrlib.gpg
 
368
        import bzrlib.commit as commit
 
369
        oldstrategy = bzrlib.gpg.GPGStrategy
 
370
        wt = self.make_branch_and_tree('.')
 
371
        branch = wt.branch
 
372
        wt.commit("base", allow_pointless=True, rev_id='A')
 
373
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
374
        try:
 
375
            from bzrlib.testament import Testament
 
376
            # monkey patch gpg signing mechanism
 
377
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
 
378
            config = MustSignConfig(branch)
 
379
            self.assertRaises(SigningFailed,
 
380
                              commit.Commit(config=config).commit,
 
381
                              message="base",
 
382
                              allow_pointless=True,
 
383
                              rev_id='B',
 
384
                              working_tree=wt)
 
385
            branch = Branch.open(self.get_url('.'))
 
386
            self.assertEqual(branch.revision_history(), ['A'])
 
387
            self.failIf(branch.repository.has_revision('B'))
 
388
        finally:
 
389
            bzrlib.gpg.GPGStrategy = oldstrategy
 
390
 
 
391
    def test_commit_invokes_hooks(self):
 
392
        import bzrlib.commit as commit
 
393
        wt = self.make_branch_and_tree('.')
 
394
        branch = wt.branch
 
395
        calls = []
 
396
        def called(branch, rev_id):
 
397
            calls.append('called')
 
398
        bzrlib.ahook = called
 
399
        try:
 
400
            config = BranchWithHooks(branch)
 
401
            commit.Commit(config=config).commit(
 
402
                            message = "base",
 
403
                            allow_pointless=True,
 
404
                            rev_id='A', working_tree = wt)
 
405
            self.assertEqual(['called', 'called'], calls)
 
406
        finally:
 
407
            del bzrlib.ahook
 
408
 
 
409
    def test_commit_object_doesnt_set_nick(self):
 
410
        # using the Commit object directly does not set the branch nick.
 
411
        wt = self.make_branch_and_tree('.')
 
412
        c = Commit()
 
413
        c.commit(working_tree=wt, message='empty tree', allow_pointless=True)
 
414
        self.assertEquals(wt.branch.revno(), 1)
 
415
        self.assertEqual({},
 
416
                         wt.branch.repository.get_revision(
 
417
                            wt.branch.last_revision()).properties)
 
418
 
 
419
    def test_safe_master_lock(self):
 
420
        os.mkdir('master')
 
421
        master = BzrDirMetaFormat1().initialize('master')
 
422
        master.create_repository()
 
423
        master_branch = master.create_branch()
 
424
        master.create_workingtree()
 
425
        bound = master.sprout('bound')
 
426
        wt = bound.open_workingtree()
 
427
        wt.branch.set_bound_location(os.path.realpath('master'))
 
428
 
 
429
        orig_default = lockdir._DEFAULT_TIMEOUT_SECONDS
 
430
        master_branch.lock_write()
 
431
        try:
 
432
            lockdir._DEFAULT_TIMEOUT_SECONDS = 1
 
433
            self.assertRaises(LockContention, wt.commit, 'silly')
 
434
        finally:
 
435
            lockdir._DEFAULT_TIMEOUT_SECONDS = orig_default
 
436
            master_branch.unlock()
 
437
 
 
438
    def test_commit_bound_merge(self):
 
439
        # see bug #43959; commit of a merge in a bound branch fails to push
 
440
        # the new commit into the master
 
441
        master_branch = self.make_branch('master')
 
442
        bound_tree = self.make_branch_and_tree('bound')
 
443
        bound_tree.branch.bind(master_branch)
 
444
 
 
445
        self.build_tree_contents([('bound/content_file', 'initial contents\n')])
 
446
        bound_tree.add(['content_file'])
 
447
        bound_tree.commit(message='woo!')
 
448
 
 
449
        other_bzrdir = master_branch.bzrdir.sprout('other')
 
450
        other_tree = other_bzrdir.open_workingtree()
 
451
 
 
452
        # do a commit to the the other branch changing the content file so
 
453
        # that our commit after merging will have a merged revision in the
 
454
        # content file history.
 
455
        self.build_tree_contents([('other/content_file', 'change in other\n')])
 
456
        other_tree.commit('change in other')
 
457
 
 
458
        # do a merge into the bound branch from other, and then change the
 
459
        # content file locally to force a new revision (rather than using the
 
460
        # revision from other). This forces extra processing in commit.
 
461
        bound_tree.merge_from_branch(other_tree.branch)
 
462
        self.build_tree_contents([('bound/content_file', 'change in bound\n')])
 
463
 
 
464
        # before #34959 was fixed, this failed with 'revision not present in
 
465
        # weave' when trying to implicitly push from the bound branch to the master
 
466
        bound_tree.commit(message='commit of merge in bound tree')
 
467
 
 
468
    def test_commit_reporting_after_merge(self):
 
469
        # when doing a commit of a merge, the reporter needs to still 
 
470
        # be called for each item that is added/removed/deleted.
 
471
        this_tree = self.make_branch_and_tree('this')
 
472
        # we need a bunch of files and dirs, to perform one action on each.
 
473
        self.build_tree([
 
474
            'this/dirtorename/',
 
475
            'this/dirtoreparent/',
 
476
            'this/dirtoleave/',
 
477
            'this/dirtoremove/',
 
478
            'this/filetoreparent',
 
479
            'this/filetorename',
 
480
            'this/filetomodify',
 
481
            'this/filetoremove',
 
482
            'this/filetoleave']
 
483
            )
 
484
        this_tree.add([
 
485
            'dirtorename',
 
486
            'dirtoreparent',
 
487
            'dirtoleave',
 
488
            'dirtoremove',
 
489
            'filetoreparent',
 
490
            'filetorename',
 
491
            'filetomodify',
 
492
            'filetoremove',
 
493
            'filetoleave']
 
494
            )
 
495
        this_tree.commit('create_files')
 
496
        other_dir = this_tree.bzrdir.sprout('other')
 
497
        other_tree = other_dir.open_workingtree()
 
498
        other_tree.lock_write()
 
499
        # perform the needed actions on the files and dirs.
 
500
        try:
 
501
            other_tree.rename_one('dirtorename', 'renameddir')
 
502
            other_tree.rename_one('dirtoreparent', 'renameddir/reparenteddir')
 
503
            other_tree.rename_one('filetorename', 'renamedfile')
 
504
            other_tree.rename_one('filetoreparent', 'renameddir/reparentedfile')
 
505
            other_tree.remove(['dirtoremove', 'filetoremove'])
 
506
            self.build_tree_contents([
 
507
                ('other/newdir/', ),
 
508
                ('other/filetomodify', 'new content'),
 
509
                ('other/newfile', 'new file content')])
 
510
            other_tree.add('newfile')
 
511
            other_tree.add('newdir/')
 
512
            other_tree.commit('modify all sample files and dirs.')
 
513
        finally:
 
514
            other_tree.unlock()
 
515
        this_tree.merge_from_branch(other_tree.branch)
 
516
        reporter = CapturingReporter()
 
517
        this_tree.commit('do the commit', reporter=reporter)
 
518
        self.assertEqual([
 
519
            ('change', 'unchanged', ''),
 
520
            ('change', 'unchanged', 'dirtoleave'),
 
521
            ('change', 'unchanged', 'filetoleave'),
 
522
            ('change', 'modified', 'filetomodify'),
 
523
            ('change', 'added', 'newdir'),
 
524
            ('change', 'added', 'newfile'),
 
525
            ('renamed', 'renamed', 'dirtorename', 'renameddir'),
 
526
            ('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
 
527
            ('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
 
528
            ('renamed', 'renamed', 'filetorename', 'renamedfile'),
 
529
            ('deleted', 'dirtoremove'),
 
530
            ('deleted', 'filetoremove'),
 
531
            ],
 
532
            reporter.calls)
 
533
 
 
534
    def test_commit_removals_respects_filespec(self):
 
535
        """Commit respects the specified_files for removals."""
 
536
        tree = self.make_branch_and_tree('.')
 
537
        self.build_tree(['a', 'b'])
 
538
        tree.add(['a', 'b'])
 
539
        tree.commit('added a, b')
 
540
        tree.remove(['a', 'b'])
 
541
        tree.commit('removed a', specific_files='a')
 
542
        basis = tree.basis_tree()
 
543
        tree.lock_read()
 
544
        try:
 
545
            self.assertIs(None, basis.path2id('a'))
 
546
            self.assertFalse(basis.path2id('b') is None)
 
547
        finally:
 
548
            tree.unlock()
 
549
 
 
550
    def test_commit_saves_1ms_timestamp(self):
 
551
        """Passing in a timestamp is saved with 1ms resolution"""
 
552
        tree = self.make_branch_and_tree('.')
 
553
        self.build_tree(['a'])
 
554
        tree.add('a')
 
555
        tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
 
556
                    rev_id='a1')
 
557
 
 
558
        rev = tree.branch.repository.get_revision('a1')
 
559
        self.assertEqual(1153248633.419, rev.timestamp)
 
560
 
 
561
    def test_commit_has_1ms_resolution(self):
 
562
        """Allowing commit to generate the timestamp also has 1ms resolution"""
 
563
        tree = self.make_branch_and_tree('.')
 
564
        self.build_tree(['a'])
 
565
        tree.add('a')
 
566
        tree.commit('added a', rev_id='a1')
 
567
 
 
568
        rev = tree.branch.repository.get_revision('a1')
 
569
        timestamp = rev.timestamp
 
570
        timestamp_1ms = round(timestamp, 3)
 
571
        self.assertEqual(timestamp_1ms, timestamp)
 
572
 
 
573
    def assertBasisTreeKind(self, kind, tree, file_id):
 
574
        basis = tree.basis_tree()
 
575
        basis.lock_read()
 
576
        try:
 
577
            self.assertEqual(kind, basis.kind(file_id))
 
578
        finally:
 
579
            basis.unlock()
 
580
 
 
581
    def test_commit_kind_changes(self):
 
582
        if not osutils.has_symlinks():
 
583
            raise tests.TestSkipped('Test requires symlink support')
 
584
        tree = self.make_branch_and_tree('.')
 
585
        os.symlink('target', 'name')
 
586
        tree.add('name', 'a-file-id')
 
587
        tree.commit('Added a symlink')
 
588
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
 
589
 
 
590
        os.unlink('name')
 
591
        self.build_tree(['name'])
 
592
        tree.commit('Changed symlink to file')
 
593
        self.assertBasisTreeKind('file', tree, 'a-file-id')
 
594
 
 
595
        os.unlink('name')
 
596
        os.symlink('target', 'name')
 
597
        tree.commit('file to symlink')
 
598
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
 
599
 
 
600
        os.unlink('name')
 
601
        os.mkdir('name')
 
602
        tree.commit('symlink to directory')
 
603
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
 
604
 
 
605
        os.rmdir('name')
 
606
        os.symlink('target', 'name')
 
607
        tree.commit('directory to symlink')
 
608
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
 
609
 
 
610
        # prepare for directory <-> file tests
 
611
        os.unlink('name')
 
612
        os.mkdir('name')
 
613
        tree.commit('symlink to directory')
 
614
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
 
615
 
 
616
        os.rmdir('name')
 
617
        self.build_tree(['name'])
 
618
        tree.commit('Changed directory to file')
 
619
        self.assertBasisTreeKind('file', tree, 'a-file-id')
 
620
 
 
621
        os.unlink('name')
 
622
        os.mkdir('name')
 
623
        tree.commit('file to directory')
 
624
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
 
625
 
 
626
    def test_commit_unversioned_specified(self):
 
627
        """Commit should raise if specified files isn't in basis or worktree"""
 
628
        tree = self.make_branch_and_tree('.')
 
629
        self.assertRaises(errors.PathsNotVersionedError, tree.commit, 
 
630
                          'message', specific_files=['bogus'])
 
631
 
 
632
    class Callback(object):
 
633
        
 
634
        def __init__(self, message, testcase):
 
635
            self.called = False
 
636
            self.message = message
 
637
            self.testcase = testcase
 
638
 
 
639
        def __call__(self, commit_obj):
 
640
            self.called = True
 
641
            self.testcase.assertTrue(isinstance(commit_obj, Commit))
 
642
            return self.message
 
643
 
 
644
    def test_commit_callback(self):
 
645
        """Commit should invoke a callback to get the message"""
 
646
 
 
647
        tree = self.make_branch_and_tree('.')
 
648
        try:
 
649
            tree.commit()
 
650
        except Exception, e:
 
651
            self.assertTrue(isinstance(e, BzrError))
 
652
            self.assertEqual('The message or message_callback keyword'
 
653
                             ' parameter is required for commit().', str(e))
 
654
        else:
 
655
            self.fail('exception not raised')
 
656
        cb = self.Callback(u'commit 1', self)
 
657
        tree.commit(message_callback=cb)
 
658
        self.assertTrue(cb.called)
 
659
        repository = tree.branch.repository
 
660
        message = repository.get_revision(tree.last_revision()).message
 
661
        self.assertEqual('commit 1', message)
 
662
 
 
663
    def test_no_callback_pointless(self):
 
664
        """Callback should not be invoked for pointless commit"""
 
665
        tree = self.make_branch_and_tree('.')
 
666
        cb = self.Callback(u'commit 2', self)
 
667
        self.assertRaises(PointlessCommit, tree.commit, message_callback=cb, 
 
668
                          allow_pointless=False)
 
669
        self.assertFalse(cb.called)
 
670
 
 
671
    def test_no_callback_netfailure(self):
 
672
        """Callback should not be invoked if connectivity fails"""
 
673
        tree = self.make_branch_and_tree('.')
 
674
        cb = self.Callback(u'commit 2', self)
 
675
        repository = tree.branch.repository
 
676
        # simulate network failure
 
677
        def raise_(self, arg, arg2):
 
678
            raise errors.NoSuchFile('foo')
 
679
        repository.add_inventory = raise_
 
680
        self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
 
681
        self.assertFalse(cb.called)