/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/blackbox/test_commit.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-08-05 07:02:32 UTC
  • mfrom: (5365.2.2 smaller-inventory-entries)
  • Revision ID: pqm@pqm.ubuntu.com-20100805070232-ezo69a4k078j1xmp
(spiv) Reduce the memory consumption of InventoryEntry instances. (Andrew
 Bennetts)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 by Canonical Ltd
2
 
 
 
1
# Copyright (C) 2006-2010 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
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
 
18
18
"""Tests for the commit CLI of bzr."""
19
19
 
20
 
from cStringIO import StringIO
21
20
import os
22
21
import re
23
 
import shutil
24
22
import sys
25
23
 
26
 
from bzrlib.branch import Branch
27
 
import bzrlib.bzrdir as bzrdir
28
 
from bzrlib.errors import BzrCommandError
29
 
from bzrlib.tests.blackbox import ExternalBase
30
 
from bzrlib.workingtree import WorkingTree
31
 
 
32
 
 
33
 
class TestCommit(ExternalBase):
 
24
from bzrlib import (
 
25
    bzrdir,
 
26
    osutils,
 
27
    ignores,
 
28
    msgeditor,
 
29
    osutils,
 
30
    tests,
 
31
    )
 
32
from bzrlib.bzrdir import BzrDir
 
33
from bzrlib.tests import (
 
34
    probe_bad_non_ascii,
 
35
    TestSkipped,
 
36
    )
 
37
from bzrlib.tests import TestCaseWithTransport
 
38
 
 
39
 
 
40
class TestCommit(TestCaseWithTransport):
34
41
 
35
42
    def test_05_empty_commit(self):
36
43
        """Commit of tree with no versioned files should fail"""
37
44
        # If forced, it should succeed, but this is not tested here.
38
 
        self.runbzr("init")
 
45
        self.make_branch_and_tree('.')
39
46
        self.build_tree(['hello.txt'])
40
 
        self.runbzr("commit -m empty", retcode=3)
 
47
        out,err = self.run_bzr('commit -m empty', retcode=3)
 
48
        self.assertEqual('', out)
 
49
        self.assertContainsRe(err, 'bzr: ERROR: No changes to commit\.'
 
50
                                  ' Use --unchanged to commit anyhow.\n')
 
51
 
 
52
    def test_commit_success(self):
 
53
        """Successful commit should not leave behind a bzr-commit-* file"""
 
54
        self.make_branch_and_tree('.')
 
55
        self.run_bzr('commit --unchanged -m message')
 
56
        self.assertEqual('', self.run_bzr('unknowns')[0])
 
57
 
 
58
        # same for unicode messages
 
59
        self.run_bzr(["commit", "--unchanged", "-m", u'foo\xb5'])
 
60
        self.assertEqual('', self.run_bzr('unknowns')[0])
 
61
 
 
62
    def test_commit_with_path(self):
 
63
        """Commit tree with path of root specified"""
 
64
        a_tree = self.make_branch_and_tree('a')
 
65
        self.build_tree(['a/a_file'])
 
66
        a_tree.add('a_file')
 
67
        self.run_bzr(['commit', '-m', 'first commit', 'a'])
 
68
 
 
69
        b_tree = a_tree.bzrdir.sprout('b').open_workingtree()
 
70
        self.build_tree_contents([('b/a_file', 'changes in b')])
 
71
        self.run_bzr(['commit', '-m', 'first commit in b', 'b'])
 
72
 
 
73
        self.build_tree_contents([('a/a_file', 'new contents')])
 
74
        self.run_bzr(['commit', '-m', 'change in a', 'a'])
 
75
 
 
76
        b_tree.merge_from_branch(a_tree.branch)
 
77
        self.assertEqual(len(b_tree.conflicts()), 1)
 
78
        self.run_bzr('resolved b/a_file')
 
79
        self.run_bzr(['commit', '-m', 'merge into b', 'b'])
 
80
 
41
81
 
42
82
    def test_10_verbose_commit(self):
43
83
        """Add one file and examine verbose commit output"""
44
 
        self.runbzr("init")
 
84
        tree = self.make_branch_and_tree('.')
45
85
        self.build_tree(['hello.txt'])
46
 
        self.runbzr("add hello.txt")
47
 
        out,err = self.run_bzr("commit", "-m", "added")
48
 
        self.assertEqual('', out)
49
 
        self.assertEqual('added hello.txt\n'
50
 
                         'Committed revision 1.\n',
51
 
                         err)
52
 
 
53
 
    def test_15_verbose_commit_with_unknown(self):
 
86
        tree.add("hello.txt")
 
87
        out,err = self.run_bzr('commit -m added')
 
88
        self.assertEqual('', out)
 
89
        self.assertContainsRe(err, '^Committing to: .*\n'
 
90
                              'added hello.txt\n'
 
91
                              'Committed revision 1.\n$',)
 
92
 
 
93
    def prepare_simple_history(self):
 
94
        """Prepare and return a working tree with one commit of one file"""
 
95
        # Commit with modified file should say so
 
96
        wt = BzrDir.create_standalone_workingtree('.')
 
97
        self.build_tree(['hello.txt', 'extra.txt'])
 
98
        wt.add(['hello.txt'])
 
99
        wt.commit(message='added')
 
100
        return wt
 
101
 
 
102
    def test_verbose_commit_modified(self):
 
103
        # Verbose commit of modified file should say so
 
