89
49
        """Commit and check two versions of a single file."""
 
90
50
        wt = self.make_branch_and_tree('.')
 
92
 
        with open('hello', 'w') as f:
 
93
 
            f.write('hello world')
 
 
52
        file('hello', 'w').write('hello world')
 
95
 
        rev1 = wt.commit(message='add hello')
 
97
 
        with open('hello', 'w') as f:
 
99
 
        rev2 = wt.commit(message='commit 2')
 
101
 
        eq = self.assertEqual
 
 
54
        wt.commit(message='add hello')
 
 
55
        file_id = wt.path2id('hello')
 
 
57
        file('hello', 'w').write('version 2')
 
 
58
        wt.commit(message='commit 2')
 
 
60
        eq = self.assertEquals
 
103
 
        rev = b.repository.get_revision(rev1)
 
 
62
        rh = b.revision_history()
 
 
63
        rev = b.repository.get_revision(rh[0])
 
104
64
        eq(rev.message, 'add hello')
 
106
 
        tree1 = b.repository.revision_tree(rev1)
 
108
 
        text = tree1.get_file_text('hello')
 
110
 
        self.assertEqual(b'hello world', text)
 
112
 
        tree2 = b.repository.revision_tree(rev2)
 
114
 
        text = tree2.get_file_text('hello')
 
116
 
        self.assertEqual(b'version 2', text)
 
118
 
    def test_commit_lossy_native(self):
 
119
 
        """Attempt a lossy commit to a native branch."""
 
120
 
        wt = self.make_branch_and_tree('.')
 
122
 
        with open('hello', 'w') as f:
 
123
 
            f.write('hello world')
 
125
 
        revid = wt.commit(message='add hello', rev_id=b'revid', lossy=True)
 
126
 
        self.assertEqual(b'revid', revid)
 
128
 
    def test_commit_lossy_foreign(self):
 
129
 
        """Attempt a lossy commit to a foreign branch."""
 
130
 
        test_foreign.register_dummy_foreign_for_test(self)
 
131
 
        wt = self.make_branch_and_tree('.',
 
132
 
                                       format=test_foreign.DummyForeignVcsDirFormat())
 
134
 
        with open('hello', 'w') as f:
 
135
 
            f.write('hello world')
 
137
 
        revid = wt.commit(message='add hello', lossy=True,
 
138
 
                          timestamp=1302659388, timezone=0)
 
139
 
        self.assertEqual(b'dummy-v1:1302659388-0-UNKNOWN', revid)
 
141
 
    def test_commit_bound_lossy_foreign(self):
 
142
 
        """Attempt a lossy commit to a bzr branch bound to a foreign branch."""
 
143
 
        test_foreign.register_dummy_foreign_for_test(self)
 
144
 
        foreign_branch = self.make_branch('foreign',
 
145
 
                                          format=test_foreign.DummyForeignVcsDirFormat())
 
146
 
        wt = foreign_branch.create_checkout("local")
 
148
 
        with open('local/hello', 'w') as f:
 
149
 
            f.write('hello world')
 
151
 
        revid = wt.commit(message='add hello', lossy=True,
 
152
 
                          timestamp=1302659388, timezone=0)
 
153
 
        self.assertEqual(b'dummy-v1:1302659388-0-0', revid)
 
154
 
        self.assertEqual(b'dummy-v1:1302659388-0-0',
 
155
 
                         foreign_branch.last_revision())
 
156
 
        self.assertEqual(b'dummy-v1:1302659388-0-0',
 
157
 
                         wt.branch.last_revision())
 
159
 
    def test_missing_commit(self):
 
160
 
        """Test a commit with a missing file"""
 
161
 
        wt = self.make_branch_and_tree('.')
 
163
 
        with open('hello', 'w') as f:
 
164
 
            f.write('hello world')
 
165
 
        wt.add(['hello'], [b'hello-id'])
 
 
66
        tree1 = b.repository.revision_tree(rh[0])
 
 
67
        text = tree1.get_file_text(file_id)
 
 
68
        eq(text, 'hello world')
 
 
70
        tree2 = b.repository.revision_tree(rh[1])
 
 
71
        eq(tree2.get_file_text(file_id), 'version 2')
 
 
73
    def test_delete_commit(self):
 
 
74
        """Test a commit with a deleted file"""
 
 
75
        wt = self.make_branch_and_tree('.')
 
 
77
        file('hello', 'w').write('hello world')
 
 
78
        wt.add(['hello'], ['hello-id'])
 
166
79
        wt.commit(message='add hello')
 
168
81
        os.remove('hello')
 
169
 
        reporter = CapturingReporter()
 
170
 
        wt.commit('removed hello', rev_id=b'rev2', reporter=reporter)
 
172
 
            [('missing', u'hello'), ('deleted', u'hello')],
 
175
 
        tree = b.repository.revision_tree(b'rev2')
 
176
 
        self.assertFalse(tree.has_filename('hello'))
 
178
 
    def test_partial_commit_move(self):
 
