~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_commit.py

MergeĀ fromĀ jam-storage.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005 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.tests import TestCaseInTempDir
 
22
from bzrlib.branch import Branch
 
23
from bzrlib.workingtree import WorkingTree
 
24
from bzrlib.commit import Commit
 
25
from bzrlib.config import BranchConfig
 
26
from bzrlib.errors import PointlessCommit, BzrError, SigningFailed
 
27
 
 
28
 
 
29
# TODO: Test commit with some added, and added-but-missing files
 
30
 
 
31
class MustSignConfig(BranchConfig):
 
32
 
 
33
    def signature_needed(self):
 
34
        return True
 
35
 
 
36
    def gpg_signing_command(self):
 
37
        return ['cat', '-']
 
38
 
 
39
 
 
40
class BranchWithHooks(BranchConfig):
 
41
 
 
42
    def post_commit(self):
 
43
        return "bzrlib.ahook bzrlib.ahook"
 
44
 
 
45
 
 
46
class TestCommit(TestCaseInTempDir):
 
47
 
 
48
    def test_simple_commit(self):
 
49
        """Commit and check two versions of a single file."""
 
50
        b = Branch.initialize(u'.')
 
51
        file('hello', 'w').write('hello world')
 
52
        b.working_tree().add('hello')
 
53
        b.working_tree().commit(message='add hello')
 
54
        file_id = b.working_tree().path2id('hello')
 
55
 
 
56
        file('hello', 'w').write('version 2')
 
57
        b.working_tree().commit(message='commit 2')
 
58
 
 
59
        eq = self.assertEquals
 
60
        eq(b.revno(), 2)
 
61
        rh = b.revision_history()
 
62
        rev = b.repository.get_revision(rh[0])
 
63
        eq(rev.message, 'add hello')
 
64
 
 
65
        tree1 = b.repository.revision_tree(rh[0])
 
66
        text = tree1.get_file_text(file_id)
 
67
        eq(text, 'hello world')
 
68
 
 
69
        tree2 = b.repository.revision_tree(rh[1])
 
70
        eq(tree2.get_file_text(file_id), 'version 2')
 
71
 
 
72
    def test_delete_commit(self):
 
73
        """Test a commit with a deleted file"""
 
74
        b = Branch.initialize(u'.')
 
75
        file('hello', 'w').write('hello world')
 
76
        b.working_tree().add(['hello'], ['hello-id'])
 
77
        b.working_tree().commit(message='add hello')
 
78
 
 
79
        os.remove('hello')
 
80
        b.working_tree().commit('removed hello', rev_id='rev2')
 
81
 
 
82
        tree = b.repository.revision_tree('rev2')
 
83
        self.assertFalse(tree.has_id('hello-id'))
 
84
 
 
85
    def test_pointless_commit(self):
 
86
        """Commit refuses unless there are changes or it's forced."""
 
87
        b = Branch.initialize(u'.')
 
88
        file('hello', 'w').write('hello')
 
89
        b.working_tree().add(['hello'])
 
90
        b.working_tree().commit(message='add hello')
 
91
        self.assertEquals(b.revno(), 1)
 
92
        self.assertRaises(PointlessCommit,
 
93
                          b.working_tree().commit,
 
94
                          message='fails',
 
95
                          allow_pointless=False)
 
96
        self.assertEquals(b.revno(), 1)
 
97
        
 
98
    def test_commit_empty(self):
 
99
        """Commiting an empty tree works."""
 
100
        b = Branch.initialize(u'.')
 
101
        b.working_tree().commit(message='empty tree', allow_pointless=True)
 
102
        self.assertRaises(PointlessCommit,
 
103
                          b.working_tree().commit,
 
104
                          message='empty tree',
 
105
                          allow_pointless=False)
 
106
        b.working_tree().commit(message='empty tree', allow_pointless=True)
 
107
        self.assertEquals(b.revno(), 2)
 
108
 
 
109
 
 
110
    def test_selective_delete(self):
 
111
        """Selective commit in tree with deletions"""
 
112
        b = Branch.initialize(u'.')
 
