/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_commit.py

  • Committer: John Arbash Meinel
  • Date: 2006-09-23 16:53:08 UTC
  • mto: This revision was merged to the branch mainline in revision 2036.
  • Revision ID: john@arbash-meinel.com-20060923165308-821b9c574cfea136
Set the external_attr bits so Windows respects our directories

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 by 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
 
 
18
import os
 
19
 
 
20
import bzrlib
 
21
from bzrlib import errors
 
22
from bzrlib.tests import TestCaseWithTransport
 
23
from bzrlib.branch import Branch
 
24
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
 
25
from bzrlib.workingtree import WorkingTree
 
26
from bzrlib.commit import Commit, NullCommitReporter
 
27
from bzrlib.config import BranchConfig
 
28
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed, 
 
29
                           LockContention)
 
30
 
 
31
 
 
32
# TODO: Test commit with some added, and added-but-missing files
 
33
 
 
34
class MustSignConfig(BranchConfig):
 
35
 
 
36
    def signature_needed(self):
 
37
        return True
 
38
 
 
39
    def gpg_signing_command(self):
 
40
        return ['cat', '-']
 
41
 
 
42
 
 
43
class BranchWithHooks(BranchConfig):
 
44
 
 
45
    def post_commit(self):
 
46
        return "bzrlib.ahook bzrlib.ahook"
 
47
 
 
48
 
 
49
class CapturingReporter(NullCommitReporter):
 
50
    """This reporter captures the calls made to it for evaluation later."""
 
51
 
 
52
    def __init__(self):
 
53
        # a list of the calls this received
 
54
        self.calls = []
 
55
 
 
56
    def snapshot_change(self, change, path):
 
57
        self.calls.append(('change', change, path))
 
58
 
 
59
    def deleted(self, file_id):
 
60
        self.calls.append(('deleted', file_id))
 
61
 
 
62
    def missing(self, path):
 
63
        self.calls.append(('missing', path))
 
64
 
 
65
    def renamed(self, change, old_path, new_path):
 
66
        self.calls.append(('renamed', change, old_path, new_path))
 
67
 
 
68
 
 
69
class TestCommit(TestCaseWithTransport):
 
70
 
 
71
    def test_simple_commit(self):
 
72
        """Commit and check two versions of a single file."""
 
73
        wt = self.make_branch_and_tree('.')
 
74
        b = wt.branch
 
75
        file('hello', 'w').write('hello world')
 
76
        wt.add('hello')
 
77
        wt.commit(message='add hello')
 
78
        file_id = wt.path2id('hello')
 
79
 
 
80
        file('hello', 'w').write('version 2')
 
81
        wt.commit(message='commit 2')
 
82
 
 
83
        eq = self.assertEquals
 
84
        eq(b.revno(), 2)
 
85
        rh = b.revision_history()
 
86
        rev = b.repository.get_revision(rh[0])
 
87
        eq(rev.message, 'add hello')
 
88
 
 
89
        tree1 = b.repository.revision_tree(rh[0])
 
90
        text = tree1.get_file_text(file_id)
 
91
        eq(text, 'hello world')
 
92
 
 
93
        tree2 = b.repository.revision_tree(rh[1])
 
94
        eq(tree2.get_file_text(file_id), 'version 2')
 
95
 
 
96
    def test_delete_commit(self):
 
97
        """Test a commit with a deleted file"""
 
98
        wt = self.make_branch_and_tree('.')
 
99
        b = wt.branch
 
100
        file('hello', 'w').write('hello world')
 
101
        wt.add(['hello'], ['hello-id'])
 
102
        wt.commit(message='add hello')
 
103
 
 
104
        os.remove('hello')
 
105
        wt.commit('removed hello', rev_id='rev2')
 
106
 
 
107
        tree = b.repository.revision_tree('rev2')
 
108
        self.assertFalse(tree.has_id('hello-id'))
 
109
 
 
110
    def test_pointless_commit(self):
 
111
        """Commit refuses unless there are changes or it's forced."""
 
112
        wt = self.make_branch_and_tree('.')
 
113
        b = wt.branch
 
114
        file('hello', 'w').write('hello')
 
115
        wt.add(['hello'])
 