104
        wt = self.prepare_simple_history()
 
105
        self.build_tree_contents([('hello.txt', 'new contents')])
 
106
        out, err = self.run_bzr('commit -m modified')
 
107
        self.assertEqual('', out)
 
108
        self.assertContainsRe(err, '^Committing to: .*\n'
 
109
                              'modified hello\.txt\n'
 
110
                              'Committed revision 2\.\n$')
 
111
 
 
112
    def test_unicode_commit_message_is_filename(self):
 
113
        """Unicode commit message same as a filename (Bug #563646).
 
114
        """
 
115
        file_name = u'\N{euro sign}'
 
116
        self.run_bzr(['init'])
 
117
        open(file_name, 'w').write('hello world')
 
118
        self.run_bzr(['add'])
 
119
        out, err = self.run_bzr(['commit', '-m', file_name])
 
120
        reflags = re.MULTILINE|re.DOTALL|re.UNICODE
 
121
        te = osutils.get_terminal_encoding()
 
122
        self.assertContainsRe(err.decode(te),
 
123
            u'The commit message is a file name:',
 
124
            flags=reflags)
 
125
 
 
126
        # Run same test with a filename that causes encode
 
127
        # error for the terminal encoding. We do this
 
128
        # by forcing terminal encoding of ascii for
 
129
        # osutils.get_terminal_encoding which is used
 
130
        # by ui.text.show_warning
 
131
        default_get_terminal_enc = osutils.get_terminal_encoding
 
132
        try:
 
133
            osutils.get_terminal_encoding = lambda trace=None: 'ascii'
 
134
            file_name = u'foo\u1234'
 
135
            open(file_name, 'w').write('hello world')
 
136
            self.run_bzr(['add'])
 
137
            out, err = self.run_bzr(['commit', '-m', file_name])
 
138
            reflags = re.MULTILINE|re.DOTALL|re.UNICODE
 
139
            te = osutils.get_terminal_encoding()
 
140
            self.assertContainsRe(err.decode(te, 'replace'),
 
141
                u'The commit message is a file name:',
 
142
                flags=reflags)
 
143
        finally:
 
144
            osutils.get_terminal_encoding = default_get_terminal_enc
 
145
 
 
146
    def test_warn_about_forgotten_commit_message(self):
 
147
        """Test that the lack of -m parameter is caught"""
 
148
        wt = self.make_branch_and_tree('.')
 
149
        self.build_tree(['one', 'two'])
 
150
        wt.add(['two'])
 
151
        out, err = self.run_bzr('commit -m one two')
 
152
        self.assertContainsRe(err, "The commit message is a file name")
 
153
 
 
154
    def test_verbose_commit_renamed(self):
 
155
        # Verbose commit of renamed file should say so
 
156
        wt = self.prepare_simple_history()
 
157
        wt.rename_one('hello.txt', 'gutentag.txt')
 
158
        out, err = self.run_bzr('commit -m renamed')
 
159
        self.assertEqual('', out)
 
160
        self.assertContainsRe(err, '^Committing to: .*\n'
 
161
                              'renamed hello\.txt => gutentag\.txt\n'
 
162
                              'Committed revision 2\.$\n')
 
163
 
 
164
    def test_verbose_commit_moved(self):
 
165
        # Verbose commit of file moved to new directory should say so
 
166
        wt = self.prepare_simple_history()
 
167
        os.mkdir('subdir')
 
168
        wt.add(['subdir'])
 
169
        wt.rename_one('hello.txt', 'subdir/hello.txt')
 
170
        out, err = self.run_bzr('commit -m renamed')
 
171
        self.assertEqual('', out)
 
172
        self.assertEqual(set([
 
173
            'Committing to: %s/' % osutils.getcwd(),
 
174
            'added subdir',
 
175
            'renamed hello.txt => subdir/hello.txt',
 
176
            'Committed revision 2.',
 
177
            '',
 
178
            ]), set(err.split('\n')))
 
179
 
 
180
    def test_verbose_commit_with_unknown(self):
54
181
        """Unknown files should not be listed by default in verbose output"""
55
182
        # Is that really the best policy?
56
 
        self.runbzr("init")
 
183
        wt = BzrDir.create_standalone_workingtree('.')
57
184
        self.build_tree(['hello.txt', 'extra.txt'])
58
 
        self.runbzr("add hello.txt")
59
 
        out,err = self.run_bzr("commit", "-m", "added")
 
185
        wt.add(['hello.txt'])
 
186
        out,err = self.run_bzr('commit -m added')
60
187
        self.assertEqual('', out)
61
 
        self.assertEqual('added hello.txt\n'
62
 
                         'Committed revision 1.\n',
63
 
                         err)
 
188
        self.assertContainsRe(err, '^Committing to: .*\n'
 
189
                              'added hello\.txt\n'
 
190
                              'Committed revision 1\.\n$')
64
191
 
65
 
    def test_16_verbose_commit_with_unchanged(self):
 
192
    def test_verbose_commit_with_unchanged(self):
66
193
        """Unchanged files should not be listed by default in verbose output"""
67
 
        self.runbzr("init")
 
194
        tree = self.make_branch_and_tree('.')
68
195
        self.build_tree(['hello.txt', 'unchanged.txt'])
69
 
        self.runbzr('add unchanged.txt')
70
 
        self.runbzr('commit -m unchanged unchanged.txt')