113
        file('hello', 'w').write('hello')
 
114
        file('buongia', 'w').write('buongia')
 
115
        b.working_tree().add(['hello', 'buongia'],
 
116
              ['hello-id', 'buongia-id'])
 
117
        b.working_tree().commit(message='add files',
 
118
                 rev_id='test@rev-1')
 
119
        
 
120
        os.remove('hello')
 
121
        file('buongia', 'w').write('new text')
 
122
        b.working_tree().commit(message='update text',
 
123
                 specific_files=['buongia'],
 
124
                 allow_pointless=False,
 
125
                 rev_id='test@rev-2')
 
126
 
 
127
        b.working_tree().commit(message='remove hello',
 
128
                 specific_files=['hello'],
 
129
                 allow_pointless=False,
 
130
                 rev_id='test@rev-3')
 
131
 
 
132
        eq = self.assertEquals
 
133
        eq(b.revno(), 3)
 
134
 
 
135
        tree2 = b.repository.revision_tree('test@rev-2')
 
136
        self.assertTrue(tree2.has_filename('hello'))
 
137
        self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
 
138
        self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
 
139
        
 
140
        tree3 = b.repository.revision_tree('test@rev-3')
 
141
        self.assertFalse(tree3.has_filename('hello'))
 
142
        self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
 
143
 
 
144
 
 
145
    def test_commit_rename(self):
 
146
        """Test commit of a revision where a file is renamed."""
 
147
        b = Branch.initialize(u'.')
 
148
        tree = WorkingTree(u'.', b)
 
149
        self.build_tree(['hello'], line_endings='binary')
 
150
        tree.add(['hello'], ['hello-id'])
 
151
        tree.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
 
152
 
 
153
        tree.rename_one('hello', 'fruity')
 
154
        tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
 
155
 
 
156
        eq = self.assertEquals
 
157
        tree1 = b.repository.revision_tree('test@rev-1')
 
158
        eq(tree1.id2path('hello-id'), 'hello')
 
159
        eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
 
160
        self.assertFalse(tree1.has_filename('fruity'))
 
161
        self.check_inventory_shape(tree1.inventory, ['hello'])
 
162
        ie = tree1.inventory['hello-id']
 
163
        eq(ie.revision, 'test@rev-1')
 
164
 
 
165
        tree2 = b.repository.revision_tree('test@rev-2')
 
166
        eq(tree2.id2path('hello-id'), 'fruity')
 
167
        eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
 
168
        self.check_inventory_shape(tree2.inventory, ['fruity'])
 
169
        ie = tree2.inventory['hello-id']
 
170
        eq(ie.revision, 'test@rev-2')
 
171
 
 
172
    def test_reused_rev_id(self):
 
173
        """Test that a revision id cannot be reused in a branch"""
 
174
        b = Branch.initialize(u'.')
 
175
        b.working_tree().commit('initial', rev_id='test@rev-1', allow_pointless=True)
 
176
        self.assertRaises(Exception,
 
177
                          b.working_tree().commit,
 
178
                          message='reused id',
 
179
                          rev_id='test@rev-1',
 
180
                          allow_pointless=True)
 
181
 
 
182
    def test_commit_move(self):
 
183
        """Test commit of revisions with moved files and directories"""
 
184
        eq = self.assertEquals
 
185
        b = Branch.initialize(u'.')
 
186
        r1 = 'test@rev-1'
 
187
        self.build_tree(['hello', 'a/', 'b/'])
 
188
        b.working_tree().add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
 
189
        b.working_tree().commit('initial', rev_id=r1, allow_pointless=False)
 
190
        b.working_tree().move(['hello'], 'a')
 
191
        r2 = 'test@rev-2'
 
192
        b.working_tree().commit('two', rev_id=r2, allow_pointless=False)
 
193
        self.check_inventory_shape(b.working_tree().read_working_inventory(),
 
194
                                   ['a', 'a/hello', 'b'])
 
195
 
 
196
        b.working_tree().move(['b'], 'a')
 
197
        r3 = 'test@rev-3'
 