116
        wt.commit(message='add hello')
 
117
        self.assertEquals(b.revno(), 1)
 
118
        self.assertRaises(PointlessCommit,
 
119
                          wt.commit,
 
120
                          message='fails',
 
121
                          allow_pointless=False)
 
122
        self.assertEquals(b.revno(), 1)
 
123
        
 
124
    def test_commit_empty(self):
 
125
        """Commiting an empty tree works."""
 
126
        wt = self.make_branch_and_tree('.')
 
127
        b = wt.branch
 
128
        wt.commit(message='empty tree', allow_pointless=True)
 
129
        self.assertRaises(PointlessCommit,
 
130
                          wt.commit,
 
131
                          message='empty tree',
 
132
                          allow_pointless=False)
 
133
        wt.commit(message='empty tree', allow_pointless=True)
 
134
        self.assertEquals(b.revno(), 2)
 
135
 
 
136
    def test_selective_delete(self):
 
137
        """Selective commit in tree with deletions"""
 
138
        wt = self.make_branch_and_tree('.')
 
139
        b = wt.branch
 
140
        file('hello', 'w').write('hello')
 
141
        file('buongia', 'w').write('buongia')
 
142
        wt.add(['hello', 'buongia'],
 
143
              ['hello-id', 'buongia-id'])
 
144
        wt.commit(message='add files',
 
145
                 rev_id='test@rev-1')
 
146
        
 
147
        os.remove('hello')
 
148
        file('buongia', 'w').write('new text')
 
149
        wt.commit(message='update text',
 
150
                 specific_files=['buongia'],
 
151
                 allow_pointless=False,
 
152
                 rev_id='test@rev-2')
 
153
 
 
154
        wt.commit(message='remove hello',
 
155
                 specific_files=['hello'],
 
156
                 allow_pointless=False,
 
157
                 rev_id='test@rev-3')
 
158
 
 
159
        eq = self.assertEquals
 
160
        eq(b.revno(), 3)
 
161
 
 
162
        tree2 = b.repository.revision_tree('test@rev-2')
 
163
        self.assertTrue(tree2.has_filename('hello'))
 
164
        self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
 
165
        self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
 
166
        
 
167
        tree3 = b.repository.revision_tree('test@rev-3')
 
168
        self.assertFalse(tree3.has_filename('hello'))
 
169
        self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
 
170
 
 
171
    def test_commit_rename(self):
 
172
        """Test commit of a revision where a file is renamed."""
 
173
        tree = self.make_branch_and_tree('.')
 
174
        b = tree.branch
 
175
        self.build_tree(['hello'], line_endings='binary')
 
176
        tree.add(['hello'], ['hello-id'])
 
177
        tree.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
 
178
 
 
179
        tree.rename_one('hello', 'fruity')
 
180
        tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
 
181
 
 
182
        eq = self.assertEquals
 
183
        tree1 = b.repository.revision_tree('test@rev-1')
 
184
        eq(tree1.id2path('hello-id'), 'hello')
 
185
        eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
 
186
        self.assertFalse(tree1.has_filename('fruity'))
 
187
        self.check_inventory_shape(tree1.inventory, ['hello'])
 
188
        ie = tree1.inventory['hello-id']
 
189
        eq(ie.revision, 'test@rev-1')
 
190
 
 
191
        tree2 = b.repository.revision_tree('test@rev-2')
 
192
        eq(tree2.id2path('hello-id'), 'fruity')
 
193
        eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
 
194
        self.check_inventory_shape(tree2.inventory, ['fruity'])
 
195
        ie = tree2.inventory['hello-id']
 
196
        eq(ie.revision, 'test@rev-2')
 
197
 
 
198
    def test_reused_rev_id(self):
 
199
        """Test that a revision id cannot be reused in a branch"""
 
200
        wt = self.make_branch_and_tree('.')
 
201
        b = wt.branch
 
202
        wt.commit('initial', rev_id='test@rev-1', allow_pointless=True)
 
203
        self.assertRaises(Exception,
 
204
                          wt.commit,
 
205
                          message='reused id',
 
206
                          rev_id='test@rev-1',
 
207
                          allow_pointless=True)
 
208
 
 
209
    def test_commit_move(self):
 