71
 
        self.runbzr("add hello.txt")
72
 
        out,err = self.run_bzr("commit", "-m", "added")
73
 
        self.assertEqual('', out)
74
 
        self.assertEqual('added hello.txt\n'
75
 
                         'Committed revision 2.\n',
76
 
                         err)
 
196
        tree.add('unchanged.txt')
 
197
        self.run_bzr('commit -m unchanged unchanged.txt')
 
198
        tree.add("hello.txt")
 
199
        out,err = self.run_bzr('commit -m added')
 
200
        self.assertEqual('', out)
 
201
        self.assertContainsRe(err, '^Committing to: .*\n'
 
202
                              'added hello\.txt\n'
 
203
                              'Committed revision 2\.$\n')
 
204
 
 
205
    def test_verbose_commit_includes_master_location(self):
 
206
        """Location of master is displayed when committing to bound branch"""
 
207
        a_tree = self.make_branch_and_tree('a')
 
208
        self.build_tree(['a/b'])
 
209
        a_tree.add('b')
 
210
        a_tree.commit(message='Initial message')
 
211
 
 
212
        b_tree = a_tree.branch.create_checkout('b')
 
213
        expected = "%s/" % (osutils.abspath('a'), )
 
214
        out, err = self.run_bzr('commit -m blah --unchanged', working_dir='b')
 
215
        self.assertEqual(err, 'Committing to: %s\n'
 
216
                         'Committed revision 2.\n' % expected)
 
217
 
 
218
    def test_commit_sanitizes_CR_in_message(self):
 
219
        # See bug #433779, basically Emacs likes to pass '\r\n' style line
 
220
        # endings to 'bzr commit -m ""' which breaks because we don't allow
 
221
        # '\r' in commit messages. (Mostly because of issues where XML style
 
222
        # formats arbitrarily strip it out of the data while parsing.)
 
223
        # To make life easier for users, we just always translate '\r\n' =>
 
224
        # '\n'. And '\r' => '\n'.
 
225
        a_tree = self.make_branch_and_tree('a')
 
226
        self.build_tree(['a/b'])
 
227
        a_tree.add('b')
 
228
        self.run_bzr(['commit',
 
229
                      '-m', 'a string\r\n\r\nwith mixed\r\rendings\n'],
 
230
                     working_dir='a')
 
231
        rev_id = a_tree.branch.last_revision()
 
232
        rev = a_tree.branch.repository.get_revision(rev_id)
 
233
        self.assertEqualDiff('a string\n\nwith mixed\n\nendings\n',
 
234
                             rev.message)
 
235
 
 
236
    def test_commit_merge_reports_all_modified_files(self):
 
237
        # the commit command should show all the files that are shown by
 
238
        # bzr diff or bzr status when committing, even when they were not
 
239
        # changed by the user but rather through doing a merge.
 
240
        this_tree = self.make_branch_and_tree('this')
 
241
        # we need a bunch of files and dirs, to perform one action on each.
 
242
        self.build_tree([
 
243
            'this/dirtorename/',
 
244
            'this/dirtoreparent/',
 
245
            'this/dirtoleave/',
 
246
            'this/dirtoremove/',
 
247
            'this/filetoreparent',
 
248
            'this/filetorename',
 
249
            'this/filetomodify',
 
250
            'this/filetoremove',
 
251
            'this/filetoleave']
 
252
            )
 
253
        this_tree.add([
 
254
            'dirtorename',
 
255
            'dirtoreparent',
 
256
            'dirtoleave',
 
257
            'dirtoremove',
 
258
            'filetoreparent',
 
259
            'filetorename',
 
260
            'filetomodify',
 
261
            'filetoremove',
 
262
            'filetoleave']
 
263
            )
 
264
        this_tree.commit('create_files')
 
265
        other_dir = this_tree.bzrdir.sprout('other')
 
266
        other_tree = other_dir.open_workingtree()
 
267
        other_tree.lock_write()
 
268
        # perform the needed actions on the files and dirs.
 
269
        try:
 
270
            other_tree.rename_one('dirtorename', 'renameddir')
 
271
            other_tree.rename_one('dirtoreparent', 'renameddir/reparenteddir')
 
272
            other_tree.rename_one('filetorename', 'renamedfile')
 
273
            other_tree.rename_one('filetoreparent',
 
274
                                  'renameddir/reparentedfile')
 
275
            other_tree.remove(['dirtoremove', 'filetoremove'])
 
276
            self.build_tree_contents([
 
277
                ('other/newdir/',),
 
278
                ('other/filetomodify', 'new content'),
 
279
                ('other/newfile', 'new file content')])
 
280
            other_tree.add('newfile')
 
281
            other_tree.add('newdir/')
 
282
            other_tree.commit('modify all sample files and dirs.')
 
283
        finally:
 
284
            other_tree.unlock()
 
285
        this_tree.merge_from_branch(other_tree.branch)
 
286
        os.chdir('this')
 
287
        out,err = self.run_bzr('commit -m added')
 
288
        self.assertEqual('', out)
 
289
        self.assertEqual(set([
 
290
            'Committing to: %s/' % osutils.getcwd(),
 
291
            'modified filetomodify',
 
292
            'added newdir',
 
293
            'added newfile',
 
294
            'renamed dirtorename => renameddir',
 
295
            'renamed filetorename => renamedfile',
 
296
            'renamed dirtoreparent => renameddir/reparenteddir',
 
297
            'renamed filetoreparent => renameddir/reparentedfile',
 
298
            'deleted dirtoremove',
 
299
            'deleted filetoremove',
 
300
            'Committed revision 2.',
 
301
            ''
 
302
            ]), set(err.split('\n')))
