~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_commit.py

Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 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
18
18
import os
19
19
 
20
20
import bzrlib
21
 
from bzrlib.tests import TestCaseWithTransport
 
21
from bzrlib import (
 
22
    errors,
 
23
    lockdir,
 
24
    osutils,
 
25
    tests,
 
26
    )
22
27
from bzrlib.branch import Branch
23
28
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
24
 
from bzrlib.workingtree import WorkingTree
25
29
from bzrlib.commit import Commit, NullCommitReporter
26
30
from bzrlib.config import BranchConfig
27
31
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed, 
28
32
                           LockContention)
 
33
from bzrlib.tests import TestCaseWithTransport
 
34
from bzrlib.workingtree import WorkingTree
29
35
 
30
36
 
31
37
# TODO: Test commit with some added, and added-but-missing files
217
223
        wt.move(['hello'], 'a')
218
224
        r2 = 'test@rev-2'
219
225
        wt.commit('two', rev_id=r2, allow_pointless=False)
220
 
        self.check_inventory_shape(wt.read_working_inventory(),
221
 
                                   ['a', 'a/hello', 'b'])
 
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()
222
232
 
223
233
        wt.move(['b'], 'a')
224
234
        r3 = 'test@rev-3'
225
235
        wt.commit('three', rev_id=r3, allow_pointless=False)
226
 
        self.check_inventory_shape(wt.read_working_inventory(),
227
 
                                   ['a', 'a/hello', 'a/b'])
228
 
        self.check_inventory_shape(b.repository.get_revision_inventory(r3),
229
 
                                   ['a', 'a/hello', 'a/b'])
 
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()
230
244
 
231
245
        wt.move(['a/hello'], 'a/b')
232
246
        r4 = 'test@rev-4'
233
247
        wt.commit('four', rev_id=r4, allow_pointless=False)
234
 
        self.check_inventory_shape(wt.read_working_inventory(),
235
 
                                   ['a', 'a/b/hello', 'a/b'])
 
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()
236
254
 
237
255
        inv = b.repository.get_revision_inventory(r4)
238
256
        eq(inv['hello-id'].revision, r4)
239
257
        eq(inv['a-id'].revision, r1)
240
258
        eq(inv['b-id'].revision, r3)
241
 
        
 
259
 
242
260
    def test_removed_commit(self):
243
261
        """Commit with a removed file"""
244
262
        wt = self.make_branch_and_tree('.')
339
357
                                                      allow_pointless=True,
340
358
                                                      rev_id='B',
341
359
                                                      working_tree=wt)
342
 
            self.assertEqual(Testament.from_revision(branch.repository,
343
 
                             'B').as_short_text(),
 
360
            def sign(text):
 
361
                return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
 
362
            self.assertEqual(sign(Testament.from_revision(branch.repository,
 
363
                             'B').as_short_text()),
344
364
                             branch.repository.get_signature_text('B'))
345
365
        finally:
346
366
            bzrlib.gpg.GPGStrategy = oldstrategy
407
427
        bound = master.sprout('bound')
408
428
        wt = bound.open_workingtree()
409
429
        wt.branch.set_bound_location(os.path.realpath('master'))
 
430
 
 
431
        orig_default = lockdir._DEFAULT_TIMEOUT_SECONDS
410
432
        master_branch.lock_write()
411
433
        try:
 
434
            lockdir._DEFAULT_TIMEOUT_SECONDS = 1
412
435
            self.assertRaises(LockContention, wt.commit, 'silly')
413
436
        finally:
 
437
            lockdir._DEFAULT_TIMEOUT_SECONDS = orig_default
414
438
            master_branch.unlock()
415
439
 
416
440
    def test_commit_bound_merge(self):
436
460
        # do a merge into the bound branch from other, and then change the
437
461
        # content file locally to force a new revision (rather than using the
438
462
        # revision from other). This forces extra processing in commit.
439
 
        self.merge(other_tree.branch, bound_tree)
 
463
        bound_tree.merge_from_branch(other_tree.branch)
440
464
        self.build_tree_contents([('bound/content_file', 'change in bound\n')])
441
465
 
442
466
        # before #34959 was fixed, this failed with 'revision not present in
490
514
            other_tree.commit('modify all sample files and dirs.')
491
515
        finally:
492
516
            other_tree.unlock()
493
 
        self.merge(other_tree.branch, this_tree)
 
517
        this_tree.merge_from_branch(other_tree.branch)
494
518
        reporter = CapturingReporter()
495
519
        this_tree.commit('do the commit', reporter=reporter)
496
520
        self.assertEqual([
 
521
            ('change', 'unchanged', ''),
497
522
            ('change', 'unchanged', 'dirtoleave'),
498
523
            ('change', 'unchanged', 'filetoleave'),
499
524
            ('change', 'modified', 'filetomodify'),
507
532
            ('deleted', 'filetoremove'),
508
533
            ],
509
534
            reporter.calls)
 
535
 
 
536
    def test_commit_removals_respects_filespec(self):
 
537
        """Commit respects the specified_files for removals."""
 
538
        tree = self.make_branch_and_tree('.')
 
539
        self.build_tree(['a', 'b'])
 
540
        tree.add(['a', 'b'])
 
541
        tree.commit('added a, b')
 
542
        tree.remove(['a', 'b'])
 
543
        tree.commit('removed a', specific_files='a')
 
544
        basis = tree.basis_tree()
 
545
        tree.lock_read()
 
546
        try:
 
547
            self.assertIs(None, basis.path2id('a'))
 
548
            self.assertFalse(basis.path2id('b') is None)
 
549
        finally:
 
550
            tree.unlock()
 
551
 
 
552
    def test_commit_saves_1ms_timestamp(self):
 
553
        """Passing in a timestamp is saved with 1ms resolution"""
 
554
        tree = self.make_branch_and_tree('.')
 
555
        self.build_tree(['a'])
 
556
        tree.add('a')
 
557
        tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
 
558
                    rev_id='a1')
 