210
        """Test commit of revisions with moved files and directories"""
 
211
        eq = self.assertEquals
 
212
        wt = self.make_branch_and_tree('.')
 
213
        b = wt.branch
 
214
        r1 = 'test@rev-1'
 
215
        self.build_tree(['hello', 'a/', 'b/'])
 
216
        wt.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
 
217
        wt.commit('initial', rev_id=r1, allow_pointless=False)
 
218
        wt.move(['hello'], 'a')
 
219
        r2 = 'test@rev-2'
 
220
        wt.commit('two', rev_id=r2, allow_pointless=False)
 
221
        self.check_inventory_shape(wt.read_working_inventory(),
 
222
                                   ['a', 'a/hello', 'b'])
 
223
 
 
224
        wt.move(['b'], 'a')
 
225
        r3 = 'test@rev-3'
 
226
        wt.commit('three', rev_id=r3, allow_pointless=False)
 
227
        self.check_inventory_shape(wt.read_working_inventory(),
 
228
                                   ['a', 'a/hello', 'a/b'])
 
229
        self.check_inventory_shape(b.repository.get_revision_inventory(r3),
 
230
                                   ['a', 'a/hello', 'a/b'])
 
231
 
 
232
        wt.move(['a/hello'], 'a/b')
 
233
        r4 = 'test@rev-4'
 
234
        wt.commit('four', rev_id=r4, allow_pointless=False)
 
235
        self.check_inventory_shape(wt.read_working_inventory(),
 
236
                                   ['a', 'a/b/hello', 'a/b'])
 
237
 
 
238
        inv = b.repository.get_revision_inventory(r4)
 
239
        eq(inv['hello-id'].revision, r4)
 
240
        eq(inv['a-id'].revision, r1)
 
241
        eq(inv['b-id'].revision, r3)
 
242
        
 
243
    def test_removed_commit(self):
 
244
        """Commit with a removed file"""
 
245
        wt = self.make_branch_and_tree('.')
 
246
        b = wt.branch
 
247
        file('hello', 'w').write('hello world')
 
248
        wt.add(['hello'], ['hello-id'])
 
249
        wt.commit(message='add hello')
 
250
        wt.remove('hello')
 
251
        wt.commit('removed hello', rev_id='rev2')
 
252
 
 
253
        tree = b.repository.revision_tree('rev2')
 
254
        self.assertFalse(tree.has_id('hello-id'))
 
255
 
 
256
    def test_committed_ancestry(self):
 
257
        """Test commit appends revisions to ancestry."""
 
258
        wt = self.make_branch_and_tree('.')
 
259
        b = wt.branch
 
260
        rev_ids = []
 
261
        for i in range(4):
 
262
            file('hello', 'w').write((str(i) * 4) + '\n')
 
263
            if i == 0:
 
264
                wt.add(['hello'], ['hello-id'])
 
265
            rev_id = 'test@rev-%d' % (i+1)
 
266
            rev_ids.append(rev_id)
 
267
            wt.commit(message='rev %d' % (i+1),
 
268
                     rev_id=rev_id)
 
269
        eq = self.assertEquals
 
270
        eq(b.revision_history(), rev_ids)
 
271
        for i in range(4):
 
272
            anc = b.repository.get_ancestry(rev_ids[i])
 
273
            eq(anc, [None] + rev_ids[:i+1])
 
274
 
 
275
    def test_commit_new_subdir_child_selective(self):
 
276
        wt = self.make_branch_and_tree('.')
 
277
        b = wt.branch
 
278
        self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
 
279
        wt.add(['dir', 'dir/file1', 'dir/file2'],
 
280
              ['dirid', 'file1id', 'file2id'])
 
281
        wt.commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
 
282
        inv = b.repository.get_inventory('1')
 
283
        self.assertEqual('1', inv['dirid'].revision)
 
284
        self.assertEqual('1', inv['file1id'].revision)
 
285
        # FIXME: This should raise a KeyError I think, rbc20051006
 
286
        self.assertRaises(BzrError, inv.__getitem__, 'file2id')
 
287
 
 
288
    def test_strict_commit(self):
 
289
        """Try and commit with unknown files and strict = True, should fail."""
 
