1
# Copyright (C) 2005-2010 Canonical Ltd
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.
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.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests of status command.
19
Most of these depend on the particular formatting used.
20
As such they really are blackbox tests even though some of the
21
tests are not using self.capture. If we add tests for the programmatic
22
interface later, they will be non blackbox tests.
25
from cStringIO import StringIO
27
from os import mkdir, chdir, rmdir, unlink
29
from tempfile import TemporaryFile
39
from bzrlib.osutils import pathjoin
40
from bzrlib.revisionspec import RevisionSpec
41
from bzrlib.status import show_tree_status
42
from bzrlib.tests import TestCaseWithTransport, TestSkipped
43
from bzrlib.workingtree import WorkingTree
46
class BranchStatus(TestCaseWithTransport):
48
def assertStatus(self, expected_lines, working_tree,
49
revision=None, short=False, pending=True, verbose=False):
50
"""Run status in working_tree and look for output.
52
:param expected_lines: The lines to look for.
53
:param working_tree: The tree to run status in.
55
output_string = self.status_string(working_tree, revision, short,
57
self.assertEqual(expected_lines, output_string.splitlines(True))
59
def status_string(self, wt, revision=None, short=False, pending=True,
61
# use a real file rather than StringIO because it doesn't handle
63
tof = codecs.getwriter('utf-8')(TemporaryFile())
64
show_tree_status(wt, to_file=tof, revision=revision, short=short,
65
show_pending=pending, verbose=verbose)
67
return tof.read().decode('utf-8')
69
def test_branch_status(self):
70
"""Test basic branch status"""
71
wt = self.make_branch_and_tree('.')
73
# status with no commits or files - it must
74
# work and show no output. We do this with no
75
# commits to be sure that it's not going to fail
77
self.assertStatus([], wt)
79
self.build_tree(['hello.c', 'bye.c'])
92
# add a commit to allow showing pending merges.
93
wt.commit('create a parent to allow testing merge output')
95
wt.add_parent_tree_id('pending@pending-0-0')
100
'pending merge tips: (use -v to see all merge revisions)\n',
101
' (ghost) pending@pending-0-0\n',
109
' (ghost) pending@pending-0-0\n',
115
'P (ghost) pending@pending-0-0\n',
128
wt, short=True, pending=False)
130
def test_branch_status_revisions(self):
131
"""Tests branch status with revisions"""
132
wt = self.make_branch_and_tree('.')
134
self.build_tree(['hello.c', 'bye.c'])
137
wt.commit('Test message')
139
revs = [RevisionSpec.from_string('0')]
148
self.build_tree(['more.c'])
150
wt.commit('Another test message')
152
revs.append(RevisionSpec.from_string('1'))
161
def test_pending(self):
162
"""Pending merges display works, including Unicode"""
164
wt = self.make_branch_and_tree('branch')
166
wt.commit("Empty commit 1")
167
b_2_dir = b.bzrdir.sprout('./copy')
168
b_2 = b_2_dir.open_branch()
169
wt2 = b_2_dir.open_workingtree()
170
wt.commit(u"\N{TIBETAN DIGIT TWO} Empty commit 2")
171
wt2.merge_from_branch(wt.branch)
172
message = self.status_string(wt2, verbose=True)
173
self.assertStartsWith(message, "pending merges:\n")
174
self.assertEndsWith(message, "Empty commit 2\n")
176
# must be long to make sure we see elipsis at the end
177
wt.commit("Empty commit 3 " +
178
"blah blah blah blah " * 100)
179
wt2.merge_from_branch(wt.branch)
180
message = self.status_string(wt2, verbose=True)
181
self.assertStartsWith(message, "pending merges:\n")
182
self.assert_("Empty commit 3" in message)
183
self.assertEndsWith(message, "...\n")
185
def test_tree_status_ignores(self):
186
"""Tests branch status with ignores"""
187
wt = self.make_branch_and_tree('.')
188
self.run_bzr('ignore *~')
189
wt.commit('commit .bzrignore')
190
self.build_tree(['foo.c', 'foo.c~'])
201
def test_tree_status_specific_files(self):
202
"""Tests branch status with given specific files"""
203
wt = self.make_branch_and_tree('.')
206
self.build_tree(['directory/','directory/hello.c', 'bye.c','test.c','dir2/'])
215
' directory/hello.c\n'
222
'? directory/hello.c\n'
227
self.assertRaises(errors.PathsDoNotExist,
229
wt, specific_files=['bye.c','test.c','absent.c'],
233
show_tree_status(wt, specific_files=['directory'], to_file=tof)
235
self.assertEquals(tof.readlines(),
237
' directory/hello.c\n'
240
show_tree_status(wt, specific_files=['directory'], to_file=tof,
243
self.assertEquals(tof.readlines(), ['? directory/hello.c\n'])
246
show_tree_status(wt, specific_files=['dir2'], to_file=tof)
248
self.assertEquals(tof.readlines(),
253
show_tree_status(wt, specific_files=['dir2'], to_file=tof, short=True)
255
self.assertEquals(tof.readlines(), ['? dir2/\n'])
258
revs = [RevisionSpec.from_string('0'), RevisionSpec.from_string('1')]
259
show_tree_status(wt, specific_files=['test.c'], to_file=tof,
260
short=True, revision=revs)
262
self.assertEquals(tof.readlines(), ['+N test.c\n'])
264
def test_specific_files_conflicts(self):
265
tree = self.make_branch_and_tree('.')
266
self.build_tree(['dir2/'])
268
tree.commit('added dir2')
269
tree.set_conflicts(conflicts.ConflictList(
270
[conflicts.ContentsConflict('foo')]))
272
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
273
self.assertEqualDiff('', tof.getvalue())
274
tree.set_conflicts(conflicts.ConflictList(
275
[conflicts.ContentsConflict('dir2')]))
277
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
278
self.assertEqualDiff('conflicts:\n Contents conflict in dir2\n',
281
tree.set_conflicts(conflicts.ConflictList(
282
[conflicts.ContentsConflict('dir2/file1')]))
284
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
285
self.assertEqualDiff('conflicts:\n Contents conflict in dir2/file1\n',
288
def _prepare_nonexistent(self):
289
wt = self.make_branch_and_tree('.')
290
self.assertStatus([], wt)
291
self.build_tree(['FILE_A', 'FILE_B', 'FILE_C', 'FILE_D', 'FILE_E', ])
297
wt.commit('Create five empty files.')
298
open('FILE_B', 'w').write('Modification to file FILE_B.')
299
open('FILE_C', 'w').write('Modification to file FILE_C.')
300
unlink('FILE_E') # FILE_E will be versioned but missing
301
open('FILE_Q', 'w').write('FILE_Q is added but not committed.')
302
wt.add('FILE_Q') # FILE_Q will be added but not committed
303
open('UNVERSIONED_BUT_EXISTING', 'w')
306
def test_status_nonexistent_file(self):
307
# files that don't exist in either the basis tree or working tree
308
# should give an error
309
wt = self._prepare_nonexistent()
319
' UNVERSIONED_BUT_EXISTING\n',
327
'? UNVERSIONED_BUT_EXISTING\n',
331
# Okay, everything's looking good with the existent files.
332
# Let's see what happens when we throw in non-existent files.
334
# bzr st [--short] NONEXISTENT '
339
out, err = self.run_bzr('status NONEXISTENT', retcode=3)
340
self.assertEqual(expected, out.splitlines(True))
341
self.assertContainsRe(err,
342
r'.*ERROR: Path\(s\) do not exist: '
347
out, err = self.run_bzr('status --short NONEXISTENT', retcode=3)
348
self.assertContainsRe(err,
349
r'.*ERROR: Path\(s\) do not exist: '
352
def test_status_nonexistent_file_with_others(self):
353
# bzr st [--short] NONEXISTENT ...others..
354
wt = self._prepare_nonexistent()
364
out, err = self.run_bzr('status NONEXISTENT '
365
'FILE_A FILE_B FILE_C FILE_D FILE_E',
367
self.assertEqual(expected, out.splitlines(True))
368
self.assertContainsRe(err,
369
r'.*ERROR: Path\(s\) do not exist: '
377
out, err = self.run_bzr('status --short NONEXISTENT '
378
'FILE_A FILE_B FILE_C FILE_D FILE_E',
380
self.assertEqual(expected, out.splitlines(True))
381
self.assertContainsRe(err,
382
r'.*ERROR: Path\(s\) do not exist: '
385
def test_status_multiple_nonexistent_files(self):
386
# bzr st [--short] NONEXISTENT ... ANOTHER_NONEXISTENT ...
387
wt = self._prepare_nonexistent()
395
' ANOTHER_NONEXISTENT\n',
398
out, err = self.run_bzr('status NONEXISTENT '
399
'FILE_A FILE_B ANOTHER_NONEXISTENT '
400
'FILE_C FILE_D FILE_E', retcode=3)
401
self.assertEqual(expected, out.splitlines(True))
402
self.assertContainsRe(err,
403
r'.*ERROR: Path\(s\) do not exist: '
404
'ANOTHER_NONEXISTENT NONEXISTENT.*')
409
'X ANOTHER_NONEXISTENT\n',
412
out, err = self.run_bzr('status --short NONEXISTENT '
413
'FILE_A FILE_B ANOTHER_NONEXISTENT '
414
'FILE_C FILE_D FILE_E', retcode=3)
415
self.assertEqual(expected, out.splitlines(True))
416
self.assertContainsRe(err,
417
r'.*ERROR: Path\(s\) do not exist: '
418
'ANOTHER_NONEXISTENT NONEXISTENT.*')
420
def test_status_nonexistent_file_with_unversioned(self):
421
# bzr st [--short] NONEXISTENT A B UNVERSIONED_BUT_EXISTING C D E Q
422
wt = self._prepare_nonexistent()
432
' UNVERSIONED_BUT_EXISTING\n',
436
out, err = self.run_bzr('status NONEXISTENT '
437
'FILE_A FILE_B UNVERSIONED_BUT_EXISTING '
438
'FILE_C FILE_D FILE_E FILE_Q', retcode=3)
439
self.assertEqual(expected, out.splitlines(True))
440
self.assertContainsRe(err,
441
r'.*ERROR: Path\(s\) do not exist: '
445
'? UNVERSIONED_BUT_EXISTING\n',
451
out, err = self.run_bzr('status --short NONEXISTENT '
452
'FILE_A FILE_B UNVERSIONED_BUT_EXISTING '
453
'FILE_C FILE_D FILE_E FILE_Q', retcode=3)
454
self.assertEqual(expected, out.splitlines(True))
455
self.assertContainsRe(err,
456
r'.*ERROR: Path\(s\) do not exist: '
459
def test_status_out_of_date(self):
460
"""Simulate status of out-of-date tree after remote push"""
461
tree = self.make_branch_and_tree('.')
462
self.build_tree_contents([('a', 'foo\n')])
466
tree.commit('add test file')
467
# simulate what happens after a remote push
468
tree.set_last_revision("0")
470
# before run another commands we should unlock tree
472
out, err = self.run_bzr('status')
473
self.assertEqual("working tree is out of date, run 'bzr update'\n",
476
def test_status_on_ignored(self):
477
"""Tests branch status on an unversioned file which is considered ignored.
479
See https://bugs.launchpad.net/bzr/+bug/40103
481
tree = self.make_branch_and_tree('.')
483
self.build_tree(['test1.c', 'test1.c~', 'test2.c~'])
484
result = self.run_bzr('status')[0]
485
self.assertContainsRe(result, "unknown:\n test1.c\n")
486
short_result = self.run_bzr('status --short')[0]
487
self.assertContainsRe(short_result, "\? test1.c\n")
489
result = self.run_bzr('status test1.c')[0]
490
self.assertContainsRe(result, "unknown:\n test1.c\n")
491
short_result = self.run_bzr('status --short test1.c')[0]
492
self.assertContainsRe(short_result, "\? test1.c\n")
494
result = self.run_bzr('status test1.c~')[0]
495
self.assertContainsRe(result, "ignored:\n test1.c~\n")
496
short_result = self.run_bzr('status --short test1.c~')[0]
497
self.assertContainsRe(short_result, "I test1.c~\n")
499
result = self.run_bzr('status test1.c~ test2.c~')[0]
500
self.assertContainsRe(result, "ignored:\n test1.c~\n test2.c~\n")
501
short_result = self.run_bzr('status --short test1.c~ test2.c~')[0]
502
self.assertContainsRe(short_result, "I test1.c~\nI test2.c~\n")
504
result = self.run_bzr('status test1.c test1.c~ test2.c~')[0]
505
self.assertContainsRe(result, "unknown:\n test1.c\nignored:\n test1.c~\n test2.c~\n")
506
short_result = self.run_bzr('status --short test1.c test1.c~ test2.c~')[0]
507
self.assertContainsRe(short_result, "\? test1.c\nI test1.c~\nI test2.c~\n")
509
def test_status_write_lock(self):
510
"""Test that status works without fetching history and
513
See https://bugs.launchpad.net/bzr/+bug/149270
516
wt = self.make_branch_and_tree('branch1')
518
wt.commit('Empty commit 1')
519
wt2 = b.bzrdir.sprout('branch2').open_workingtree()
520
wt2.commit('Empty commit 2')
521
out, err = self.run_bzr('status branch1 -rbranch:branch2')
522
self.assertEqual('', out)
524
def test_status_with_shelves(self):
525
"""Ensure that _show_shelve_summary handler works.
527
wt = self.make_branch_and_tree('.')
528
self.build_tree(['hello.c'])
530
self.run_bzr(['shelve', '--all', '-m', 'foo'])
531
self.build_tree(['bye.c'])
533
# As TestCase.setUp clears all hooks, we install this default
534
# post_status hook handler for the test.
535
status.hooks.install_named_hook('post_status',
536
status._show_shelve_summary,
541
'1 shelves exist. See "bzr shelve --list" for details.\n',
546
class CheckoutStatus(BranchStatus):
549
super(CheckoutStatus, self).setUp()
553
def make_branch_and_tree(self, relpath):
554
source = self.make_branch(pathjoin('..', relpath))
555
checkout = bzrdir.BzrDirMetaFormat1().initialize(relpath)
556
bzrlib.branch.BranchReferenceFormat().initialize(checkout,
557
target_branch=source)
558
return checkout.create_workingtree()
561
class TestStatus(TestCaseWithTransport):
563
def test_status_plain(self):
564
tree = self.make_branch_and_tree('.')
566
self.build_tree(['hello.txt'])
567
result = self.run_bzr("status")[0]
568
self.assertContainsRe(result, "unknown:\n hello.txt\n")
570
tree.add("hello.txt")
571
result = self.run_bzr("status")[0]
572
self.assertContainsRe(result, "added:\n hello.txt\n")
574
tree.commit(message="added")
575
result = self.run_bzr("status -r 0..1")[0]
576
self.assertContainsRe(result, "added:\n hello.txt\n")
578
result = self.run_bzr("status -c 1")[0]
579
self.assertContainsRe(result, "added:\n hello.txt\n")
581
self.build_tree(['world.txt'])
582
result = self.run_bzr("status -r 0")[0]
583
self.assertContainsRe(result, "added:\n hello.txt\n" \
584
"unknown:\n world.txt\n")
585
result2 = self.run_bzr("status -r 0..")[0]
586
self.assertEquals(result2, result)
588
def test_status_short(self):
589
tree = self.make_branch_and_tree('.')
591
self.build_tree(['hello.txt'])
592
result = self.run_bzr("status --short")[0]
593
self.assertContainsRe(result, "[?] hello.txt\n")
595
tree.add("hello.txt")
596
result = self.run_bzr("status --short")[0]
597
self.assertContainsRe(result, "[+]N hello.txt\n")
599
tree.commit(message="added")
600
result = self.run_bzr("status --short -r 0..1")[0]
601
self.assertContainsRe(result, "[+]N hello.txt\n")
603
self.build_tree(['world.txt'])
604
result = self.run_bzr("status --short -r 0")[0]
605
self.assertContainsRe(result, "[+]N hello.txt\n" \
607
result2 = self.run_bzr("status --short -r 0..")[0]
608
self.assertEquals(result2, result)
610
def test_status_versioned(self):
611
tree = self.make_branch_and_tree('.')
613
self.build_tree(['hello.txt'])
614
result = self.run_bzr("status --versioned")[0]
615
self.assertNotContainsRe(result, "unknown:\n hello.txt\n")
617
tree.add("hello.txt")
618
result = self.run_bzr("status --versioned")[0]
619
self.assertContainsRe(result, "added:\n hello.txt\n")
622
result = self.run_bzr("status --versioned -r 0..1")[0]
623
self.assertContainsRe(result, "added:\n hello.txt\n")
625
self.build_tree(['world.txt'])
626
result = self.run_bzr("status --versioned -r 0")[0]
627
self.assertContainsRe(result, "added:\n hello.txt\n")
628
self.assertNotContainsRe(result, "unknown:\n world.txt\n")
629
result2 = self.run_bzr("status --versioned -r 0..")[0]
630
self.assertEquals(result2, result)
632
def test_status_SV(self):
633
tree = self.make_branch_and_tree('.')
635
self.build_tree(['hello.txt'])
636
result = self.run_bzr("status -SV")[0]
637
self.assertNotContainsRe(result, "hello.txt")
639
tree.add("hello.txt")
640
result = self.run_bzr("status -SV")[0]
641
self.assertContainsRe(result, "[+]N hello.txt\n")
643
tree.commit(message="added")
644
result = self.run_bzr("status -SV -r 0..1")[0]
645
self.assertContainsRe(result, "[+]N hello.txt\n")
647
self.build_tree(['world.txt'])
648
result = self.run_bzr("status -SV -r 0")[0]
649
self.assertContainsRe(result, "[+]N hello.txt\n")
651
result2 = self.run_bzr("status -SV -r 0..")[0]
652
self.assertEquals(result2, result)
654
def assertStatusContains(self, pattern, short=False):
655
"""Run status, and assert it contains the given pattern"""
657
result = self.run_bzr("status --short")[0]
659
result = self.run_bzr("status")[0]
660
self.assertContainsRe(result, pattern)
662
def test_kind_change_plain(self):
663
tree = self.make_branch_and_tree('.')
664
self.build_tree(['file'])
666
tree.commit('added file')
668
self.build_tree(['file/'])
669
self.assertStatusContains('kind changed:\n file \(file => directory\)')
670
tree.rename_one('file', 'directory')
671
self.assertStatusContains('renamed:\n file/ => directory/\n' \
672
'modified:\n directory/\n')
674
self.assertStatusContains('removed:\n file\n')
676
def test_kind_change_short(self):
677
tree = self.make_branch_and_tree('.')
678
self.build_tree(['file'])
680
tree.commit('added file')
682
self.build_tree(['file/'])
683
self.assertStatusContains('K file => file/',
685
tree.rename_one('file', 'directory')
686
self.assertStatusContains('RK file => directory/',
689
self.assertStatusContains('RD file => directory',
692
def test_status_illegal_revision_specifiers(self):
693
out, err = self.run_bzr('status -r 1..23..123', retcode=3)
694
self.assertContainsRe(err, 'one or two revision specifiers')
696
def test_status_no_pending(self):
697
a_tree = self.make_branch_and_tree('a')
698
self.build_tree(['a/a'])
701
b_tree = a_tree.bzrdir.sprout('b').open_workingtree()
702
self.build_tree(['b/b'])
706
self.run_bzr('merge ../b', working_dir='a')
707
out, err = self.run_bzr('status --no-pending', working_dir='a')
708
self.assertEquals(out, "added:\n b\n")
710
def test_pending_specific_files(self):
711
"""With a specific file list, pending merges are not shown."""
712
tree = self.make_branch_and_tree('tree')
713
self.build_tree_contents([('tree/a', 'content of a\n')])
715
r1_id = tree.commit('one')
716
alt = tree.bzrdir.sprout('alt').open_workingtree()
717
self.build_tree_contents([('alt/a', 'content of a\nfrom alt\n')])
718
alt_id = alt.commit('alt')
719
tree.merge_from_branch(alt.branch)
720
output = self.make_utf8_encoded_stringio()
721
show_tree_status(tree, to_file=output)
722
self.assertContainsRe(output.getvalue(), 'pending merge')
723
out, err = self.run_bzr('status tree/a')
724
self.assertNotContainsRe(out, 'pending merge')
727
class TestStatusEncodings(TestCaseWithTransport):
729
def make_uncommitted_tree(self):
730
"""Build a branch with uncommitted unicode named changes in the cwd."""
731
working_tree = self.make_branch_and_tree(u'.')
732
filename = u'hell\u00d8'
734
self.build_tree_contents([(filename, 'contents of hello')])
735
except UnicodeEncodeError:
736
raise TestSkipped("can't build unicode working tree in "
737
"filesystem encoding %s" % sys.getfilesystemencoding())
738
working_tree.add(filename)
741
def test_stdout_ascii(self):
742
self.overrideAttr(osutils, '_cached_user_encoding', 'ascii')
743
working_tree = self.make_uncommitted_tree()
744
stdout, stderr = self.run_bzr("status")
746
self.assertEquals(stdout, """\
751
def test_stdout_latin1(self):
752
self.overrideAttr(osutils, '_cached_user_encoding', 'latin-1')
753
working_tree = self.make_uncommitted_tree()
754
stdout, stderr = self.run_bzr('status')
756
self.assertEquals(stdout, u"""\
759
""".encode('latin-1'))