198
        b.working_tree().commit('three', rev_id=r3, allow_pointless=False)
 
199
        self.check_inventory_shape(b.working_tree().read_working_inventory(),
 
200
                                   ['a', 'a/hello', 'a/b'])
 
201
        self.check_inventory_shape(b.repository.get_revision_inventory(r3),
 
202
                                   ['a', 'a/hello', 'a/b'])
 
203
 
 
204
        b.working_tree().move(['a/hello'], 'a/b')
 
205
        r4 = 'test@rev-4'
 
206
        b.working_tree().commit('four', rev_id=r4, allow_pointless=False)
 
207
        self.check_inventory_shape(b.working_tree().read_working_inventory(),
 
208
                                   ['a', 'a/b/hello', 'a/b'])
 
209
 
 
210
        inv = b.repository.get_revision_inventory(r4)
 
211
        eq(inv['hello-id'].revision, r4)
 
212
        eq(inv['a-id'].revision, r1)
 
213
        eq(inv['b-id'].revision, r3)
 
214
        
 
215
    def test_removed_commit(self):
 
216
        """Commit with a removed file"""
 
217
        b = Branch.initialize(u'.')
 
218
        wt = b.working_tree()
 
219
        file('hello', 'w').write('hello world')
 
220
        b.working_tree().add(['hello'], ['hello-id'])
 
221
        b.working_tree().commit(message='add hello')
 
222
 
 
223
        wt = b.working_tree()  # FIXME: kludge for aliasing of working inventory
 
224
        wt.remove('hello')
 
225
        b.working_tree().commit('removed hello', rev_id='rev2')
 
226
 
 
227
        tree = b.repository.revision_tree('rev2')
 
228
        self.assertFalse(tree.has_id('hello-id'))
 
229
 
 
230
 
 
231
    def test_committed_ancestry(self):
 
232
        """Test commit appends revisions to ancestry."""
 
233
        b = Branch.initialize(u'.')
 
234
        rev_ids = []
 
235
        for i in range(4):
 
236
            file('hello', 'w').write((str(i) * 4) + '\n')
 
237
            if i == 0:
 
238
                b.working_tree().add(['hello'], ['hello-id'])
 
239
            rev_id = 'test@rev-%d' % (i+1)
 
240
            rev_ids.append(rev_id)
 
241
            b.working_tree().commit(message='rev %d' % (i+1),
 
242
                     rev_id=rev_id)
 
243
        eq = self.assertEquals
 
244
        eq(b.revision_history(), rev_ids)
 
245
        for i in range(4):
 
246
            anc = b.repository.get_ancestry(rev_ids[i])
 
247
            eq(anc, [None] + rev_ids[:i+1])
 
248
 
 
249
    def test_commit_new_subdir_child_selective(self):
 
250
        b = Branch.initialize(u'.')
 
251
        self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
 
252
        b.working_tree().add(['dir', 'dir/file1', 'dir/file2'],
 
253
              ['dirid', 'file1id', 'file2id'])
 
254
        b.working_tree().commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
 
255
        inv = b.repository.get_inventory('1')
 
256
        self.assertEqual('1', inv['dirid'].revision)
 
257
        self.assertEqual('1', inv['file1id'].revision)
 
258
        # FIXME: This should raise a KeyError I think, rbc20051006
 
259
        self.assertRaises(BzrError, inv.__getitem__, 'file2id')
 
260
 
 
261
    def test_strict_commit(self):
 
262
        """Try and commit with unknown files and strict = True, should fail."""
 
263
        from bzrlib.errors import StrictCommitFailed
 
264
        b = Branch.initialize(u'.')
 
265
        file('hello', 'w').write('hello world')
 
266
        b.working_tree().add('hello')
 
267
        file('goodbye', 'w').write('goodbye cruel world!')
 
268
        self.assertRaises(StrictCommitFailed, b.working_tree().commit,
 
269
            message='add hello but not goodbye', strict=True)
 
270
 
 
271
    def test_strict_commit_without_unknowns(self):
 