290
        from bzrlib.errors import StrictCommitFailed
 
291
        wt = self.make_branch_and_tree('.')
 
292
        b = wt.branch
 
293
        file('hello', 'w').write('hello world')
 
294
        wt.add('hello')
 
295
        file('goodbye', 'w').write('goodbye cruel world!')
 
296
        self.assertRaises(StrictCommitFailed, wt.commit,
 
297
            message='add hello but not goodbye', strict=True)
 
298
 
 
299
    def test_strict_commit_without_unknowns(self):
 
300
        """Try and commit with no unknown files and strict = True,
 
301
        should work."""
 
302
        from bzrlib.errors import StrictCommitFailed
 
303
        wt = self.make_branch_and_tree('.')
 
304
        b = wt.branch
 
305
        file('hello', 'w').write('hello world')
 
306
        wt.add('hello')
 
307
        wt.commit(message='add hello', strict=True)
 
308
 
 
309
    def test_nonstrict_commit(self):
 
310
        """Try and commit with unknown files and strict = False, should work."""
 
311
        wt = self.make_branch_and_tree('.')
 
312
        b = wt.branch
 
313
        file('hello', 'w').write('hello world')
 
314
        wt.add('hello')
 
315
        file('goodbye', 'w').write('goodbye cruel world!')
 
316
        wt.commit(message='add hello but not goodbye', strict=False)
 
317
 
 
318
    def test_nonstrict_commit_without_unknowns(self):
 
319
        """Try and commit with no unknown files and strict = False,
 
320
        should work."""
 
321
        wt = self.make_branch_and_tree('.')
 
322
        b = wt.branch
 
323
        file('hello', 'w').write('hello world')
 
324
        wt.add('hello')
 
325
        wt.commit(message='add hello', strict=False)
 
326
 
 
327
    def test_signed_commit(self):
 
328
        import bzrlib.gpg
 
329
        import bzrlib.commit as commit
 
330
        oldstrategy = bzrlib.gpg.GPGStrategy
 
331
        wt = self.make_branch_and_tree('.')
 
332
        branch = wt.branch
 
333
        wt.commit("base", allow_pointless=True, rev_id='A')
 
334
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
335
        try:
 
336
            from bzrlib.testament import Testament
 
337
            # monkey patch gpg signing mechanism
 
338
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
 
339
            commit.Commit(config=MustSignConfig(branch)).commit(message="base",
 
340
                                                      allow_pointless=True,
 
341
                                                      rev_id='B',
 
342
                                                      working_tree=wt)
 
343
            self.assertEqual(Testament.from_revision(branch.repository,
 
344
                             'B').as_short_text(),
 
345
                             branch.repository.get_signature_text('B'))
 
346
        finally:
 
347
            bzrlib.gpg.GPGStrategy = oldstrategy
 
348
 
 
349
    def test_commit_failed_signature(self):
 
350
        import bzrlib.gpg
 
351
        import bzrlib.commit as commit
 
352
        oldstrategy = bzrlib.gpg.GPGStrategy
 
353
        wt = self.make_branch_and_tree('.')
 
354
        branch = wt.branch
 
355
        wt.commit("base", allow_pointless=True, rev_id='A')
 
356
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
357
        try:
 
358
            from bzrlib.testament import Testament
 
359
            # monkey patch gpg signing mechanism
 
360
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
 
361
            config = MustSignConfig(branch)
 
362
            self.assertRaises(SigningFailed,
 
363
                              commit.Commit(config=config).commit,
 
364
                              message="base",
 
365
                              allow_pointless=True,
 
366
                              rev_id='B',
 
367
                              working_tree=wt)
 
368
            branch = Branch.open(self.get_url('.'))
 
369
            self.assertEqual(branch.revision_history(), ['A'])
 
370
            self.failIf(branch.repository.has_revision('B'))
 
371
        finally:
 
372
            bzrlib.gpg.GPGStrategy = oldstrategy
 
373
 
 
374
    def test_commit_invokes_hooks(self):
 
375
        import bzrlib.commit as commit
 
376
        wt = self.make_branch_and_tree('.')
 
377
        branch = wt.branch
 
378
        calls = []
 
379
        def called(branch, rev_id):
 
380
            calls.append('called')
 