559
 
 
560
        rev = tree.branch.repository.get_revision('a1')
 
561
        self.assertEqual(1153248633.419, rev.timestamp)
 
562
 
 
563
    def test_commit_has_1ms_resolution(self):
 
564
        """Allowing commit to generate the timestamp also has 1ms resolution"""
 
565
        tree = self.make_branch_and_tree('.')
 
566
        self.build_tree(['a'])
 
567
        tree.add('a')
 
568
        tree.commit('added a', rev_id='a1')
 
569
 
 
570
        rev = tree.branch.repository.get_revision('a1')
 
571
        timestamp = rev.timestamp
 
572
        timestamp_1ms = round(timestamp, 3)
 
573
        self.assertEqual(timestamp_1ms, timestamp)
 
574
 
 
575
    def assertBasisTreeKind(self, kind, tree, file_id):
 
576
        basis = tree.basis_tree()
 
577
        basis.lock_read()
 
578
        try:
 
579
            self.assertEqual(kind, basis.kind(file_id))
 
580
        finally:
 
581
            basis.unlock()
 
582
 
 
583
    def test_commit_kind_changes(self):
 
584
        if not osutils.has_symlinks():
 
585
            raise tests.TestSkipped('Test requires symlink support')
 
586
        tree = self.make_branch_and_tree('.')
 
587
        os.symlink('target', 'name')
 
588
        tree.add('name', 'a-file-id')
 
589
        tree.commit('Added a symlink')
 
590
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
 
591
 
 
592
        os.unlink('name')
 
593
        self.build_tree(['name'])
 
594
        tree.commit('Changed symlink to file')
 
595
        self.assertBasisTreeKind('file', tree, 'a-file-id')
 
596
 
 
597
        os.unlink('name')
 
598
        os.symlink('target', 'name')
 
599
        tree.commit('file to symlink')
 
600
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
 
601
 
 
602
        os.unlink('name')
 
603
        os.mkdir('name')
 
604
        tree.commit('symlink to directory')
 
605
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
 
606
 
 
607
        os.rmdir('name')
 
608
        os.symlink('target', 'name')
 
609
        tree.commit('directory to symlink')
 
610
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
 
611
 
 
612
        # prepare for directory <-> file tests
 
613
        os.unlink('name')
 
614
        os.mkdir('name')
 
615
        tree.commit('symlink to directory')
 
616
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
 
617
 
 
618
        os.rmdir('name')
 
619
        self.build_tree(['name'])
 
620
        tree.commit('Changed directory to file')
 
621
        self.assertBasisTreeKind('file', tree, 'a-file-id')
 
622
 
 
623
        os.unlink('name')
 
624
        os.mkdir('name')
 
625
        tree.commit('file to directory')
 
626
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
 
627
 
 
628
    def test_commit_unversioned_specified(self):
 
629
        """Commit should raise if specified files isn't in basis or worktree"""
 
630
        tree = self.make_branch_and_tree('.')
 
631
        self.assertRaises(errors.PathsNotVersionedError, tree.commit, 
 
632
                          'message', specific_files=['bogus'])
 
633
 
 
634
    class Callback(object):
 
635
        
 
636
        def __init__(self, message, testcase):
 
637
            self.called = False
 
638
            self.message = message
 
639
            self.testcase = testcase
 
640
 
 
641
        def __call__(self, commit_obj):
 
642
            self.called = True
 
643
            self.testcase.assertTrue(isinstance(commit_obj, Commit))
 
644
            return self.message
 
645
 
 
646
    def test_commit_callback(self):
 
647
        """Commit should invoke a callback to get the message"""
 
648
 
 
649
        tree = self.make_branch_and_tree('.')
 
650
        try:
 
651
            tree.commit()
 
652
        except Exception, e:
 
653
            self.assertTrue(isinstance(e, BzrError))
 
654
            self.assertEqual('The message or message_callback keyword'
 
655
                             ' parameter is required for commit().', str(e))
 
656
        else:
 
657
            self.fail('exception not raised')
 
658
        cb = self.Callback(u'commit 1', self)
 
659
        tree.commit(message_callback=cb)
 
660
        self.assertTrue(cb.called)
 
661
        repository = tree.branch.repository
 
662
        message = repository.get_revision(tree.last_revision()).message
 
663
        self.assertEqual('commit 1', message)
 
664
 
 
665
    def test_no_callback_pointless(self):
 
666
        """Callback should not be invoked for pointless commit"""
 
667
        tree = self.make_branch_and_tree('.')
 
668
        cb = self.Callback(u'commit 2', self)
 
669
        self.assertRaises(PointlessCommit, tree.commit, message_callback=cb, 
 
670
                          allow_pointless=False)
 
671
        self.assertFalse(cb.called)
 
672
 
 
673
    def test_no_callback_netfailure(self):
 
674
        """Callback should not be invoked if connectivity fails"""
 
675
        tree = self.make_branch_and_tree('.')
 
676
        cb = self.Callback(u'commit 2', self)
 
677
        repository = tree.branch.repository
 
678
        # simulate network failure
 
679
        def raise_(self, arg, arg2):
 
680
            raise errors.NoSuchFile('foo')
 
681
        repository.add_inventory = raise_
 
682
        self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
 
683
        self.assertFalse(cb.called)