77
303
 
78
304
    def test_empty_commit_message(self):
79
 
        self.runbzr("init")
80
 
        file('foo.c', 'wt').write('int main() {}')
81
 
        self.runbzr(['add', 'foo.c'])
82
 
        self.runbzr(["commit", "-m", ""] , retcode=3)
 
305
        tree = self.make_branch_and_tree('.')
 
306
        self.build_tree_contents([('foo.c', 'int main() {}')])
 
307
        tree.add('foo.c')
 
308
        self.run_bzr('commit -m ""', retcode=3)
 
309
 
 
310
    def test_unsupported_encoding_commit_message(self):
 
311
        if sys.platform == 'win32':
 
312
            raise tests.TestNotApplicable('Win32 parses arguments directly'
 
313
                ' as Unicode, so we can\'t pass invalid non-ascii')
 
314
        tree = self.make_branch_and_tree('.')
 
315
        self.build_tree_contents([('foo.c', 'int main() {}')])
 
316
        tree.add('foo.c')
 
317
        # LANG env variable has no effect on Windows
 
318
        # but some characters anyway cannot be represented
 
319
        # in default user encoding
 
320
        char = probe_bad_non_ascii(osutils.get_user_encoding())
 
321
        if char is None:
 
322
            raise TestSkipped('Cannot find suitable non-ascii character'
 
323
                'for user_encoding (%s)' % osutils.get_user_encoding())
 
324
        out,err = self.run_bzr_subprocess('commit -m "%s"' % char,
 
325
                                          retcode=1,
 
326
                                          env_changes={'LANG': 'C'})
 
327
        self.assertContainsRe(err, r'bzrlib.errors.BzrError: Parameter.*is '
 
328
                                    'unsupported by the current encoding.')
83
329
 
84
330
    def test_other_branch_commit(self):
85
331
        # this branch is to ensure consistent behaviour, whether we're run
86
332
        # inside a branch, or not.
87
 
        os.mkdir('empty_branch')
88
 
        os.chdir('empty_branch')
89
 
        self.runbzr('init')
90
 
        os.mkdir('branch')
91
 
        os.chdir('branch')
92
 
        self.runbzr('init')
93
 
        file('foo.c', 'wt').write('int main() {}')
94
 
        file('bar.c', 'wt').write('int main() {}')
95
 
        os.chdir('..')
96
 
        self.runbzr(['add', 'branch/foo.c'])
97
 
        self.runbzr(['add', 'branch'])
 
333
        outer_tree = self.make_branch_and_tree('.')
 
334
        inner_tree = self.make_branch_and_tree('branch')
 
335
        self.build_tree_contents([
 
336
            ('branch/foo.c', 'int main() {}'),
 
337
            ('branch/bar.c', 'int main() {}')])
 
338
        inner_tree.add(['foo.c', 'bar.c'])
98
339
        # can't commit files in different trees; sane error
99
 
        self.runbzr('commit -m newstuff branch/foo.c .', retcode=3)
100
 
        self.runbzr('commit -m newstuff branch/foo.c')
101
 
        self.runbzr('commit -m newstuff branch')
102
 
        self.runbzr('commit -m newstuff branch', retcode=3)
 
340
        self.run_bzr('commit -m newstuff branch/foo.c .', retcode=3)
 
341
        # can commit to branch - records foo.c only
 
342
        self.run_bzr('commit -m newstuff branch/foo.c')
 
343
        # can commit to branch - records bar.c
 
344
        self.run_bzr('commit -m newstuff branch')
 
345
        # No changes left
 
346
        self.run_bzr_error(["No changes to commit"], 'commit -m newstuff branch')
103
347
 
104
348
    def test_out_of_date_tree_commit(self):
105
349
        # check we get an error code and a clear message committing with an out
106
350
        # of date checkout
107
 
        self.make_branch_and_tree('branch')
 
351
        tree = self.make_branch_and_tree('branch')
108
352
        # make a checkout
109
 
        self.runbzr('checkout --lightweight branch checkout')
 
353
        checkout = tree.branch.create_checkout('checkout', lightweight=True)
110
354
        # commit to the original branch to make the checkout out of date
111
 
        self.runbzr('commit --unchanged -m message branch')
 
355
        tree.commit('message branch', allow_pointless=True)
112
356
        # now commit to the checkout should emit
113
357
        # ERROR: Out of date with the branch, 'bzr update' is suggested
114
 
        output = self.runbzr('commit --unchanged -m checkout_message '
 
358
        output = self.run_bzr('commit --unchanged -m checkout_message '
115
359
                             'checkout', retcode=3)
116
360
        self.assertEqual(output,
117
361
                         ('',
118
 
                          "bzr: ERROR: Working tree is out of date, please run "
119
 
                          "'bzr update'.\n"))
 
362
                          "bzr: ERROR: Working tree is out of date, please "
 
363
                          "run 'bzr update'.\n"))
120
364
 
121
365
    def test_local_commit_unbound(self):
122
366
        # a --local commit on an unbound branch is an error
123
367
        self.make_branch_and_tree('.')
124
 
        out, err = self.run_bzr('commit', '--local', retcode=3)
 