381
        bzrlib.ahook = called
 
382
        try:
 
383
            config = BranchWithHooks(branch)
 
384
            commit.Commit(config=config).commit(
 
385
                            message = "base",
 
386
                            allow_pointless=True,
 
387
                            rev_id='A', working_tree = wt)
 
388
            self.assertEqual(['called', 'called'], calls)
 
389
        finally:
 
390
            del bzrlib.ahook
 
391
 
 
392
    def test_commit_object_doesnt_set_nick(self):
 
393
        # using the Commit object directly does not set the branch nick.
 
394
        wt = self.make_branch_and_tree('.')
 
395
        c = Commit()
 
396
        c.commit(working_tree=wt, message='empty tree', allow_pointless=True)
 
397
        self.assertEquals(wt.branch.revno(), 1)
 
398
        self.assertEqual({},
 
399
                         wt.branch.repository.get_revision(
 
400
                            wt.branch.last_revision()).properties)
 
401
 
 
402
    def test_safe_master_lock(self):
 
403
        os.mkdir('master')
 
404
        master = BzrDirMetaFormat1().initialize('master')
 
405
        master.create_repository()
 
406
        master_branch = master.create_branch()
 
407
        master.create_workingtree()
 
408
        bound = master.sprout('bound')
 
409
        wt = bound.open_workingtree()
 
410
        wt.branch.set_bound_location(os.path.realpath('master'))
 
411
        master_branch.lock_write()
 
412
        try:
 
413
            self.assertRaises(LockContention, wt.commit, 'silly')
 
414
        finally:
 
415
            master_branch.unlock()
 
416
 
 
417
    def test_commit_bound_merge(self):
 
418
        # see bug #43959; commit of a merge in a bound branch fails to push
 
419
        # the new commit into the master
 
420
        master_branch = self.make_branch('master')
 
421
        bound_tree = self.make_branch_and_tree('bound')
 
422
        bound_tree.branch.bind(master_branch)
 
423
 
 
424
        self.build_tree_contents([('bound/content_file', 'initial contents\n')])
 
425
        bound_tree.add(['content_file'])
 
426
        bound_tree.commit(message='woo!')
 
427
 
 
428
        other_bzrdir = master_branch.bzrdir.sprout('other')
 
429
        other_tree = other_bzrdir.open_workingtree()
 
430
 
 
431
        # do a commit to the the other branch changing the content file so
 
432
        # that our commit after merging will have a merged revision in the
 
433
        # content file history.
 
434
        self.build_tree_contents([('other/content_file', 'change in other\n')])
 
435
        other_tree.commit('change in other')
 
436
 
 
437
        # do a merge into the bound branch from other, and then change the
 
438
        # content file locally to force a new revision (rather than using the
 
439
        # revision from other). This forces extra processing in commit.
 
440
        bound_tree.merge_from_branch(other_tree.branch)
 
441
        self.build_tree_contents([('bound/content_file', 'change in bound\n')])
 
442
 
 
443
        # before #34959 was fixed, this failed with 'revision not present in
 
444
        # weave' when trying to implicitly push from the bound branch to the master
 
445
        bound_tree.commit(message='commit of merge in bound tree')
 
446
 
 
447
    def test_commit_reporting_after_merge(self):
 
448
        # when doing a commit of a merge, the reporter needs to still 
 
449
        # be called for each item that is added/removed/deleted.
 
450
        this_tree = self.make_branch_and_tree('this')
 
451
        # we need a bunch of files and dirs, to perform one action on each.
 
452
        self.build_tree([
 
453
            'this/dirtorename/',
 
454
            'this/dirtoreparent/',
 
455
            'this/dirtoleave/',
 
456
            'this/dirtoremove/',
 
457
            'this/filetoreparent',
 
458
            'this/filetorename',
 
459
            'this/filetomodify',
 
460
            'this/filetoremove',
 
461
            'this/filetoleave']
 
462
            )
 
463
        this_tree.add([
 
464
            'dirtorename',
 
465
            'dirtoreparent',
 
466
            'dirtoleave',
 
467
            'dirtoremove',
 
468
            'filetoreparent',
 
469
            'filetorename',
 
470
            'filetomodify',
 
471
            'filetoremove',
 
472
            'filetoleave']
 
473
            )
 