179
 
        """Test a partial commit where a file was renamed but not committed.
 
181
 
        https://bugs.launchpad.net/bzr/+bug/83039
 
183
 
        If not handled properly, commit will try to snapshot
 
184
 
        dialog.py with olive/ as a parent, while
 
185
 
        olive/ has not been snapshotted yet.
 
187
 
        wt = self.make_branch_and_tree('.')
 
189
 
        self.build_tree(['annotate/', 'annotate/foo.py',
 
190
 
                         'olive/', 'olive/dialog.py'
 
192
 
        wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
 
193
 
        wt.commit(message='add files')
 
194
 
        wt.rename_one("olive/dialog.py", "aaa")
 
195
 
        self.build_tree_contents([('annotate/foo.py', b'modified\n')])
 
196
 
        wt.commit('renamed hello', specific_files=["annotate"])
 
 
82
        wt.commit('removed hello', rev_id='rev2')
 
 
84
        tree = b.repository.revision_tree('rev2')
 
 
85
        self.assertFalse(tree.has_id('hello-id'))
 
198
87
    def test_pointless_commit(self):
 
199
88
        """Commit refuses unless there are changes or it's forced."""
 
200
89
        wt = self.make_branch_and_tree('.')
 
202
 
        with open('hello', 'w') as f:
 
 
91
        file('hello', 'w').write('hello')
 
205
93
        wt.commit(message='add hello')
 
206
 
        self.assertEqual(b.revno(), 1)
 
 
94
        self.assertEquals(b.revno(), 1)
 
207
95
        self.assertRaises(PointlessCommit,
 
210
98
                          allow_pointless=False)
 
211
 
        self.assertEqual(b.revno(), 1)
 
 
99
        self.assertEquals(b.revno(), 1)
 
213
101
    def test_commit_empty(self):
 
214
102
        """Commiting an empty tree works."""
 
215
103
        wt = self.make_branch_and_tree('.')
 
 
220
108
                          message='empty tree',
 
221
109
                          allow_pointless=False)
 
222
110
        wt.commit(message='empty tree', allow_pointless=True)
 
223
 
        self.assertEqual(b.revno(), 2)
 
 
111
        self.assertEquals(b.revno(), 2)
 
225
113
    def test_selective_delete(self):
 
226
114
        """Selective commit in tree with deletions"""
 
227
115
        wt = self.make_branch_and_tree('.')
 
229
 
        with open('hello', 'w') as f:
 
231
 
        with open('buongia', 'w') as f:
 
 
117
        file('hello', 'w').write('hello')
 
 
118
        file('buongia', 'w').write('buongia')
 
233
119
        wt.add(['hello', 'buongia'],
 
234
 
               [b'hello-id', b'buongia-id'])
 
 
120
              ['hello-id', 'buongia-id'])
 
235
121
        wt.commit(message='add files',
 
236
 
                  rev_id=b'test@rev-1')
 
238
124
        os.remove('hello')
 
239
 
        with open('buongia', 'w') as f:
 
 
125
        file('buongia', 'w').write('new text')
 
241
126
        wt.commit(message='update text',
 
242
 
                  specific_files=['buongia'],
 
243
 
                  allow_pointless=False,
 
244
 
                  rev_id=b'test@rev-2')
 
 
127
                 specific_files=['buongia'],
 
 
128
                 allow_pointless=False,
 
246
131
        wt.commit(message='remove hello',
 
247
 
                  specific_files=['hello'],
 
248
 
                  allow_pointless=False,
 
249
 
                  rev_id=b'test@rev-3')
 
 
132
                 specific_files=['hello'],
 
 
133
                 allow_pointless=False,
 
251
 
        eq = self.assertEqual
 
 
136
        eq = self.assertEquals
 
254
 
        tree2 = b.repository.revision_tree(b'test@rev-2')
 
256
 
        self.addCleanup(tree2.unlock)
 
 
139
        tree2 = b.repository.revision_tree('test@rev-2')
 
257
140
        self.assertTrue(tree2.has_filename('hello'))
 
258
 
        self.assertEqual(tree2.get_file_text('hello'), b'hello')
 
259
 
        self.assertEqual(tree2.get_file_text('buongia'), b'new text')
 
261
 
        tree3 = b.repository.revision_tree(b'test@rev-3')
 
263
 
        self.addCleanup(tree3.unlock)
 
 
141
        self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
 
 
142
        self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
 
 
144
        tree3 = b.repository.revision_tree('test@rev-3')
 
264
145
        self.assertFalse(tree3.has_filename('hello'))
 
265
 
        self.assertEqual(tree3.get_file_text('buongia'), b'new text')
 
 
146
        self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
 
267
148
    def test_commit_rename(self):
 
268
149
        """Test commit of a revision where a file is renamed."""
 
269
150
        tree = self.make_branch_and_tree('.')
 
271
152
        self.build_tree(['hello'], line_endings='binary')
 
272
 
        tree.add(['hello'], [b'hello-id'])
 