368
        out, err = self.run_bzr('commit --local', retcode=3)
125
369
        self.assertEqualDiff('', out)
126
370
        self.assertEqualDiff('bzr: ERROR: Cannot perform local-only commits '
127
371
                             'on unbound branches.\n', err)
 
372
 
 
373
    def test_commit_a_text_merge_in_a_checkout(self):
 
374
        # checkouts perform multiple actions in a transaction across bond
 
375
        # branches and their master, and have been observed to fail in the
 
376
        # past. This is a user story reported to fail in bug #43959 where
 
377
        # a merge done in a checkout (using the update command) failed to
 
378
        # commit correctly.
 
379
        trunk = self.make_branch_and_tree('trunk')
 
380
 
 
381
        u1 = trunk.branch.create_checkout('u1')
 
382
        self.build_tree_contents([('u1/hosts', 'initial contents\n')])
 
383
        u1.add('hosts')
 
384
        self.run_bzr('commit -m add-hosts u1')
 
385
 
 
386
        u2 = trunk.branch.create_checkout('u2')
 
387
        self.build_tree_contents([('u2/hosts', 'altered in u2\n')])
 
388
        self.run_bzr('commit -m checkin-from-u2 u2')
 
389
 
 
390
        # make an offline commits
 
391
        self.build_tree_contents([('u1/hosts', 'first offline change in u1\n')])
 
392
        self.run_bzr('commit -m checkin-offline --local u1')
 
393
 
 
394
        # now try to pull in online work from u2, and then commit our offline
 
395
        # work as a merge
 
396
        # retcode 1 as we expect a text conflict
 
397
        self.run_bzr('update u1', retcode=1)
 
398
        self.assertFileEqual('''\
 
399
<<<<<<< TREE
 
400
first offline change in u1
 
401
=======
 
402
altered in u2
 
403
>>>>>>> MERGE-SOURCE
 
404
''',
 
405
                             'u1/hosts')
 
406
 
 
407
        self.run_bzr('resolved u1/hosts')
 
408
        # add a text change here to represent resolving the merge conflicts in
 
409
        # favour of a new version of the file not identical to either the u1
 
410
        # version or the u2 version.
 
411
        self.build_tree_contents([('u1/hosts', 'merge resolution\n')])
 
412
        self.run_bzr('commit -m checkin-merge-of-the-offline-work-from-u1 u1')
 
413
 
 
414
    def test_commit_exclude_excludes_modified_files(self):
 
415
        """Commit -x foo should ignore changes to foo."""
 
416
        tree = self.make_branch_and_tree('.')
 
417
        self.build_tree(['a', 'b', 'c'])
 
418
        tree.smart_add(['.'])
 
419
        out, err = self.run_bzr(['commit', '-m', 'test', '-x', 'b'])
 
420
        self.assertFalse('added b' in out)
 
421
        self.assertFalse('added b' in err)
 
422
        # If b was excluded it will still be 'added' in status.
 
423
        out, err = self.run_bzr(['added'])
 
424
        self.assertEqual('b\n', out)
 
425
        self.assertEqual('', err)
 
426
 
 
427
    def test_commit_exclude_twice_uses_both_rules(self):
 
428
        """Commit -x foo -x bar should ignore changes to foo and bar."""
 
429
        tree = self.make_branch_and_tree('.')
 
430
        self.build_tree(['a', 'b', 'c'])
 
431
        tree.smart_add(['.'])
 
432
        out, err = self.run_bzr(['commit', '-m', 'test', '-x', 'b', '-x', 'c'])
 
433
        self.assertFalse('added b' in out)
 
434
        self.assertFalse('added c' in out)
 
435
        self.assertFalse('added b' in err)
 
436
        self.assertFalse('added c' in err)
 
437
        # If b was excluded it will still be 'added' in status.
 
438
        out, err = self.run_bzr(['added'])
 
439
        self.assertTrue('b\n' in out)
 
440
        self.assertTrue('c\n' in out)
 
441
        self.assertEqual('', err)
 
442
 
 
443
    def test_commit_respects_spec_for_removals(self):
 
444
        """Commit with a file spec should only commit removals that match"""
 
445
        t = self.make_branch_and_tree('.')
 
446
        self.build_tree(['file-a', 'dir-a/', 'dir-a/file-b'])
 
447
        t.add(['file-a', 'dir-a', 'dir-a/file-b'])
 
448
        t.commit('Create')
 
449
        t.remove(['file-a', 'dir-a/file-b'])
 
450
        os.chdir('dir-a')
 
451
        result = self.run_bzr('commit . -m removed-file-b')[1]
 
452
        self.assertNotContainsRe(result, 'file-a')
 
453
        result = self.run_bzr('status')[0]
 
454
        self.assertContainsRe(result, 'removed:\n  file-a')
 
455
 
 
456
    def test_strict_commit(self):
 
457
        """Commit with --strict works if everything is known"""
 
458
        ignores._set_user_ignores([])
 
459
        tree = self.make_branch_and_tree('tree')
 
460
        self.build_tree(['tree/a'])
 
461
        tree.add('a')
 
462
        # A simple change should just work
 
463
        self.run_bzr('commit --strict -m adding-a',
 
464
                     working_dir='tree')
 
465
 
 
466
    def test_strict_commit_no_changes(self):
 
467
        """commit --strict gives "no changes" if there is nothing to commit"""
 
