1
# Copyright (C) 2005-2012, 2016 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.
26
from os import mkdir, chdir, rmdir, unlink
35
from breezy.bzr import (
39
from ...osutils import pathjoin
40
from ...revisionspec import RevisionSpec
41
from ...sixish import (
44
from ...status import show_tree_status
45
from .. import TestCaseWithTransport, TestSkipped
46
from ...workingtree import WorkingTree
49
class BranchStatus(TestCaseWithTransport):
52
super(BranchStatus, self).setUp()
53
# As TestCase.setUp clears all hooks, we install this default
54
# post_status hook handler for the test.
55
status.hooks.install_named_hook('post_status',
56
status._show_shelve_summary,
59
def assertStatus(self, expected_lines, working_tree, specific_files=None,
60
revision=None, short=False, pending=True, verbose=False):
61
"""Run status in working_tree and look for output.
63
:param expected_lines: The lines to look for.
64
:param working_tree: The tree to run status in.
66
output_string = self.status_string(working_tree, specific_files, revision, short,
68
self.assertEqual(expected_lines, output_string.splitlines(True))
70
def status_string(self, wt, specific_files=None, revision=None,
71
short=False, pending=True, verbose=False):
72
uio = self.make_utf8_encoded_stringio()
73
show_tree_status(wt, specific_files=specific_files, to_file=uio,
74
revision=revision, short=short, show_pending=pending,
76
return uio.getvalue().decode('utf-8')
78
def test_branch_status(self):
79
"""Test basic branch status"""
80
wt = self.make_branch_and_tree('.')
82
# status with no commits or files - it must
83
# work and show no output. We do this with no
84
# commits to be sure that it's not going to fail
86
self.assertStatus([], wt)
88
self.build_tree(['hello.c', 'bye.c'])
101
# add a commit to allow showing pending merges.
102
wt.commit('create a parent to allow testing merge output')
104
wt.add_parent_tree_id('pending@pending-0-0')
109
'pending merge tips: (use -v to see all merge revisions)\n',
110
' (ghost) pending@pending-0-0\n',
118
' (ghost) pending@pending-0-0\n',
124
'P (ghost) pending@pending-0-0\n',
137
wt, short=True, pending=False)
139
def test_branch_status_revisions(self):
140
"""Tests branch status with revisions"""
141
wt = self.make_branch_and_tree('.')
143
self.build_tree(['hello.c', 'bye.c'])
146
wt.commit('Test message')
148
revs = [RevisionSpec.from_string('0')]
157
self.build_tree(['more.c'])
159
wt.commit('Another test message')
161
revs.append(RevisionSpec.from_string('1'))
170
def test_pending(self):
171
"""Pending merges display works, including Unicode"""
173
wt = self.make_branch_and_tree('branch')
175
wt.commit("Empty commit 1")
176
b_2_dir = b.controldir.sprout('./copy')
177
b_2 = b_2_dir.open_branch()
178
wt2 = b_2_dir.open_workingtree()
179
wt.commit(u"\N{TIBETAN DIGIT TWO} Empty commit 2")
180
wt2.merge_from_branch(wt.branch)
181
message = self.status_string(wt2, verbose=True)
182
self.assertStartsWith(message, "pending merges:\n")
183
self.assertEndsWith(message, "Empty commit 2\n")
185
# must be long to make sure we see elipsis at the end
186
wt.commit("Empty commit 3 " +
187
"blah blah blah blah " * 100)
188
wt2.merge_from_branch(wt.branch)
189
message = self.status_string(wt2, verbose=True)
190
self.assertStartsWith(message, "pending merges:\n")
191
self.assertTrue("Empty commit 3" in message)
192
self.assertEndsWith(message, "...\n")
194
def test_tree_status_ignores(self):
195
"""Tests branch status with ignores"""
196
wt = self.make_branch_and_tree('.')
197
self.run_bzr('ignore *~')
198
wt.commit('commit .bzrignore')
199
self.build_tree(['foo.c', 'foo.c~'])
210
def test_tree_status_specific_files(self):
211
"""Tests branch status with given specific files"""
212
wt = self.make_branch_and_tree('.')
215
self.build_tree(['directory/','directory/hello.c',
216
'bye.c','test.c','dir2/',
230
' directory/hello.c\n'
238
'? directory/hello.c\n'
243
self.assertRaises(errors.PathsDoNotExist,
245
wt, specific_files=['bye.c','test.c','absent.c'],
249
show_tree_status(wt, specific_files=['directory'], to_file=tof)
251
self.assertEqual(tof.readlines(),
253
' directory/hello.c\n'
256
show_tree_status(wt, specific_files=['directory'], to_file=tof,
259
self.assertEqual(tof.readlines(), ['? directory/hello.c\n'])
262
show_tree_status(wt, specific_files=['dir2'], to_file=tof)
264
self.assertEqual(tof.readlines(),
269
show_tree_status(wt, specific_files=['dir2'], to_file=tof, short=True)
271
self.assertEqual(tof.readlines(), ['? dir2/\n'])
274
revs = [RevisionSpec.from_string('0'), RevisionSpec.from_string('1')]
275
show_tree_status(wt, specific_files=['test.c'], to_file=tof,
276
short=True, revision=revs)
278
self.assertEqual(tof.readlines(), ['+N test.c\n'])
281
show_tree_status(wt, specific_files=['missing.c'], to_file=tof)
283
self.assertEqual(tof.readlines(),
288
show_tree_status(wt, specific_files=['missing.c'], to_file=tof,
291
self.assertEqual(tof.readlines(),
294
def test_specific_files_conflicts(self):
295
tree = self.make_branch_and_tree('.')
296
self.build_tree(['dir2/'])
298
tree.commit('added dir2')
299
tree.set_conflicts(conflicts.ConflictList(
300
[conflicts.ContentsConflict('foo')]))
302
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
303
self.assertEqualDiff('', tof.getvalue())
304
tree.set_conflicts(conflicts.ConflictList(
305
[conflicts.ContentsConflict('dir2')]))
307
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
308
self.assertEqualDiff('conflicts:\n Contents conflict in dir2\n',
311
tree.set_conflicts(conflicts.ConflictList(
312
[conflicts.ContentsConflict('dir2/file1')]))
314
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
315
self.assertEqualDiff('conflicts:\n Contents conflict in dir2/file1\n',
318
def _prepare_nonexistent(self):
319
wt = self.make_branch_and_tree('.')
320
self.assertStatus([], wt)
321
self.build_tree(['FILE_A', 'FILE_B', 'FILE_C', 'FILE_D', 'FILE_E', ])
327
wt.commit('Create five empty files.')
328
with open('FILE_B', 'w') as f: f.write('Modification to file FILE_B.')
329
with open('FILE_C', 'w') as f: f.write('Modification to file FILE_C.')
330
unlink('FILE_E') # FILE_E will be versioned but missing
331
with open('FILE_Q', 'w') as f: f.write('FILE_Q is added but not committed.')
332
wt.add('FILE_Q') # FILE_Q will be added but not committed
333
open('UNVERSIONED_BUT_EXISTING', 'w')
336
def test_status_nonexistent_file(self):
337
# files that don't exist in either the basis tree or working tree
338
# should give an error
339
wt = self._prepare_nonexistent()
349
' UNVERSIONED_BUT_EXISTING\n',
357
'? UNVERSIONED_BUT_EXISTING\n',
361
# Okay, everything's looking good with the existent files.
362
# Let's see what happens when we throw in non-existent files.
364
# brz st [--short] NONEXISTENT '
369
out, err = self.run_bzr('status NONEXISTENT', retcode=3)
370
self.assertEqual(expected, out.splitlines(True))
371
self.assertContainsRe(err,
372
r'.*ERROR: Path\(s\) do not exist: '
377
out, err = self.run_bzr('status --short NONEXISTENT', retcode=3)
378
self.assertContainsRe(err,
379
r'.*ERROR: Path\(s\) do not exist: '
382
def test_status_nonexistent_file_with_others(self):
383
# brz st [--short] NONEXISTENT ...others..
384
wt = self._prepare_nonexistent()
394
out, err = self.run_bzr('status NONEXISTENT '
395
'FILE_A FILE_B FILE_C FILE_D FILE_E',
397
self.assertEqual(expected, out.splitlines(True))
398
self.assertContainsRe(err,
399
r'.*ERROR: Path\(s\) do not exist: '
407
out, err = self.run_bzr('status --short NONEXISTENT '
408
'FILE_A FILE_B FILE_C FILE_D FILE_E',
410
self.assertEqual(expected, out.splitlines(True))
411
self.assertContainsRe(err,
412
r'.*ERROR: Path\(s\) do not exist: '
415
def test_status_multiple_nonexistent_files(self):
416
# brz st [--short] NONEXISTENT ... ANOTHER_NONEXISTENT ...
417
wt = self._prepare_nonexistent()
425
' ANOTHER_NONEXISTENT\n',
428
out, err = self.run_bzr('status NONEXISTENT '
429
'FILE_A FILE_B ANOTHER_NONEXISTENT '
430
'FILE_C FILE_D FILE_E', retcode=3)
431
self.assertEqual(expected, out.splitlines(True))
432
self.assertContainsRe(err,
433
r'.*ERROR: Path\(s\) do not exist: '
434
'ANOTHER_NONEXISTENT NONEXISTENT.*')
439
'X ANOTHER_NONEXISTENT\n',
442
out, err = self.run_bzr('status --short NONEXISTENT '
443
'FILE_A FILE_B ANOTHER_NONEXISTENT '
444
'FILE_C FILE_D FILE_E', retcode=3)
445
self.assertEqual(expected, out.splitlines(True))
446
self.assertContainsRe(err,
447
r'.*ERROR: Path\(s\) do not exist: '
448
'ANOTHER_NONEXISTENT NONEXISTENT.*')
450
def test_status_nonexistent_file_with_unversioned(self):
451
# brz st [--short] NONEXISTENT A B UNVERSIONED_BUT_EXISTING C D E Q
452
wt = self._prepare_nonexistent()
462
' UNVERSIONED_BUT_EXISTING\n',
466
out, err = self.run_bzr('status NONEXISTENT '
467
'FILE_A FILE_B UNVERSIONED_BUT_EXISTING '
468
'FILE_C FILE_D FILE_E FILE_Q', retcode=3)
469
self.assertEqual(expected, out.splitlines(True))
470
self.assertContainsRe(err,
471
r'.*ERROR: Path\(s\) do not exist: '
475
'? UNVERSIONED_BUT_EXISTING\n',
481
out, err = self.run_bzr('status --short NONEXISTENT '
482
'FILE_A FILE_B UNVERSIONED_BUT_EXISTING '
483
'FILE_C FILE_D FILE_E FILE_Q', retcode=3)
484
actual = out.splitlines(True)
486
self.assertEqual(expected, actual)
487
self.assertContainsRe(err,
488
r'.*ERROR: Path\(s\) do not exist: '
491
def test_status_out_of_date(self):
492
"""Simulate status of out-of-date tree after remote push"""
493
tree = self.make_branch_and_tree('.')
494
self.build_tree_contents([('a', 'foo\n')])
498
tree.commit('add test file')
499
# simulate what happens after a remote push
500
tree.set_last_revision("0")
502
# before run another commands we should unlock tree
504
out, err = self.run_bzr('status')
505
self.assertEqual("working tree is out of date, run 'brz update'\n",
508
def test_status_on_ignored(self):
509
"""Tests branch status on an unversioned file which is considered ignored.
511
See https://bugs.launchpad.net/bzr/+bug/40103
513
tree = self.make_branch_and_tree('.')
515
self.build_tree(['test1.c', 'test1.c~', 'test2.c~'])
516
result = self.run_bzr('status')[0]
517
self.assertContainsRe(result, "unknown:\n test1.c\n")
518
short_result = self.run_bzr('status --short')[0]
519
self.assertContainsRe(short_result, "\? test1.c\n")
521
result = self.run_bzr('status test1.c')[0]
522
self.assertContainsRe(result, "unknown:\n test1.c\n")
523
short_result = self.run_bzr('status --short test1.c')[0]
524
self.assertContainsRe(short_result, "\? test1.c\n")
526
result = self.run_bzr('status test1.c~')[0]
527
self.assertContainsRe(result, "ignored:\n test1.c~\n")
528
short_result = self.run_bzr('status --short test1.c~')[0]
529
self.assertContainsRe(short_result, "I test1.c~\n")
531
result = self.run_bzr('status test1.c~ test2.c~')[0]
532
self.assertContainsRe(result, "ignored:\n test1.c~\n test2.c~\n")
533
short_result = self.run_bzr('status --short test1.c~ test2.c~')[0]
534
self.assertContainsRe(short_result, "I test1.c~\nI test2.c~\n")
536
result = self.run_bzr('status test1.c test1.c~ test2.c~')[0]
537
self.assertContainsRe(result, "unknown:\n test1.c\nignored:\n test1.c~\n test2.c~\n")
538
short_result = self.run_bzr('status --short test1.c test1.c~ test2.c~')[0]
539
self.assertContainsRe(short_result, "\? test1.c\nI test1.c~\nI test2.c~\n")
541
def test_status_write_lock(self):
542
"""Test that status works without fetching history and
545
See https://bugs.launchpad.net/bzr/+bug/149270
548
wt = self.make_branch_and_tree('branch1')
550
wt.commit('Empty commit 1')
551
wt2 = b.controldir.sprout('branch2').open_workingtree()
552
wt2.commit('Empty commit 2')
553
out, err = self.run_bzr('status branch1 -rbranch:branch2')
554
self.assertEqual('', out)
556
def test_status_with_shelves(self):
557
"""Ensure that _show_shelve_summary handler works.
559
wt = self.make_branch_and_tree('.')
560
self.build_tree(['hello.c'])
562
self.run_bzr(['shelve', '--all', '-m', 'foo'])
563
self.build_tree(['bye.c'])
568
'1 shelf exists. See "brz shelve --list" for details.\n',
571
self.run_bzr(['shelve', '--all', '-m', 'bar'])
572
self.build_tree(['eggs.c', 'spam.c'])
579
'2 shelves exist. See "brz shelve --list" for details.\n',
587
specific_files=['spam.c'])
590
class CheckoutStatus(BranchStatus):
593
super(CheckoutStatus, self).setUp()
597
def make_branch_and_tree(self, relpath):
598
source = self.make_branch(pathjoin('..', relpath))
599
checkout = bzrdir.BzrDirMetaFormat1().initialize(relpath)
600
checkout.set_branch_reference(source)
601
return checkout.create_workingtree()
604
class TestStatus(TestCaseWithTransport):
606
def test_status_plain(self):
607
tree = self.make_branch_and_tree('.')
609
self.build_tree(['hello.txt'])
610
result = self.run_bzr("status")[0]
611
self.assertContainsRe(result, "unknown:\n hello.txt\n")
613
tree.add("hello.txt")
614
result = self.run_bzr("status")[0]
615
self.assertContainsRe(result, "added:\n hello.txt\n")
617
tree.commit(message="added")
618
result = self.run_bzr("status -r 0..1")[0]
619
self.assertContainsRe(result, "added:\n hello.txt\n")
621
result = self.run_bzr("status -c 1")[0]
622
self.assertContainsRe(result, "added:\n hello.txt\n")
624
self.build_tree(['world.txt'])
625
result = self.run_bzr("status -r 0")[0]
626
self.assertContainsRe(result, "added:\n hello.txt\n" \
627
"unknown:\n world.txt\n")
628
result2 = self.run_bzr("status -r 0..")[0]
629
self.assertEqual(result2, result)
631
def test_status_short(self):
632
tree = self.make_branch_and_tree('.')
634
self.build_tree(['hello.txt'])
635
result = self.run_bzr("status --short")[0]
636
self.assertContainsRe(result, "[?] hello.txt\n")
638
tree.add("hello.txt")
639
result = self.run_bzr("status --short")[0]
640
self.assertContainsRe(result, "[+]N hello.txt\n")
642
tree.commit(message="added")
643
result = self.run_bzr("status --short -r 0..1")[0]
644
self.assertContainsRe(result, "[+]N hello.txt\n")
646
self.build_tree(['world.txt'])
647
result = self.run_bzr("status -S -r 0")[0]
648
self.assertContainsRe(result, "[+]N hello.txt\n" \
650
result2 = self.run_bzr("status -S -r 0..")[0]
651
self.assertEqual(result2, result)
653
def test_status_versioned(self):
654
tree = self.make_branch_and_tree('.')
656
self.build_tree(['hello.txt'])
657
result = self.run_bzr("status --versioned")[0]
658
self.assertNotContainsRe(result, "unknown:\n hello.txt\n")
660
tree.add("hello.txt")
661
result = self.run_bzr("status --versioned")[0]
662
self.assertContainsRe(result, "added:\n hello.txt\n")
665
result = self.run_bzr("status --versioned -r 0..1")[0]
666
self.assertContainsRe(result, "added:\n hello.txt\n")
668
self.build_tree(['world.txt'])
669
result = self.run_bzr("status --versioned -r 0")[0]
670
self.assertContainsRe(result, "added:\n hello.txt\n")
671
self.assertNotContainsRe(result, "unknown:\n world.txt\n")
672
result2 = self.run_bzr("status --versioned -r 0..")[0]
673
self.assertEqual(result2, result)
675
def test_status_SV(self):
676
tree = self.make_branch_and_tree('.')
678
self.build_tree(['hello.txt'])
679
result = self.run_bzr("status -SV")[0]
680
self.assertNotContainsRe(result, "hello.txt")
682
tree.add("hello.txt")
683
result = self.run_bzr("status -SV")[0]
684
self.assertContainsRe(result, "[+]N hello.txt\n")
686
tree.commit(message="added")
687
result = self.run_bzr("status -SV -r 0..1")[0]
688
self.assertContainsRe(result, "[+]N hello.txt\n")
690
self.build_tree(['world.txt'])
691
result = self.run_bzr("status -SV -r 0")[0]
692
self.assertContainsRe(result, "[+]N hello.txt\n")
694
result2 = self.run_bzr("status -SV -r 0..")[0]
695
self.assertEqual(result2, result)
697
def assertStatusContains(self, pattern, short=False):
698
"""Run status, and assert it contains the given pattern"""
700
result = self.run_bzr("status --short")[0]
702
result = self.run_bzr("status")[0]
703
self.assertContainsRe(result, pattern)
705
def test_kind_change_plain(self):
706
tree = self.make_branch_and_tree('.')
707
self.build_tree(['file'])
709
tree.commit('added file')
711
self.build_tree(['file/'])
712
self.assertStatusContains('kind changed:\n file \(file => directory\)')
713
tree.rename_one('file', 'directory')
714
self.assertStatusContains('renamed:\n file/ => directory/\n' \
715
'modified:\n directory/\n')
717
self.assertStatusContains('removed:\n file\n')
719
def test_kind_change_short(self):
720
tree = self.make_branch_and_tree('.')
721
self.build_tree(['file'])
723
tree.commit('added file')
725
self.build_tree(['file/'])
726
self.assertStatusContains('K file => file/',
728
tree.rename_one('file', 'directory')
729
self.assertStatusContains('RK file => directory/',
732
self.assertStatusContains('RD file => directory',
735
def test_status_illegal_revision_specifiers(self):
736
out, err = self.run_bzr('status -r 1..23..123', retcode=3)
737
self.assertContainsRe(err, 'one or two revision specifiers')
739
def test_status_no_pending(self):
740
a_tree = self.make_branch_and_tree('a')
741
self.build_tree(['a/a'])
744
b_tree = a_tree.controldir.sprout('b').open_workingtree()
745
self.build_tree(['b/b'])
749
self.run_bzr('merge ../b', working_dir='a')
750
out, err = self.run_bzr('status --no-pending', working_dir='a')
751
self.assertEqual(out, "added:\n b\n")
753
def test_pending_specific_files(self):
754
"""With a specific file list, pending merges are not shown."""
755
tree = self.make_branch_and_tree('tree')
756
self.build_tree_contents([('tree/a', 'content of a\n')])
758
r1_id = tree.commit('one')
759
alt = tree.controldir.sprout('alt').open_workingtree()
760
self.build_tree_contents([('alt/a', 'content of a\nfrom alt\n')])
761
alt_id = alt.commit('alt')
762
tree.merge_from_branch(alt.branch)
763
output = self.make_utf8_encoded_stringio()
764
show_tree_status(tree, to_file=output)
765
self.assertContainsRe(output.getvalue(), 'pending merge')
766
out, err = self.run_bzr('status tree/a')
767
self.assertNotContainsRe(out, 'pending merge')
770
class TestStatusEncodings(TestCaseWithTransport):
772
def make_uncommitted_tree(self):
773
"""Build a branch with uncommitted unicode named changes in the cwd."""
774
working_tree = self.make_branch_and_tree(u'.')
775
filename = u'hell\u00d8'
777
self.build_tree_contents([(filename, 'contents of hello')])
778
except UnicodeEncodeError:
779
raise TestSkipped("can't build unicode working tree in "
780
"filesystem encoding %s" % sys.getfilesystemencoding())
781
working_tree.add(filename)
784
def test_stdout_ascii(self):
785
self.overrideAttr(osutils, '_cached_user_encoding', 'ascii')
786
working_tree = self.make_uncommitted_tree()
787
stdout, stderr = self.run_bzr("status")
789
self.assertEqual(stdout, """\
794
def test_stdout_latin1(self):
795
self.overrideAttr(osutils, '_cached_user_encoding', 'latin-1')
796
working_tree = self.make_uncommitted_tree()
797
stdout, stderr = self.run_bzr('status')
799
self.assertEqual(stdout, u"""\
802
""".encode('latin-1'))