272
        """Try and commit with no unknown files and strict = True,
 
273
        should work."""
 
274
        from bzrlib.errors import StrictCommitFailed
 
275
        b = Branch.initialize(u'.')
 
276
        file('hello', 'w').write('hello world')
 
277
        b.working_tree().add('hello')
 
278
        b.working_tree().commit(message='add hello', strict=True)
 
279
 
 
280
    def test_nonstrict_commit(self):
 
281
        """Try and commit with unknown files and strict = False, should work."""
 
282
        b = Branch.initialize(u'.')
 
283
        file('hello', 'w').write('hello world')
 
284
        b.working_tree().add('hello')
 
285
        file('goodbye', 'w').write('goodbye cruel world!')
 
286
        b.working_tree().commit(message='add hello but not goodbye', strict=False)
 
287
 
 
288
    def test_nonstrict_commit_without_unknowns(self):
 
289
        """Try and commit with no unknown files and strict = False,
 
290
        should work."""
 
291
        b = Branch.initialize(u'.')
 
292
        file('hello', 'w').write('hello world')
 
293
        b.working_tree().add('hello')
 
294
        b.working_tree().commit(message='add hello', strict=False)
 
295
 
 
296
    def test_signed_commit(self):
 
297
        import bzrlib.gpg
 
298
        import bzrlib.commit as commit
 
299
        oldstrategy = bzrlib.gpg.GPGStrategy
 
300
        branch = Branch.initialize(u'.')
 
301
        branch.working_tree().commit("base", allow_pointless=True, rev_id='A')
 
302
        self.failIf(branch.repository.revision_store.has_id('A', 'sig'))
 
303
        try:
 
304
            from bzrlib.testament import Testament
 
305
            # monkey patch gpg signing mechanism
 
306
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
 
307
            commit.Commit(config=MustSignConfig(branch)).commit(branch, "base",
 
308
                                                      allow_pointless=True,
 
309
                                                      rev_id='B')
 
310
            self.assertEqual(Testament.from_revision(branch.repository,
 
311
                             'B').as_short_text(),
 
312
                             branch.repository.revision_store.get('B', 
 
313
                                                               'sig').read())
 
314
        finally:
 
315
            bzrlib.gpg.GPGStrategy = oldstrategy
 
316
 
 
317
    def test_commit_failed_signature(self):
 
318
        import bzrlib.gpg
 
319
        import bzrlib.commit as commit
 
320
        oldstrategy = bzrlib.gpg.GPGStrategy
 
321
        branch = Branch.initialize(u'.')
 
322
        branch.working_tree().commit("base", allow_pointless=True, rev_id='A')
 
323
        self.failIf(branch.repository.revision_store.has_id('A', 'sig'))
 
324
        try:
 
325
            from bzrlib.testament import Testament
 
326
            # monkey patch gpg signing mechanism
 
327
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
 
328
            config = MustSignConfig(branch)
 
329
            self.assertRaises(SigningFailed,
 
330
                              commit.Commit(config=config).commit,
 
331
                              branch, "base",
 
332
                              allow_pointless=True,
 
333
                              rev_id='B')
 
334
            branch = Branch.open(u'.')
 
335
            self.assertEqual(branch.revision_history(), ['A'])
 
336
            self.failIf(branch.repository.revision_store.has_id('B'))
 
337
        finally:
 
338
            bzrlib.gpg.GPGStrategy = oldstrategy
 
339
 
 
340
    def test_commit_invokes_hooks(self):
 
341
        import bzrlib.commit as commit
 
342
        branch = Branch.initialize(u'.')
 
343
        calls = []
 
344
        def called(branch, rev_id):
 
345
            calls.append('called')
 
346
        bzrlib.ahook = called
 
347
        try:
 
348
            config = BranchWithHooks(branch)
 
349
            commit.Commit(config=config).commit(
 
350
                            branch, "base",
 
351
                            allow_pointless=True,
 
352
                            rev_id='A')
 
353
            self.assertEqual(['called', 'called'], calls)
 
354
        finally:
 
355
            del bzrlib.ahook