273
 
        tree.commit(message='one', rev_id=b'test@rev-1', allow_pointless=False)
 
 
153
        tree.add(['hello'], ['hello-id'])
 
 
154
        tree.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
 
275
156
        tree.rename_one('hello', 'fruity')
 
276
 
        tree.commit(message='renamed', rev_id=b'test@rev-2',
 
277
 
                    allow_pointless=False)
 
 
157
        tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
 
279
 
        eq = self.assertEqual
 
280
 
        tree1 = b.repository.revision_tree(b'test@rev-1')
 
282
 
        self.addCleanup(tree1.unlock)
 
283
 
        eq(tree1.id2path(b'hello-id'), 'hello')
 
284
 
        eq(tree1.get_file_text('hello'), b'contents of hello\n')
 
 
159
        eq = self.assertEquals
 
 
160
        tree1 = b.repository.revision_tree('test@rev-1')
 
 
161
        eq(tree1.id2path('hello-id'), 'hello')
 
 
162
        eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
 
285
163
        self.assertFalse(tree1.has_filename('fruity'))
 
286
 
        self.check_tree_shape(tree1, ['hello'])
 
287
 
        eq(tree1.get_file_revision('hello'), b'test@rev-1')
 
 
164
        self.check_inventory_shape(tree1.inventory, ['hello'])
 
 
165
        ie = tree1.inventory['hello-id']
 
 
166
        eq(ie.revision, 'test@rev-1')
 
289
 
        tree2 = b.repository.revision_tree(b'test@rev-2')
 
291
 
        self.addCleanup(tree2.unlock)
 
292
 
        eq(tree2.id2path(b'hello-id'), 'fruity')
 
293
 
        eq(tree2.get_file_text('fruity'), b'contents of hello\n')
 
294
 
        self.check_tree_shape(tree2, ['fruity'])
 
295
 
        eq(tree2.get_file_revision('fruity'), b'test@rev-2')
 
 
168
        tree2 = b.repository.revision_tree('test@rev-2')
 
 
169
        eq(tree2.id2path('hello-id'), 'fruity')
 
 
170
        eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
 
 
171
        self.check_inventory_shape(tree2.inventory, ['fruity'])
 
 
172
        ie = tree2.inventory['hello-id']
 
 
173
        eq(ie.revision, 'test@rev-2')
 
297
175
    def test_reused_rev_id(self):
 
298
176
        """Test that a revision id cannot be reused in a branch"""
 
299
177
        wt = self.make_branch_and_tree('.')
 
301
 
        wt.commit('initial', rev_id=b'test@rev-1', allow_pointless=True)
 
 
179
        wt.commit('initial', rev_id='test@rev-1', allow_pointless=True)
 
302
180
        self.assertRaises(Exception,
 
304
182
                          message='reused id',
 
305
 
                          rev_id=b'test@rev-1',
 
306
184
                          allow_pointless=True)
 
308
186
    def test_commit_move(self):
 
309
187
        """Test commit of revisions with moved files and directories"""
 
310
 
        eq = self.assertEqual
 
 
188
        eq = self.assertEquals
 
311
189
        wt = self.make_branch_and_tree('.')
 
314
192
        self.build_tree(['hello', 'a/', 'b/'])
 
315
 
        wt.add(['hello', 'a', 'b'], [b'hello-id', b'a-id', b'b-id'])
 
 
193
        wt.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
 
316
194
        wt.commit('initial', rev_id=r1, allow_pointless=False)
 
317
195
        wt.move(['hello'], 'a')
 
319
197
        wt.commit('two', rev_id=r2, allow_pointless=False)
 
322
 
            self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
 
 
198
        self.check_inventory_shape(wt.read_working_inventory(),
 
 
199
                                   ['a', 'a/hello', 'b'])
 
326
201
        wt.move(['b'], 'a')
 
328
203
        wt.commit('three', rev_id=r3, allow_pointless=False)
 
331
 
            self.check_tree_shape(wt,
 
332
 
                                  ['a/', 'a/hello', 'a/b/'])
 
333
 
            self.check_tree_shape(b.repository.revision_tree(r3),
 
334
 
                                  ['a/', 'a/hello', 'a/b/'])
 
 
204
        self.check_inventory_shape(wt.read_working_inventory(),
 
 
205
                                   ['a', 'a/hello', 'a/b'])
 
 
206
        self.check_inventory_shape(b.repository.get_revision_inventory(r3),
 
 
207
                                   ['a', 'a/hello', 'a/b'])
 
338
209
        wt.move(['a/hello'], 'a/b')
 
340
211
        wt.commit('four', rev_id=r4, allow_pointless=False)
 
343
 
            self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
 
347
 
        inv = b.repository.get_inventory(r4)
 
348
 
        eq(inv.get_entry(b'hello-id').revision, r4)
 
349
 
        eq(inv.get_entry(b'a-id').revision, r1)
 
350
 
        eq(inv.get_entry(b'b-id').revision, r3)
 
 