468
        tree = self.make_branch_and_tree('tree')
 
469
        self.build_tree(['tree/a'])
 
470
        tree.add('a')
 
471
        tree.commit('adding a')
 
472
 
 
473
        # With no changes, it should just be 'no changes'
 
474
        # Make sure that commit is failing because there is nothing to do
 
475
        self.run_bzr_error(['No changes to commit'],
 
476
                           'commit --strict -m no-changes',
 
477
                           working_dir='tree')
 
478
 
 
479
        # But --strict doesn't care if you supply --unchanged
 
480
        self.run_bzr('commit --strict --unchanged -m no-changes',
 
481
                     working_dir='tree')
 
482
 
 
483
    def test_strict_commit_unknown(self):
 
484
        """commit --strict fails if a file is unknown"""
 
485
        tree = self.make_branch_and_tree('tree')
 
486
        self.build_tree(['tree/a'])
 
487
        tree.add('a')
 
488
        tree.commit('adding a')
 
489
 
 
490
        # Add one file so there is a change, but forget the other
 
491
        self.build_tree(['tree/b', 'tree/c'])
 
492
        tree.add('b')
 
493
        self.run_bzr_error(['Commit refused because there are unknown files'],
 
494
                           'commit --strict -m add-b',
 
495
                           working_dir='tree')
 
496
 
 
497
        # --no-strict overrides --strict
 
498
        self.run_bzr('commit --strict -m add-b --no-strict',
 
499
                     working_dir='tree')
 
500
 
 
501
    def test_fixes_bug_output(self):
 
502
        """commit --fixes=lp:23452 succeeds without output."""
 
503
        tree = self.make_branch_and_tree('tree')
 
504
        self.build_tree(['tree/hello.txt'])
 
505
        tree.add('hello.txt')
 
506
        output, err = self.run_bzr(
 
507
            'commit -m hello --fixes=lp:23452 tree/hello.txt')
 
508
        self.assertEqual('', output)
 
509
        self.assertContainsRe(err, 'Committing to: .*\n'
 
510
                              'added hello\.txt\n'
 
511
                              'Committed revision 1\.\n')
 
512
 
 
513
    def test_no_bugs_no_properties(self):
 
514
        """If no bugs are fixed, the bugs property is not set.
 
515
 
 
516
        see https://beta.launchpad.net/bzr/+bug/109613
 
517
        """
 
518
        tree = self.make_branch_and_tree('tree')
 
519
        self.build_tree(['tree/hello.txt'])
 
520
        tree.add('hello.txt')
 
521
        self.run_bzr( 'commit -m hello tree/hello.txt')
 
522
        # Get the revision properties, ignoring the branch-nick property, which
 
523
        # we don't care about for this test.
 
524
        last_rev = tree.branch.repository.get_revision(tree.last_revision())
 
525
        properties = dict(last_rev.properties)
 
526
        del properties['branch-nick']
 
527
        self.assertFalse('bugs' in properties)
 
528
 
 
529
    def test_fixes_bug_sets_property(self):
 
530
        """commit --fixes=lp:234 sets the lp:234 revprop to 'fixed'."""
 
531
        tree = self.make_branch_and_tree('tree')
 
532
        self.build_tree(['tree/hello.txt'])
 
533
        tree.add('hello.txt')
 
534
        self.run_bzr('commit -m hello --fixes=lp:234 tree/hello.txt')
 
535
 
 
536
        # Get the revision properties, ignoring the branch-nick property, which
 
537
        # we don't care about for this test.
 
538
        last_rev = tree.branch.repository.get_revision(tree.last_revision())
 
539
        properties = dict(last_rev.properties)
 
540
        del properties['branch-nick']
 
541
 
 
542
        self.assertEqual({'bugs': 'https://launchpad.net/bugs/234 fixed'},
 
543
                         properties)
 
544
 
 
545
    def test_fixes_multiple_bugs_sets_properties(self):
 
546
        """--fixes can be used more than once to show that bugs are fixed."""
 
547
        tree = self.make_branch_and_tree('tree')
 
548
        self.build_tree(['tree/hello.txt'])
 
549
        tree.add('hello.txt')
 
550
        self.run_bzr('commit -m hello --fixes=lp:123 --fixes=lp:235'
 
551
                     ' tree/hello.txt')
 
552
 
 
553
        # Get the revision properties, ignoring the branch-nick property, which
 
554
        # we don't care about for this test.
 
555
        last_rev = tree.branch.repository.get_revision(tree.last_revision())
 
556
        properties = dict(last_rev.properties)
 
557
        del properties['branch-nick']
 
558
 
 
559
        self.assertEqual(
 
560
            {'bugs': 'https://launchpad.net/bugs/123 fixed\n'
 
561
                     'https://launchpad.net/bugs/235 fixed'},
 
562
            properties)
 
563
 
 
564
    def test_fixes_bug_with_alternate_trackers(self):
 
565
        """--fixes can be used on a properly configured branch to mark bug
 
566
        fixes on multiple trackers.
 
567
        """
 
568
        tree = self.make_branch_and_tree('tree')
 
569
        tree.branch.get_config().set_user_option(
 
570
            'trac_twisted_url', 'http://twistedmatrix.com/trac')
 
571
        self.build_tree(['tree/hello.txt'])
 
572
        tree.add('hello.txt')
 
573
        self.run_bzr('commit -m hello --fixes=lp:123 --fixes=twisted:235 tree/')
 
