1
# Copyright (C) 2005-2010 Canonical Ltd
1
# Copyright (C) 2005, 2006 by Canonical Ltd
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.
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.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
"""Tests of status command.
19
19
Most of these depend on the particular formatting used.
20
As such they really are blackbox tests even though some of the
20
As such they really are blackbox tests even though some of the
21
21
tests are not using self.capture. If we add tests for the programmatic
22
22
interface later, they will be non blackbox tests.
25
25
from cStringIO import StringIO
27
from os import mkdir, chdir, rmdir, unlink
27
from os import mkdir, chdir
29
29
from tempfile import TemporaryFile
38
31
import bzrlib.branch
32
from bzrlib.builtins import merge
33
import bzrlib.bzrdir as bzrdir
34
import bzrlib.errors as errors
39
35
from bzrlib.osutils import pathjoin
40
36
from bzrlib.revisionspec import RevisionSpec
41
37
from bzrlib.status import show_tree_status
46
42
class BranchStatus(TestCaseWithTransport):
49
super(BranchStatus, self).setUp()
50
# As TestCase.setUp clears all hooks, we install this default
51
# post_status hook handler for the test.
52
status.hooks.install_named_hook('post_status',
53
status._show_shelve_summary,
56
def assertStatus(self, expected_lines, working_tree,
57
revision=None, short=False, pending=True, verbose=False):
58
"""Run status in working_tree and look for output.
60
:param expected_lines: The lines to look for.
61
:param working_tree: The tree to run status in.
63
output_string = self.status_string(working_tree, revision, short,
65
self.assertEqual(expected_lines, output_string.splitlines(True))
67
def status_string(self, wt, revision=None, short=False, pending=True,
69
# use a real file rather than StringIO because it doesn't handle
71
tof = codecs.getwriter('utf-8')(TemporaryFile())
72
show_tree_status(wt, to_file=tof, revision=revision, short=short,
73
show_pending=pending, verbose=verbose)
75
return tof.read().decode('utf-8')
77
def test_branch_status(self):
44
def test_branch_status(self):
78
45
"""Test basic branch status"""
79
46
wt = self.make_branch_and_tree('.')
81
# status with no commits or files - it must
82
# work and show no output. We do this with no
83
# commits to be sure that it's not going to fail
85
self.assertStatus([], wt)
51
show_tree_status(wt, to_file=tof)
52
self.assertEquals(tof.getvalue(), "")
87
55
self.build_tree(['hello.c', 'bye.c'])
100
# add a commit to allow showing pending merges.
101
wt.commit('create a parent to allow testing merge output')
103
wt.add_parent_tree_id('pending@pending-0-0')
108
'pending merge tips: (use -v to see all merge revisions)\n',
109
' (ghost) pending@pending-0-0\n',
117
' (ghost) pending@pending-0-0\n',
123
'P (ghost) pending@pending-0-0\n',
136
wt, short=True, pending=False)
56
wt.add_pending_merge('pending@pending-0-0')
57
show_tree_status(wt, to_file=tof)
59
self.assertEquals(tof.readlines(),
64
' pending@pending-0-0\n'
138
67
def test_branch_status_revisions(self):
139
68
"""Tests branch status with revisions"""
140
69
wt = self.make_branch_and_tree('.')
142
73
self.build_tree(['hello.c', 'bye.c'])
145
76
wt.commit('Test message')
147
revs = [RevisionSpec.from_string('0')]
80
revs.append(RevisionSpec(0))
82
show_tree_status(wt, to_file=tof, revision=revs)
85
self.assertEquals(tof.readlines(),
156
90
self.build_tree(['more.c'])
158
92
wt.commit('Another test message')
95
revs.append(RevisionSpec(1))
97
show_tree_status(wt, to_file=tof, revision=revs)
100
self.assertEquals(tof.readlines(),
160
revs.append(RevisionSpec.from_string('1'))
105
def status_string(self, wt):
106
# use a real file rather than StringIO because it doesn't handle
108
tof = codecs.getwriter('utf-8')(TemporaryFile())
109
show_tree_status(wt, to_file=tof)
111
return tof.read().decode('utf-8')
169
113
def test_pending(self):
170
114
"""Pending merges display works, including Unicode"""
176
120
b_2 = b_2_dir.open_branch()
177
121
wt2 = b_2_dir.open_workingtree()
178
122
wt.commit(u"\N{TIBETAN DIGIT TWO} Empty commit 2")
179
wt2.merge_from_branch(wt.branch)
180
message = self.status_string(wt2, verbose=True)
181
self.assertStartsWith(message, "pending merges:\n")
182
self.assertEndsWith(message, "Empty commit 2\n")
123
merge(["./branch", -1], [None, None], this_dir = './copy')
124
message = self.status_string(wt2)
125
self.assert_(message.startswith("pending merges:\n"))
126
self.assert_(message.endswith("Empty commit 2\n"))
183
127
wt2.commit("merged")
184
128
# must be long to make sure we see elipsis at the end
185
wt.commit("Empty commit 3 " +
186
"blah blah blah blah " * 100)
187
wt2.merge_from_branch(wt.branch)
188
message = self.status_string(wt2, verbose=True)
189
self.assertStartsWith(message, "pending merges:\n")
129
wt.commit("Empty commit 3 " +
130
"blah blah blah blah " * 10)
131
merge(["./branch", -1], [None, None], this_dir = './copy')
132
message = self.status_string(wt2)
133
self.assert_(message.startswith("pending merges:\n"))
190
134
self.assert_("Empty commit 3" in message)
191
self.assertEndsWith(message, "...\n")
193
def test_tree_status_ignores(self):
194
"""Tests branch status with ignores"""
195
wt = self.make_branch_and_tree('.')
196
self.run_bzr('ignore *~')
197
wt.commit('commit .bzrignore')
198
self.build_tree(['foo.c', 'foo.c~'])
209
def test_tree_status_specific_files(self):
135
self.assert_(message.endswith("...\n"))
137
def test_branch_status_specific_files(self):
210
138
"""Tests branch status with given specific files"""
211
139
wt = self.make_branch_and_tree('.')
245
167
' directory/hello.c\n'
248
show_tree_status(wt, specific_files=['directory'], to_file=tof,
251
self.assertEquals(tof.readlines(), ['? directory/hello.c\n'])
254
170
show_tree_status(wt, specific_files=['dir2'], to_file=tof)
256
172
self.assertEquals(tof.readlines(),
261
show_tree_status(wt, specific_files=['dir2'], to_file=tof, short=True)
263
self.assertEquals(tof.readlines(), ['? dir2/\n'])
266
revs = [RevisionSpec.from_string('0'), RevisionSpec.from_string('1')]
267
show_tree_status(wt, specific_files=['test.c'], to_file=tof,
268
short=True, revision=revs)
270
self.assertEquals(tof.readlines(), ['+N test.c\n'])
272
def test_specific_files_conflicts(self):
273
tree = self.make_branch_and_tree('.')
274
self.build_tree(['dir2/'])
276
tree.commit('added dir2')
277
tree.set_conflicts(conflicts.ConflictList(
278
[conflicts.ContentsConflict('foo')]))
280
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
281
self.assertEqualDiff('', tof.getvalue())
282
tree.set_conflicts(conflicts.ConflictList(
283
[conflicts.ContentsConflict('dir2')]))
285
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
286
self.assertEqualDiff('conflicts:\n Contents conflict in dir2\n',
289
tree.set_conflicts(conflicts.ConflictList(
290
[conflicts.ContentsConflict('dir2/file1')]))
292
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
293
self.assertEqualDiff('conflicts:\n Contents conflict in dir2/file1\n',
296
def _prepare_nonexistent(self):
297
wt = self.make_branch_and_tree('.')
298
self.assertStatus([], wt)
299
self.build_tree(['FILE_A', 'FILE_B', 'FILE_C', 'FILE_D', 'FILE_E', ])
305
wt.commit('Create five empty files.')
306
open('FILE_B', 'w').write('Modification to file FILE_B.')
307
open('FILE_C', 'w').write('Modification to file FILE_C.')
308
unlink('FILE_E') # FILE_E will be versioned but missing
309
open('FILE_Q', 'w').write('FILE_Q is added but not committed.')
310
wt.add('FILE_Q') # FILE_Q will be added but not committed
311
open('UNVERSIONED_BUT_EXISTING', 'w')
314
177
def test_status_nonexistent_file(self):
315
178
# files that don't exist in either the basis tree or working tree
316
179
# should give an error
317
wt = self._prepare_nonexistent()
327
' UNVERSIONED_BUT_EXISTING\n',
335
'? UNVERSIONED_BUT_EXISTING\n',
339
# Okay, everything's looking good with the existent files.
340
# Let's see what happens when we throw in non-existent files.
342
# bzr st [--short] NONEXISTENT '
347
out, err = self.run_bzr('status NONEXISTENT', retcode=3)
348
self.assertEqual(expected, out.splitlines(True))
349
self.assertContainsRe(err,
350
r'.*ERROR: Path\(s\) do not exist: '
355
out, err = self.run_bzr('status --short NONEXISTENT', retcode=3)
356
self.assertContainsRe(err,
357
r'.*ERROR: Path\(s\) do not exist: '
360
def test_status_nonexistent_file_with_others(self):
361
# bzr st [--short] NONEXISTENT ...others..
362
wt = self._prepare_nonexistent()
372
out, err = self.run_bzr('status NONEXISTENT '
373
'FILE_A FILE_B FILE_C FILE_D FILE_E',
375
self.assertEqual(expected, out.splitlines(True))
376
self.assertContainsRe(err,
377
r'.*ERROR: Path\(s\) do not exist: '
385
out, err = self.run_bzr('status --short NONEXISTENT '
386
'FILE_A FILE_B FILE_C FILE_D FILE_E',
388
self.assertEqual(expected, out.splitlines(True))
389
self.assertContainsRe(err,
390
r'.*ERROR: Path\(s\) do not exist: '
393
def test_status_multiple_nonexistent_files(self):
394
# bzr st [--short] NONEXISTENT ... ANOTHER_NONEXISTENT ...
395
wt = self._prepare_nonexistent()
403
' ANOTHER_NONEXISTENT\n',
406
out, err = self.run_bzr('status NONEXISTENT '
407
'FILE_A FILE_B ANOTHER_NONEXISTENT '
408
'FILE_C FILE_D FILE_E', retcode=3)
409
self.assertEqual(expected, out.splitlines(True))
410
self.assertContainsRe(err,
411
r'.*ERROR: Path\(s\) do not exist: '
412
'ANOTHER_NONEXISTENT NONEXISTENT.*')
417
'X ANOTHER_NONEXISTENT\n',
420
out, err = self.run_bzr('status --short NONEXISTENT '
421
'FILE_A FILE_B ANOTHER_NONEXISTENT '
422
'FILE_C FILE_D FILE_E', retcode=3)
423
self.assertEqual(expected, out.splitlines(True))
424
self.assertContainsRe(err,
425
r'.*ERROR: Path\(s\) do not exist: '
426
'ANOTHER_NONEXISTENT NONEXISTENT.*')
428
def test_status_nonexistent_file_with_unversioned(self):
429
# bzr st [--short] NONEXISTENT A B UNVERSIONED_BUT_EXISTING C D E Q
430
wt = self._prepare_nonexistent()
440
' UNVERSIONED_BUT_EXISTING\n',
444
out, err = self.run_bzr('status NONEXISTENT '
445
'FILE_A FILE_B UNVERSIONED_BUT_EXISTING '
446
'FILE_C FILE_D FILE_E FILE_Q', retcode=3)
447
self.assertEqual(expected, out.splitlines(True))
448
self.assertContainsRe(err,
449
r'.*ERROR: Path\(s\) do not exist: '
453
'? UNVERSIONED_BUT_EXISTING\n',
459
out, err = self.run_bzr('status --short NONEXISTENT '
460
'FILE_A FILE_B UNVERSIONED_BUT_EXISTING '
461
'FILE_C FILE_D FILE_E FILE_Q', retcode=3)
462
self.assertEqual(expected, out.splitlines(True))
463
self.assertContainsRe(err,
464
r'.*ERROR: Path\(s\) do not exist: '
467
def test_status_out_of_date(self):
468
"""Simulate status of out-of-date tree after remote push"""
469
tree = self.make_branch_and_tree('.')
470
self.build_tree_contents([('a', 'foo\n')])
474
tree.commit('add test file')
475
# simulate what happens after a remote push
476
tree.set_last_revision("0")
478
# before run another commands we should unlock tree
480
out, err = self.run_bzr('status')
481
self.assertEqual("working tree is out of date, run 'bzr update'\n",
484
def test_status_on_ignored(self):
485
"""Tests branch status on an unversioned file which is considered ignored.
487
See https://bugs.launchpad.net/bzr/+bug/40103
489
tree = self.make_branch_and_tree('.')
491
self.build_tree(['test1.c', 'test1.c~', 'test2.c~'])
492
result = self.run_bzr('status')[0]
493
self.assertContainsRe(result, "unknown:\n test1.c\n")
494
short_result = self.run_bzr('status --short')[0]
495
self.assertContainsRe(short_result, "\? test1.c\n")
497
result = self.run_bzr('status test1.c')[0]
498
self.assertContainsRe(result, "unknown:\n test1.c\n")
499
short_result = self.run_bzr('status --short test1.c')[0]
500
self.assertContainsRe(short_result, "\? test1.c\n")
502
result = self.run_bzr('status test1.c~')[0]
503
self.assertContainsRe(result, "ignored:\n test1.c~\n")
504
short_result = self.run_bzr('status --short test1.c~')[0]
505
self.assertContainsRe(short_result, "I test1.c~\n")
507
result = self.run_bzr('status test1.c~ test2.c~')[0]
508
self.assertContainsRe(result, "ignored:\n test1.c~\n test2.c~\n")
509
short_result = self.run_bzr('status --short test1.c~ test2.c~')[0]
510
self.assertContainsRe(short_result, "I test1.c~\nI test2.c~\n")
512
result = self.run_bzr('status test1.c test1.c~ test2.c~')[0]
513
self.assertContainsRe(result, "unknown:\n test1.c\nignored:\n test1.c~\n test2.c~\n")
514
short_result = self.run_bzr('status --short test1.c test1.c~ test2.c~')[0]
515
self.assertContainsRe(short_result, "\? test1.c\nI test1.c~\nI test2.c~\n")
517
def test_status_write_lock(self):
518
"""Test that status works without fetching history and
521
See https://bugs.launchpad.net/bzr/+bug/149270
524
wt = self.make_branch_and_tree('branch1')
526
wt.commit('Empty commit 1')
527
wt2 = b.bzrdir.sprout('branch2').open_workingtree()
528
wt2.commit('Empty commit 2')
529
out, err = self.run_bzr('status branch1 -rbranch:branch2')
530
self.assertEqual('', out)
532
def test_status_with_shelves(self):
533
"""Ensure that _show_shelve_summary handler works.
535
180
wt = self.make_branch_and_tree('.')
536
self.build_tree(['hello.c'])
538
self.run_bzr(['shelve', '--all', '-m', 'foo'])
539
self.build_tree(['bye.c'])
544
'1 shelves exist. See "bzr shelve --list" for details.\n',
181
out, err = self.run_bzr('status', 'does-not-exist', retcode=3)
182
self.assertContainsRe(err, r'do not exist.*does-not-exist')
549
185
class CheckoutStatus(BranchStatus):
552
188
super(CheckoutStatus, self).setUp()
556
192
def make_branch_and_tree(self, relpath):
557
193
source = self.make_branch(pathjoin('..', relpath))
558
194
checkout = bzrdir.BzrDirMetaFormat1().initialize(relpath)
559
bzrlib.branch.BranchReferenceFormat().initialize(checkout,
560
target_branch=source)
195
bzrlib.branch.BranchReferenceFormat().initialize(checkout, source)
561
196
return checkout.create_workingtree()
564
199
class TestStatus(TestCaseWithTransport):
566
def test_status_plain(self):
567
tree = self.make_branch_and_tree('.')
569
self.build_tree(['hello.txt'])
570
result = self.run_bzr("status")[0]
571
self.assertContainsRe(result, "unknown:\n hello.txt\n")
573
tree.add("hello.txt")
574
result = self.run_bzr("status")[0]
575
self.assertContainsRe(result, "added:\n hello.txt\n")
577
tree.commit(message="added")
578
result = self.run_bzr("status -r 0..1")[0]
579
self.assertContainsRe(result, "added:\n hello.txt\n")
581
result = self.run_bzr("status -c 1")[0]
582
self.assertContainsRe(result, "added:\n hello.txt\n")
584
self.build_tree(['world.txt'])
585
result = self.run_bzr("status -r 0")[0]
586
self.assertContainsRe(result, "added:\n hello.txt\n" \
587
"unknown:\n world.txt\n")
588
result2 = self.run_bzr("status -r 0..")[0]
589
self.assertEquals(result2, result)
591
def test_status_short(self):
592
tree = self.make_branch_and_tree('.')
594
self.build_tree(['hello.txt'])
595
result = self.run_bzr("status --short")[0]
596
self.assertContainsRe(result, "[?] hello.txt\n")
598
tree.add("hello.txt")
599
result = self.run_bzr("status --short")[0]
600
self.assertContainsRe(result, "[+]N hello.txt\n")
602
tree.commit(message="added")
603
result = self.run_bzr("status --short -r 0..1")[0]
604
self.assertContainsRe(result, "[+]N hello.txt\n")
606
self.build_tree(['world.txt'])
607
result = self.run_bzr("status --short -r 0")[0]
608
self.assertContainsRe(result, "[+]N hello.txt\n" \
610
result2 = self.run_bzr("status --short -r 0..")[0]
611
self.assertEquals(result2, result)
613
def test_status_versioned(self):
614
tree = self.make_branch_and_tree('.')
616
self.build_tree(['hello.txt'])
617
result = self.run_bzr("status --versioned")[0]
618
self.assertNotContainsRe(result, "unknown:\n hello.txt\n")
620
tree.add("hello.txt")
621
result = self.run_bzr("status --versioned")[0]
622
self.assertContainsRe(result, "added:\n hello.txt\n")
625
result = self.run_bzr("status --versioned -r 0..1")[0]
626
self.assertContainsRe(result, "added:\n hello.txt\n")
628
self.build_tree(['world.txt'])
629
result = self.run_bzr("status --versioned -r 0")[0]
630
self.assertContainsRe(result, "added:\n hello.txt\n")
631
self.assertNotContainsRe(result, "unknown:\n world.txt\n")
632
result2 = self.run_bzr("status --versioned -r 0..")[0]
633
self.assertEquals(result2, result)
635
def test_status_SV(self):
636
tree = self.make_branch_and_tree('.')
638
self.build_tree(['hello.txt'])
639
result = self.run_bzr("status -SV")[0]
640
self.assertNotContainsRe(result, "hello.txt")
642
tree.add("hello.txt")
643
result = self.run_bzr("status -SV")[0]
644
self.assertContainsRe(result, "[+]N hello.txt\n")
646
tree.commit(message="added")
647
result = self.run_bzr("status -SV -r 0..1")[0]
648
self.assertContainsRe(result, "[+]N hello.txt\n")
650
self.build_tree(['world.txt'])
651
result = self.run_bzr("status -SV -r 0")[0]
652
self.assertContainsRe(result, "[+]N hello.txt\n")
654
result2 = self.run_bzr("status -SV -r 0..")[0]
655
self.assertEquals(result2, result)
657
def assertStatusContains(self, pattern, short=False):
658
"""Run status, and assert it contains the given pattern"""
660
result = self.run_bzr("status --short")[0]
662
result = self.run_bzr("status")[0]
663
self.assertContainsRe(result, pattern)
665
def test_kind_change_plain(self):
666
tree = self.make_branch_and_tree('.')
667
self.build_tree(['file'])
669
tree.commit('added file')
671
self.build_tree(['file/'])
672
self.assertStatusContains('kind changed:\n file \(file => directory\)')
673
tree.rename_one('file', 'directory')
674
self.assertStatusContains('renamed:\n file/ => directory/\n' \
675
'modified:\n directory/\n')
677
self.assertStatusContains('removed:\n file\n')
679
def test_kind_change_short(self):
680
tree = self.make_branch_and_tree('.')
681
self.build_tree(['file'])
683
tree.commit('added file')
685
self.build_tree(['file/'])
686
self.assertStatusContains('K file => file/',
688
tree.rename_one('file', 'directory')
689
self.assertStatusContains('RK file => directory/',
692
self.assertStatusContains('RD file => directory',
695
def test_status_illegal_revision_specifiers(self):
696
out, err = self.run_bzr('status -r 1..23..123', retcode=3)
697
self.assertContainsRe(err, 'one or two revision specifiers')
699
def test_status_no_pending(self):
700
a_tree = self.make_branch_and_tree('a')
701
self.build_tree(['a/a'])
704
b_tree = a_tree.bzrdir.sprout('b').open_workingtree()
705
self.build_tree(['b/b'])
709
self.run_bzr('merge ../b', working_dir='a')
710
out, err = self.run_bzr('status --no-pending', working_dir='a')
711
self.assertEquals(out, "added:\n b\n")
713
def test_pending_specific_files(self):
714
"""With a specific file list, pending merges are not shown."""
715
tree = self.make_branch_and_tree('tree')
716
self.build_tree_contents([('tree/a', 'content of a\n')])
718
r1_id = tree.commit('one')
719
alt = tree.bzrdir.sprout('alt').open_workingtree()
720
self.build_tree_contents([('alt/a', 'content of a\nfrom alt\n')])
721
alt_id = alt.commit('alt')
722
tree.merge_from_branch(alt.branch)
723
output = self.make_utf8_encoded_stringio()
724
show_tree_status(tree, to_file=output)
725
self.assertContainsRe(output.getvalue(), 'pending merge')
726
out, err = self.run_bzr('status tree/a')
727
self.assertNotContainsRe(out, 'pending merge')
201
def test_status(self):
203
self.build_tree(['hello.txt'])
204
result = self.run_bzr("status")[0]
205
self.assert_("unknown:\n hello.txt\n" in result, result)
206
self.run_bzr("add", "hello.txt")
207
result = self.run_bzr("status")[0]
208
self.assert_("added:\n hello.txt\n" in result, result)
209
self.run_bzr("commit", "-m", "added")
210
result = self.run_bzr("status", "-r", "0..1")[0]
211
self.assert_("added:\n hello.txt\n" in result, result)
212
self.build_tree(['world.txt'])
213
result = self.run_bzr("status", "-r", "0")[0]
214
self.assert_("added:\n hello.txt\n" \
215
"unknown:\n world.txt\n" in result, result)
217
result2 = self.run_bzr("status", "-r", "0..")[0]
218
self.assertEquals(result2, result)
730
221
class TestStatusEncodings(TestCaseWithTransport):
224
TestCaseWithTransport.setUp(self)
225
self.user_encoding = bzrlib.user_encoding
226
self.stdout = sys.stdout
229
bzrlib.user_encoding = self.user_encoding
230
sys.stdout = self.stdout
231
TestCaseWithTransport.tearDown(self)
732
233
def make_uncommitted_tree(self):
733
234
"""Build a branch with uncommitted unicode named changes in the cwd."""