212
        self.check_inventory_shape(wt.read_working_inventory(),
 
 
213
                                   ['a', 'a/b/hello', 'a/b'])
 
 
215
        inv = b.repository.get_revision_inventory(r4)
 
 
216
        eq(inv['hello-id'].revision, r4)
 
 
217
        eq(inv['a-id'].revision, r1)
 
 
218
        eq(inv['b-id'].revision, r3)
 
352
220
    def test_removed_commit(self):
 
353
221
        """Commit with a removed file"""
 
354
222
        wt = self.make_branch_and_tree('.')
 
356
 
        with open('hello', 'w') as f:
 
357
 
            f.write('hello world')
 
358
 
        wt.add(['hello'], [b'hello-id'])
 
 
224
        file('hello', 'w').write('hello world')
 
 
225
        wt.add(['hello'], ['hello-id'])
 
359
226
        wt.commit(message='add hello')
 
360
227
        wt.remove('hello')
 
361
 
        wt.commit('removed hello', rev_id=b'rev2')
 
 
228
        wt.commit('removed hello', rev_id='rev2')
 
363
 
        tree = b.repository.revision_tree(b'rev2')
 
364
 
        self.assertFalse(tree.has_filename('hello'))
 
 
230
        tree = b.repository.revision_tree('rev2')
 
 
231
        self.assertFalse(tree.has_id('hello-id'))
 
366
233
    def test_committed_ancestry(self):
 
367
234
        """Test commit appends revisions to ancestry."""
 
 
434
298
        wt = self.make_branch_and_tree('.')
 
436
 
        with open('hello', 'w') as f:
 
437
 
            f.write('hello world')
 
 
300
        file('hello', 'w').write('hello world')
 
439
302
        wt.commit(message='add hello', strict=False)
 
441
304
    def test_signed_commit(self):
 
443
 
        import breezy.commit as commit
 
444
 
        oldstrategy = breezy.gpg.GPGStrategy
 
 
306
        import bzrlib.commit as commit
 
 
307
        oldstrategy = bzrlib.gpg.GPGStrategy
 
445
308
        wt = self.make_branch_and_tree('.')
 
446
309
        branch = wt.branch
 
447
 
        wt.commit("base", allow_pointless=True, rev_id=b'A')
 
448
 
        self.assertFalse(branch.repository.has_signature_for_revision_id(b'A'))
 
 
310
        wt.commit("base", allow_pointless=True, rev_id='A')
 
 
311
        self.failIf(branch.repository.revision_store.has_id('A', 'sig'))
 
450
 
            from ..bzr.testament import Testament
 
 
313
            from bzrlib.testament import Testament
 
451
314
            # monkey patch gpg signing mechanism
 
452
 
            breezy.gpg.GPGStrategy = breezy.gpg.LoopbackGPGStrategy
 