574
 
 
575
        # Get the revision properties, ignoring the branch-nick property, which
 
576
        # we don't care about for this test.
 
577
        last_rev = tree.branch.repository.get_revision(tree.last_revision())
 
578
        properties = dict(last_rev.properties)
 
579
        del properties['branch-nick']
 
580
 
 
581
        self.assertEqual(
 
582
            {'bugs': 'https://launchpad.net/bugs/123 fixed\n'
 
583
                     'http://twistedmatrix.com/trac/ticket/235 fixed'},
 
584
            properties)
 
585
 
 
586
    def test_fixes_unknown_bug_prefix(self):
 
587
        tree = self.make_branch_and_tree('tree')
 
588
        self.build_tree(['tree/hello.txt'])
 
589
        tree.add('hello.txt')
 
590
        self.run_bzr_error(
 
591
            ["Unrecognized bug %s. Commit refused." % 'xxx:123'],
 
592
            'commit -m add-b --fixes=xxx:123',
 
593
            working_dir='tree')
 
594
 
 
595
    def test_fixes_invalid_bug_number(self):
 
596
        tree = self.make_branch_and_tree('tree')
 
597
        self.build_tree(['tree/hello.txt'])
 
598
        tree.add('hello.txt')
 
599
        self.run_bzr_error(
 
600
            ["Did not understand bug identifier orange: Must be an integer. "
 
601
             "See \"bzr help bugs\" for more information on this feature.\n"
 
602
             "Commit refused."],
 
603
            'commit -m add-b --fixes=lp:orange',
 
604
            working_dir='tree')
 
605
 
 
606
    def test_fixes_invalid_argument(self):
 
607
        """Raise an appropriate error when the fixes argument isn't tag:id."""
 
608
        tree = self.make_branch_and_tree('tree')
 
609
        self.build_tree(['tree/hello.txt'])
 
610
        tree.add('hello.txt')
 
611
        self.run_bzr_error(
 
612
            [r"Invalid bug orange. Must be in the form of 'tracker:id'\. "
 
613
             r"See \"bzr help bugs\" for more information on this feature.\n"
 
614
             r"Commit refused\."],
 
615
            'commit -m add-b --fixes=orange',
 
616
            working_dir='tree')
 
617
 
 
618
    def test_no_author(self):
 
619
        """If the author is not specified, the author property is not set."""
 
620
        tree = self.make_branch_and_tree('tree')
 
621
        self.build_tree(['tree/hello.txt'])
 
622
        tree.add('hello.txt')
 
623
        self.run_bzr( 'commit -m hello tree/hello.txt')
 
624
        last_rev = tree.branch.repository.get_revision(tree.last_revision())
 
625
        properties = last_rev.properties
 
626
        self.assertFalse('author' in properties)
 
627
 
 
628
    def test_author_sets_property(self):
 
629
        """commit --author='John Doe <jdoe@example.com>' sets the author
 
630
           revprop.
 
631
        """
 
632
        tree = self.make_branch_and_tree('tree')
 
633
        self.build_tree(['tree/hello.txt'])
 
634
        tree.add('hello.txt')
 
635
        self.run_bzr(["commit", '-m', 'hello',
 
636
                      '--author', u'John D\xf6 <jdoe@example.com>',
 
637
                     "tree/hello.txt"])
 
638
        last_rev = tree.branch.repository.get_revision(tree.last_revision())
 
639
        properties = last_rev.properties
 
640
        self.assertEqual(u'John D\xf6 <jdoe@example.com>', properties['authors'])
 
641
 
 
642
    def test_author_no_email(self):
 
643
        """Author's name without an email address is allowed, too."""
 
644
        tree = self.make_branch_and_tree('tree')
 
645
        self.build_tree(['tree/hello.txt'])
 
646
        tree.add('hello.txt')
 
647
        out, err = self.run_bzr("commit -m hello --author='John Doe' "
 
648
                                "tree/hello.txt")
 
649
        last_rev = tree.branch.repository.get_revision(tree.last_revision())
 
650
        properties = last_rev.properties
 
651
        self.assertEqual('John Doe', properties['authors'])
 
652
 
 
653
    def test_multiple_authors(self):
 
654
        """Multiple authors can be specyfied, and all are stored."""
 
655
        tree = self.make_branch_and_tree('tree')
 
656
        self.build_tree(['tree/hello.txt'])
 
657
        tree.add('hello.txt')
 
658
        out, err = self.run_bzr("commit -m hello --author='John Doe' "
 
659
                                "--author='Jane Rey' tree/hello.txt")
 
660
        last_rev = tree.branch.repository.get_revision(tree.last_revision())
 
661
        properties = last_rev.properties
 
662
        self.assertEqual('John Doe\nJane Rey', properties['authors'])
 
663
 
 
664
    def test_commit_time(self):
 
665
        tree = self.make_branch_and_tree('tree')
 
666
        self.build_tree(['tree/hello.txt'])
 
667
        tree.add('hello.txt')
 
668
        out, err = self.run_bzr("commit -m hello "
 
669
            "--commit-time='2009-10-10 08:00:00 +0100' tree/hello.txt")
 
670
        last_rev = tree.branch.repository.get_revision(tree.last_revision())
 
671
        self.assertEqual(
 
672
            'Sat 2009-10-10 08:00:00 +0100',
 
673
            osutils.format_date(last_rev.timestamp, last_rev.timezone))
 