474
        this_tree.commit('create_files')
 
475
        other_dir = this_tree.bzrdir.sprout('other')
 
476
        other_tree = other_dir.open_workingtree()
 
477
        other_tree.lock_write()
 
478
        # perform the needed actions on the files and dirs.
 
479
        try:
 
480
            other_tree.rename_one('dirtorename', 'renameddir')
 
481
            other_tree.rename_one('dirtoreparent', 'renameddir/reparenteddir')
 
482
            other_tree.rename_one('filetorename', 'renamedfile')
 
483
            other_tree.rename_one('filetoreparent', 'renameddir/reparentedfile')
 
484
            other_tree.remove(['dirtoremove', 'filetoremove'])
 
485
            self.build_tree_contents([
 
486
                ('other/newdir/', ),
 
487
                ('other/filetomodify', 'new content'),
 
488
                ('other/newfile', 'new file content')])
 
489
            other_tree.add('newfile')
 
490
            other_tree.add('newdir/')
 
491
            other_tree.commit('modify all sample files and dirs.')
 
492
        finally:
 
493
            other_tree.unlock()
 
494
        this_tree.merge_from_branch(other_tree.branch)
 
495
        reporter = CapturingReporter()
 
496
        this_tree.commit('do the commit', reporter=reporter)
 
497
        self.assertEqual([
 
498
            ('change', 'unchanged', ''),
 
499
            ('change', 'unchanged', 'dirtoleave'),
 
500
            ('change', 'unchanged', 'filetoleave'),
 
501
            ('change', 'modified', 'filetomodify'),
 
502
            ('change', 'added', 'newdir'),
 
503
            ('change', 'added', 'newfile'),
 
504
            ('renamed', 'renamed', 'dirtorename', 'renameddir'),
 
505
            ('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
 
506
            ('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
 
507
            ('renamed', 'renamed', 'filetorename', 'renamedfile'),
 
508
            ('deleted', 'dirtoremove'),
 
509
            ('deleted', 'filetoremove'),
 
510
            ],
 
511
            reporter.calls)
 
512
 
 
513
    def test_commit_removals_respects_filespec(self):
 
514
        """Commit respects the specified_files for removals."""
 
515
        tree = self.make_branch_and_tree('.')
 
516
        self.build_tree(['a', 'b'])
 
517
        tree.add(['a', 'b'])
 
518
        tree.commit('added a, b')
 
519
        tree.remove(['a', 'b'])
 
520
        tree.commit('removed a', specific_files='a')
 
521
        basis = tree.basis_tree().inventory
 
522
        self.assertIs(None, basis.path2id('a'))
 
523
        self.assertFalse(basis.path2id('b') is None)
 
524
 
 
525
    def test_commit_saves_1ms_timestamp(self):
 
526
        """Passing in a timestamp is saved with 1ms resolution"""
 
527
        tree = self.make_branch_and_tree('.')
 
528
        self.build_tree(['a'])
 
529
        tree.add('a')
 
530
        tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
 
531
                    rev_id='a1')
 
532
 
 
533
        rev = tree.branch.repository.get_revision('a1')
 
534
        self.assertEqual(1153248633.419, rev.timestamp)
 
535
 
 
536
    def test_commit_has_1ms_resolution(self):
 
537
        """Allowing commit to generate the timestamp also has 1ms resolution"""
 
538
        tree = self.make_branch_and_tree('.')
 
539
        self.build_tree(['a'])
 
540
        tree.add('a')
 
541
        tree.commit('added a', rev_id='a1')
 
542
 
 
543
        rev = tree.branch.repository.get_revision('a1')
 
544
        timestamp = rev.timestamp
 
545
        timestamp_1ms = round(timestamp, 3)
 
546
        self.assertEqual(timestamp_1ms, timestamp)
 
547
 
 
548
    def test_commit_unversioned_specified(self):
 
549
        """Commit should raise if specified files isn't in basis or worktree"""
 
550
        tree = self.make_branch_and_tree('.')
 
551
        self.assertRaises(errors.PathsNotVersionedError, tree.commit, 
 
552
                          'message', specific_files=['bogus'])