453
 
            conf = config.MemoryStack(b'''
 
454
 
create_signatures=always
 
456
 
            commit.Commit(config_stack=conf).commit(
 
457
 
                message="base", allow_pointless=True, rev_id=b'B',
 
461
 
                return breezy.gpg.LoopbackGPGStrategy(None).sign(
 
462
 
                    text, breezy.gpg.MODE_CLEAR)
 
463
 
            self.assertEqual(sign(Testament.from_revision(branch.repository,
 
464
 
                                                          b'B').as_short_text()),
 
465
 
                             branch.repository.get_signature_text(b'B'))
 
 
315
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
 
 
316
            commit.Commit(config=MustSignConfig(branch)).commit(message="base",
 
 
317
                                                      allow_pointless=True,
 
 
320
            self.assertEqual(Testament.from_revision(branch.repository,
 
 
321
                             'B').as_short_text(),
 
 
322
                             branch.repository.revision_store.get('B', 
 
467
 
            breezy.gpg.GPGStrategy = oldstrategy
 
 
325
            bzrlib.gpg.GPGStrategy = oldstrategy
 
469
327
    def test_commit_failed_signature(self):
 
471
 
        import breezy.commit as commit
 
472
 
        oldstrategy = breezy.gpg.GPGStrategy
 
 
329
        import bzrlib.commit as commit
 
 
330
        oldstrategy = bzrlib.gpg.GPGStrategy
 
473
331
        wt = self.make_branch_and_tree('.')
 
474
332
        branch = wt.branch
 
475
 
        wt.commit("base", allow_pointless=True, rev_id=b'A')
 
476
 
        self.assertFalse(branch.repository.has_signature_for_revision_id(b'A'))
 
 
333
        wt.commit("base", allow_pointless=True, rev_id='A')
 
 
334
        self.failIf(branch.repository.revision_store.has_id('A', 'sig'))
 
 
336
            from bzrlib.testament import Testament
 
478
337
            # monkey patch gpg signing mechanism
 
479
 
            breezy.gpg.GPGStrategy = breezy.gpg.DisabledGPGStrategy
 
480
 
            conf = config.MemoryStack(b'''
 
481
 
create_signatures=always
 
483
 
            self.assertRaises(breezy.gpg.SigningFailed,
 
484
 
                              commit.Commit(config_stack=conf).commit,
 
 
338
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
 
 
339
            config = MustSignConfig(branch)
 
 
340
            self.assertRaises(SigningFailed,
 
 
341
                              commit.Commit(config=config).commit,
 
486
343
                              allow_pointless=True,
 
489
346
            branch = Branch.open(self.get_url('.'))
 
490
 
            self.assertEqual(branch.last_revision(), b'A')
 
491
 
            self.assertFalse(branch.repository.has_revision(b'B'))
 
 
347
            self.assertEqual(branch.revision_history(), ['A'])
 
 
348
            self.failIf(branch.repository.revision_store.has_id('B'))
 
493
 
            breezy.gpg.GPGStrategy = oldstrategy
 
 
350
            bzrlib.gpg.GPGStrategy = oldstrategy
 
495
352
    def test_commit_invokes_hooks(self):
 
496
 
        import breezy.commit as commit
 
 
353
        import bzrlib.commit as commit
 
497
354
        wt = self.make_branch_and_tree('.')
 
498
355
        branch = wt.branch
 
501
357
        def called(branch, rev_id):
 
502
358
            calls.append('called')
 
503
 
        breezy.ahook = called
 
 
359
        bzrlib.ahook = called
 
505
 
            conf = config.MemoryStack(b'post_commit=breezy.ahook breezy.ahook')
 
506
 
            commit.Commit(config_stack=conf).commit(
 
507
 
                message="base", allow_pointless=True, rev_id=b'A',
 
 
361
            config = BranchWithHooks(branch)
 
 
362
            commit.Commit(config=config).commit(
 
 
364
                            allow_pointless=True,
 
 
365
                            rev_id='A', working_tree = wt)
 
509
366
            self.assertEqual(['called', 'called'], calls)
 
513
 
    def test_commit_object_doesnt_set_nick(self):
 
514
 
        # using the Commit object directly does not set the branch nick.
 
515
 
        wt = self.make_branch_and_tree('.')
 
517
 
        c.commit(working_tree=wt, message='empty tree', allow_pointless=True)
 
518
 
        self.assertEqual(wt.branch.revno(), 1)
 
520
 
                         wt.branch.repository.get_revision(
 
521
 
            wt.branch.last_revision()).properties)
 
523
 
    def test_safe_master_lock(self):
 
525
 
        master = BzrDirMetaFormat1().initialize('master')
 
526
 
        master.create_repository()
 
527
 
        master_branch = master.create_branch()
 
528
 
        master.create_workingtree()
 
529
 
        bound = master.sprout('bound')
 
530
 
        wt = bound.open_workingtree()
 
531
 
        wt.branch.set_bound_location(os.path.realpath('master'))
 
532
 
        master_branch.lock_write()
 
534
 
            self.assertRaises(LockContention, wt.commit, 'silly')
 
536
 
            master_branch.unlock()
 
538
 
    def test_commit_bound_merge(self):
 
539
 
        # see bug #43959; commit of a merge in a bound branch fails to push
 
540
 
        # the new commit into the master
 
541
 
        master_branch = self.make_branch('master')
 
542
 
        bound_tree = self.make_branch_and_tree('bound')
 
543
 
        bound_tree.branch.bind(master_branch)
 
545
 
        self.build_tree_contents(
 
546
 
            [('bound/content_file', b'initial contents\n')])
 
547
 
        bound_tree.add(['content_file'])
 
548
 
        bound_tree.commit(message='woo!')
 
550
 
        other_bzrdir = master_branch.controldir.sprout('other')
 
551
 
        other_tree = other_bzrdir.open_workingtree()
 
553
 
        # do a commit to the other branch changing the content file so
 
554
 
        # that our commit after merging will have a merged revision in the
 
555
 
        # content file history.
 
556
 
        self.build_tree_contents(
 
557
 
            [('other/content_file', b'change in other\n')])
 
558
 
        other_tree.commit('change in other')
 
560
 
        # do a merge into the bound branch from other, and then change the
 
561
 
        # content file locally to force a new revision (rather than using the
 
562
 
        # revision from other). This forces extra processing in commit.
 
563
 
        bound_tree.merge_from_branch(other_tree.branch)
 
564
 
        self.build_tree_contents(
 
565
 
            [('bound/content_file', b'change in bound\n')])
 
567
 
        # before #34959 was fixed, this failed with 'revision not present in
 
568
 
        # weave' when trying to implicitly push from the bound branch to the master
 
569
 
        bound_tree.commit(message='commit of merge in bound tree')
 
571
 
    def test_commit_reporting_after_merge(self):
 
572
 
        # when doing a commit of a merge, the reporter needs to still
 
573
 
        # be called for each item that is added/removed/deleted.
 
574
 
        this_tree = self.make_branch_and_tree('this')
 
575
 
        # we need a bunch of files and dirs, to perform one action on each.
 
578
 
            'this/dirtoreparent/',
 
581
 
            'this/filetoreparent',
 
598
 
        this_tree.commit('create_files')
 
599
 
        other_dir = this_tree.controldir.sprout('other')
 
600
 
        other_tree = other_dir.open_workingtree()
 
601
 
        other_tree.lock_write()
 
602
 
        # perform the needed actions on the files and dirs.
 
604
 
            other_tree.rename_one('dirtorename', 'renameddir')
 
605
 
            other_tree.rename_one('dirtoreparent', 'renameddir/reparenteddir')
 
606
 
            other_tree.rename_one('filetorename', 'renamedfile')
 
607
 
            other_tree.rename_one(
 
608
 
                'filetoreparent', 'renameddir/reparentedfile')
 
609
 
            other_tree.remove(['dirtoremove', 'filetoremove'])
 
610
 
            self.build_tree_contents([
 
612
 
                ('other/filetomodify', b'new content'),
 
613
 
                ('other/newfile', b'new file content')])
 
614
 
            other_tree.add('newfile')
 
615
 
            other_tree.add('newdir/')
 
616
 
            other_tree.commit('modify all sample files and dirs.')
 
619
 
        this_tree.merge_from_branch(other_tree.branch)
 
620
 
        reporter = CapturingReporter()
 
621
 
        this_tree.commit('do the commit', reporter=reporter)
 
623
 
            ('change', 'modified', 'filetomodify'),
 
624
 
            ('change', 'added', 'newdir'),
 
625
 
            ('change', 'added', 'newfile'),
 
626
 
            ('renamed', 'renamed', 'dirtorename', 'renameddir'),
 
627
 
            ('renamed', 'renamed', 'filetorename', 'renamedfile'),
 
628
 
            ('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
 
629
 
            ('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
 
630
 
            ('deleted', 'dirtoremove'),
 
631
 
            ('deleted', 'filetoremove'),
 
633
 
        result = set(reporter.calls)
 
634
 
        missing = expected - result
 
635
 
        new = result - expected
 
636
 
        self.assertEqual((set(), set()), (missing, new))
 
638
 
    def test_commit_removals_respects_filespec(self):
 
639
 
        """Commit respects the specified_files for removals."""
 
640
 
        tree = self.make_branch_and_tree('.')
 
641
 
        self.build_tree(['a', 'b'])
 
643
 
        tree.commit('added a, b')
 
644
 
        tree.remove(['a', 'b'])
 
645
 
        tree.commit('removed a', specific_files='a')
 
646
 
        basis = tree.basis_tree()
 
647
 
        with tree.lock_read():
 
648
 
            self.assertFalse(basis.is_versioned('a'))
 
649
 
            self.assertTrue(basis.is_versioned('b'))
 
651
 
    def test_commit_saves_1ms_timestamp(self):
 
652
 
        """Passing in a timestamp is saved with 1ms resolution"""
 
653
 
        tree = self.make_branch_and_tree('.')
 
654
 
        self.build_tree(['a'])
 
656
 
        tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
 
659
 
        rev = tree.branch.repository.get_revision(b'a1')
 
660
 
        self.assertEqual(1153248633.419, rev.timestamp)
 
662
 
    def test_commit_has_1ms_resolution(self):
 
663
 
        """Allowing commit to generate the timestamp also has 1ms resolution"""
 
664
 
        tree = self.make_branch_and_tree('.')
 
665
 
        self.build_tree(['a'])
 
667
 
        tree.commit('added a', rev_id=b'a1')
 
669
 
        rev = tree.branch.repository.get_revision(b'a1')
 
670
 
        timestamp = rev.timestamp
 
671
 
        timestamp_1ms = round(timestamp, 3)
 
672
 
        self.assertEqual(timestamp_1ms, timestamp)
 
674
 
    def assertBasisTreeKind(self, kind, tree, path):
 
675
 
        basis = tree.basis_tree()
 
678
 
            self.assertEqual(kind, basis.kind(path))
 
682
 
    def test_unsupported_symlink_commit(self):
 
683
 
        self.requireFeature(SymlinkFeature)
 
684
 
        tree = self.make_branch_and_tree('.')
 
685
 
        self.build_tree(['hello'])
 
687
 
        tree.commit('added hello', rev_id=b'hello_id')
 
688
 
        os.symlink('hello', 'foo')
 
690
 
        tree.commit('added foo', rev_id=b'foo_id')
 
692
 
        trace.push_log_file(log)
 
693
 
        os_symlink = getattr(os, 'symlink', None)
 
696
 
            # At this point as bzr thinks symlinks are not supported
 
697
 
            # we should get a warning about symlink foo and bzr should
 
698
 
            # not think its removed.
 
700
 
            self.build_tree(['world'])
 
702
 
            tree.commit('added world', rev_id=b'world_id')
 
705
 
                os.symlink = os_symlink
 
706
 
        self.assertContainsRe(
 
708
 
            b'Ignoring "foo" as symlinks are not '
 
709
 
            b'supported on this filesystem\\.')
 
711
 
    def test_commit_kind_changes(self):
 
712
 
        self.requireFeature(SymlinkFeature)
 
713
 
        tree = self.make_branch_and_tree('.')
 
714
 
        os.symlink('target', 'name')
 
715
 
        tree.add('name', b'a-file-id')
 
716
 
        tree.commit('Added a symlink')
 
717
 
        self.assertBasisTreeKind('symlink', tree, 'name')
 
720
 
        self.build_tree(['name'])
 
721
 
        tree.commit('Changed symlink to file')
 
722
 
        self.assertBasisTreeKind('file', tree, 'name')
 
725
 
        os.symlink('target', 'name')
 
726
 
        tree.commit('file to symlink')
 
727
 
        self.assertBasisTreeKind('symlink', tree, 'name')
 
731
 
        tree.commit('symlink to directory')
 
732
 
        self.assertBasisTreeKind('directory', tree, 'name')
 
735
 
        os.symlink('target', 'name')
 
736
 
        tree.commit('directory to symlink')
 
737
 
        self.assertBasisTreeKind('symlink', tree, 'name')
 
739
 
        # prepare for directory <-> file tests
 
742
 
        tree.commit('symlink to directory')
 
743
 
        self.assertBasisTreeKind('directory', tree, 'name')
 
746
 
        self.build_tree(['name'])
 
747
 
        tree.commit('Changed directory to file')
 
748
 
        self.assertBasisTreeKind('file', tree, 'name')
 
752
 
        tree.commit('file to directory')
 
753
 
        self.assertBasisTreeKind('directory', tree, 'name')
 
755
 
    def test_commit_unversioned_specified(self):
 
756
 
        """Commit should raise if specified files isn't in basis or worktree"""
 
757
 
        tree = self.make_branch_and_tree('.')
 
758
 
        self.assertRaises(errors.PathsNotVersionedError, tree.commit,
 
759
 
                          'message', specific_files=['bogus'])
 
761
 
    class Callback(object):
 
763
 
        def __init__(self, message, testcase):
 
765
 
            self.message = message
 
766
 
            self.testcase = testcase
 
768
 
        def __call__(self, commit_obj):
 
770
 
            self.testcase.assertTrue(isinstance(commit_obj, Commit))
 
773
 
    def test_commit_callback(self):
 
774
 
        """Commit should invoke a callback to get the message"""
 
776
 
        tree = self.make_branch_and_tree('.')
 
779
 
        except Exception as e:
 
780
 
            self.assertTrue(isinstance(e, BzrError))
 
781
 
            self.assertEqual('The message or message_callback keyword'
 
782
 
                             ' parameter is required for commit().', str(e))
 
784
 
            self.fail('exception not raised')
 
785
 
        cb = self.Callback(u'commit 1', self)
 
786
 
        tree.commit(message_callback=cb)
 
787
 
        self.assertTrue(cb.called)
 
788
 
        repository = tree.branch.repository
 
789
 
        message = repository.get_revision(tree.last_revision()).message
 
790
 
        self.assertEqual('commit 1', message)
 
792
 
    def test_no_callback_pointless(self):
 
793
 
        """Callback should not be invoked for pointless commit"""
 
794
 
        tree = self.make_branch_and_tree('.')
 
795
 
        cb = self.Callback(u'commit 2', self)
 
796
 
        self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
 
797
 
                          allow_pointless=False)
 
798
 
        self.assertFalse(cb.called)
 
800
 
    def test_no_callback_netfailure(self):
 
801
 
        """Callback should not be invoked if connectivity fails"""
 
802
 
        tree = self.make_branch_and_tree('.')
 
803
 
        cb = self.Callback(u'commit 2', self)
 
804
 
        repository = tree.branch.repository
 
805
 
        # simulate network failure
 
807
 
        def raise_(self, arg, arg2, arg3=None, arg4=None):
 
808
 
            raise errors.NoSuchFile('foo')
 
809
 
        repository.add_inventory = raise_
 
810
 
        repository.add_inventory_by_delta = raise_
 
811
 
        self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
 
812
 
        self.assertFalse(cb.called)
 
814
 
    def test_selected_file_merge_commit(self):
 
815
 
        """Ensure the correct error is raised"""
 
816
 
        tree = self.make_branch_and_tree('foo')
 
817
 
        # pending merge would turn into a left parent
 
818
 
        tree.commit('commit 1')
 
819
 
        tree.add_parent_tree_id(b'example')
 
820
 
        self.build_tree(['foo/bar', 'foo/baz'])
 
821
 
        tree.add(['bar', 'baz'])
 
822
 
        err = self.assertRaises(CannotCommitSelectedFileMerge,
 
823
 
                                tree.commit, 'commit 2', specific_files=['bar', 'baz'])
 
824
 
        self.assertEqual(['bar', 'baz'], err.files)
 
825
 
        self.assertEqual('Selected-file commit of merges is not supported'
 
826
 
                         ' yet: files bar, baz', str(err))
 
828
 
    def test_commit_ordering(self):
 
829
 
        """Test of corner-case commit ordering error"""
 
830
 
        tree = self.make_branch_and_tree('.')
 
831
 
        self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
 
832
 
        tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
 
834
 
        self.build_tree(['a/c/d/'])
 
836
 
        tree.rename_one('a/z/x', 'a/c/d/x')
 
837
 
        tree.commit('test', specific_files=['a/z/y'])
 
839
 
    def test_commit_no_author(self):
 
840
 
        """The default kwarg author in MutableTree.commit should not add
 
841
 
        the 'author' revision property.
 
843
 
        tree = self.make_branch_and_tree('foo')
 
844
 
        rev_id = tree.commit('commit 1')
 
845
 
        rev = tree.branch.repository.get_revision(rev_id)
 
846
 
        self.assertFalse('author' in rev.properties)
 
847
 
        self.assertFalse('authors' in rev.properties)
 
849
 
    def test_commit_author(self):
 
850
 
        """Passing a non-empty authors kwarg to MutableTree.commit should add
 
851
 
        the 'author' revision property.
 
853
 
        tree = self.make_branch_and_tree('foo')
 
854
 
        rev_id = tree.commit(
 
856
 
            authors=['John Doe <jdoe@example.com>'])
 
857
 
        rev = tree.branch.repository.get_revision(rev_id)
 
858
 
        self.assertEqual('John Doe <jdoe@example.com>',
 
859
 
                         rev.properties['authors'])
 
860
 
        self.assertFalse('author' in rev.properties)
 
862
 
    def test_commit_empty_authors_list(self):
 
863
 
        """Passing an empty list to authors shouldn't add the property."""
 
864
 
        tree = self.make_branch_and_tree('foo')
 
865
 
        rev_id = tree.commit('commit 1', authors=[])
 
866
 
        rev = tree.branch.repository.get_revision(rev_id)
 
867
 
        self.assertFalse('author' in rev.properties)
 
868
 
        self.assertFalse('authors' in rev.properties)
 
870
 
    def test_multiple_authors(self):
 
871
 
        tree = self.make_branch_and_tree('foo')
 
872
 
        rev_id = tree.commit('commit 1',
 
873
 
                             authors=['John Doe <jdoe@example.com>',
 
874
 
                                      'Jane Rey <jrey@example.com>'])
 
875
 
        rev = tree.branch.repository.get_revision(rev_id)
 
876
 
        self.assertEqual('John Doe <jdoe@example.com>\n'
 
877
 
                         'Jane Rey <jrey@example.com>', rev.properties['authors'])
 
878
 
        self.assertFalse('author' in rev.properties)
 
880
 
    def test_author_with_newline_rejected(self):
 
881
 
        tree = self.make_branch_and_tree('foo')
 
882
 
        self.assertRaises(AssertionError, tree.commit, 'commit 1',
 
883
 
                          authors=['John\nDoe <jdoe@example.com>'])
 
885
 
    def test_commit_with_checkout_and_branch_sharing_repo(self):
 
886
 
        repo = self.make_repository('repo', shared=True)
 
887
 
        # make_branch_and_tree ignores shared repos
 
888
 
        branch = controldir.ControlDir.create_branch_convenience('repo/branch')
 
889
 
        tree2 = branch.create_checkout('repo/tree2')
 
890
 
        tree2.commit('message', rev_id=b'rev1')
 
891
 
        self.assertTrue(tree2.branch.repository.has_revision(b'rev1'))
 
894
 
class FilterExcludedTests(TestCase):
 
896
 
    def test_add_file_not_excluded(self):
 
899
 
                'fid', (None, 'newpath'),
 
900
 
                0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
 
901
 
                ('file', 'file'), (True, True))]
 
902
 
        self.assertEqual(changes, list(
 
903
 
            filter_excluded(changes, ['otherpath'])))
 
905
 
    def test_add_file_excluded(self):
 
908
 
                'fid', (None, 'newpath'),
 
909
 
                0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
 
910
 
                ('file', 'file'), (True, True))]
 
911
 
        self.assertEqual([], list(filter_excluded(changes, ['newpath'])))
 
913
 
    def test_delete_file_excluded(self):
 
916
 
                'fid', ('somepath', None),
 
917
 
                0, (False, None), ('pid', None), ('newpath', None),
 
918
 
                ('file', None), (True, None))]
 
919
 
        self.assertEqual([], list(filter_excluded(changes, ['somepath'])))
 
921
 
    def test_move_from_or_to_excluded(self):
 
924
 
                'fid', ('oldpath', 'newpath'),
 
925
 
                0, (False, False), ('pid', 'pid'), ('oldpath', 'newpath'),
 
926
 
                ('file', 'file'), (True, True))]
 
927
 
        self.assertEqual([], list(filter_excluded(changes, ['oldpath'])))
 
928
 
        self.assertEqual([], list(filter_excluded(changes, ['newpath'])))