674
        
 
675
    def test_commit_time_bad_time(self):
 
676
        tree = self.make_branch_and_tree('tree')
 
677
        self.build_tree(['tree/hello.txt'])
 
678
        tree.add('hello.txt')
 
679
        out, err = self.run_bzr("commit -m hello "
 
680
            "--commit-time='NOT A TIME' tree/hello.txt", retcode=3)
 
681
        self.assertStartsWith(
 
682
            err, "bzr: ERROR: Could not parse --commit-time:")
 
683
 
 
684
    def test_partial_commit_with_renames_in_tree(self):
 
685
        # this test illustrates bug #140419
 
686
        t = self.make_branch_and_tree('.')
 
687
        self.build_tree(['dir/', 'dir/a', 'test'])
 
688
        t.add(['dir/', 'dir/a', 'test'])
 
689
        t.commit('initial commit')
 
690
        # important part: file dir/a should change parent
 
691
        # and should appear before old parent
 
692
        # then during partial commit we have error
 
693
        # parent_id {dir-XXX} not in inventory
 
694
        t.rename_one('dir/a', 'a')
 
695
        self.build_tree_contents([('test', 'changes in test')])
 
696
        # partial commit
 
697
        out, err = self.run_bzr('commit test -m "partial commit"')
 
698
        self.assertEquals('', out)
 
699
        self.assertContainsRe(err, r'modified test\nCommitted revision 2.')
 
700
 
 
701
    def test_commit_readonly_checkout(self):
 
702
        # https://bugs.launchpad.net/bzr/+bug/129701
 
703
        # "UnlockableTransport error trying to commit in checkout of readonly
 
704
        # branch"
 
705
        self.make_branch('master')
 
706
        master = BzrDir.open_from_transport(
 
707
            self.get_readonly_transport('master')).open_branch()
 
708
        master.create_checkout('checkout')
 
709
        out, err = self.run_bzr(['commit', '--unchanged', '-mfoo', 'checkout'],
 
710
            retcode=3)
 
711
        self.assertContainsRe(err,
 
712
            r'^bzr: ERROR: Cannot lock.*readonly transport')
 
713
 
 
714
    def setup_editor(self):
 
715
        # Test that commit template hooks work
 
716
        if sys.platform == "win32":
 
717
            f = file('fed.bat', 'w')
 
718
            f.write('@rem dummy fed')
 
719
            f.close()
 
720
            osutils.set_or_unset_env('BZR_EDITOR', "fed.bat")
 
721
        else:
 
722
            f = file('fed.sh', 'wb')
 
723
            f.write('#!/bin/sh\n')
 
724
            f.close()
 
725
            os.chmod('fed.sh', 0755)
 
726
            osutils.set_or_unset_env('BZR_EDITOR', "./fed.sh")
 
727
 
 
728
    def setup_commit_with_template(self):
 
729
        self.setup_editor()
 
730
        msgeditor.hooks.install_named_hook("commit_message_template",
 
731
                lambda commit_obj, msg: "save me some typing\n", None)
 
732
        tree = self.make_branch_and_tree('tree')
 
733
        self.build_tree(['tree/hello.txt'])
 
734
        tree.add('hello.txt')
 
735
        return tree
 
736
 
 
737
    def test_commit_hook_template_accepted(self):
 
738
        tree = self.setup_commit_with_template()
 
739
        out, err = self.run_bzr("commit tree/hello.txt", stdin="y\n")
 
740
        last_rev = tree.branch.repository.get_revision(tree.last_revision())
 
741
        self.assertEqual('save me some typing\n', last_rev.message)
 
742
 
 
743
    def test_commit_hook_template_rejected(self):
 
744
        tree = self.setup_commit_with_template()
 
745
        expected = tree.last_revision()
 
746
        out, err = self.run_bzr_error(["empty commit message"],
 
747
            "commit tree/hello.txt", stdin="n\n")
 
748
        self.assertEqual(expected, tree.last_revision())
 
749
 
 
750
    def test_commit_without_username(self):
 
751
        """Ensure commit error if username is not set.
 
752
        """
 
753
        self.run_bzr(['init', 'foo'])
 
754
        os.chdir('foo')
 
755
        open('foo.txt', 'w').write('hello')
 
756
        self.run_bzr(['add'])
 
757
        osutils.set_or_unset_env('EMAIL', None)
 
758
        osutils.set_or_unset_env('BZR_EMAIL', None)
 
759
        out, err = self.run_bzr(['commit', '-m', 'initial'], 3)
 
760
        self.assertContainsRe(err, 'Unable to determine your name')
 
761
 
 
762
    def test_commit_recursive_checkout(self):
 
763
        """Ensure that a commit to a recursive checkout fails cleanly.
 
764
        """
 
765
        self.run_bzr(['init', 'test_branch'])
 
766
        self.run_bzr(['checkout', 'test_branch', 'test_checkout'])
 
767
        os.chdir('test_checkout')
 
768
        self.run_bzr(['bind', '.']) # bind to self
 
769
        open('foo.txt', 'w').write('hello')
 
770
        self.run_bzr(['add'])
 
771
        out, err = self.run_bzr(['commit', '-m', 'addedfoo'], 3)
 
772
        self.assertEqual(out, '')
 
773
        self.assertContainsRe(err,
 
774
            'Branch.*test_checkout.*appears to be bound to itself')
 
775