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.
30
from os import mkdir, chdir, rmdir, unlink
38
from breezy.bzr import (
43
from ...osutils import pathjoin
44
from ...revisionspec import RevisionSpec
45
from ...status import show_tree_status
46
from .. import TestCaseWithTransport, TestSkipped
47
from ...workingtree import WorkingTree
50
class BranchStatus(TestCaseWithTransport):
53
super(BranchStatus, self).setUp()
54
# As TestCase.setUp clears all hooks, we install this default
55
# post_status hook handler for the test.
56
status.hooks.install_named_hook('post_status',
57
status._show_shelve_summary,
60
def assertStatus(self, expected_lines, working_tree, specific_files=None,
61
revision=None, short=False, pending=True, verbose=False):
62
"""Run status in working_tree and look for output.
64
:param expected_lines: The lines to look for.
65
:param working_tree: The tree to run status in.
67
output_string = self.status_string(working_tree, specific_files, revision, short,
69
self.assertEqual(expected_lines, output_string.splitlines(True))
71
def status_string(self, wt, specific_files=None, revision=None,
72
short=False, pending=True, verbose=False):
73
uio = self.make_utf8_encoded_stringio()
74
show_tree_status(wt, specific_files=specific_files, to_file=uio,
75
revision=revision, short=short, show_pending=pending,
77
return uio.getvalue().decode('utf-8')
79
def test_branch_status(self):
80
"""Test basic branch status"""
81
wt = self.make_branch_and_tree('.')
83
# status with no commits or files - it must
84
# work and show no output. We do this with no
85
# commits to be sure that it's not going to fail
87
self.assertStatus([], wt)
89
self.build_tree(['hello.c', 'bye.c'])
102
# add a commit to allow showing pending merges.
103
wt.commit('create a parent to allow testing merge output')
105
wt.add_parent_tree_id(b'pending@pending-0-0')
110
'pending merge tips: (use -v to see all merge revisions)\n',
111
' (ghost) pending@pending-0-0\n',
119
' (ghost) pending@pending-0-0\n',
125
'P (ghost) pending@pending-0-0\n',
138
wt, short=True, pending=False)
140
def test_branch_status_revisions(self):
141
"""Tests branch status with revisions"""
142
wt = self.make_branch_and_tree('.')
144
self.build_tree(['hello.c', 'bye.c'])
147
wt.commit('Test message')
149
revs = [RevisionSpec.from_string('0')]
158
self.build_tree(['more.c'])
160
wt.commit('Another test message')
162
revs.append(RevisionSpec.from_string('1'))
171
def test_pending(self):
172
"""Pending merges display works, including Unicode"""
174
wt = self.make_branch_and_tree('branch')
176
wt.commit("Empty commit 1")
177
b_2_dir = b.controldir.sprout('./copy')
178
b_2 = b_2_dir.open_branch()
179
wt2 = b_2_dir.open_workingtree()
180
wt.commit(u"\N{TIBETAN DIGIT TWO} Empty commit 2")
181
wt2.merge_from_branch(wt.branch)
182
message = self.status_string(wt2, verbose=True)
183
self.assertStartsWith(message, "pending merges:\n")
184
self.assertEndsWith(message, "Empty commit 2\n")
186
# must be long to make sure we see elipsis at the end
187
wt.commit("Empty commit 3 "
188
+ "blah blah blah blah " * 100)
189
wt2.merge_from_branch(wt.branch)
190
message = self.status_string(wt2, verbose=True)
191
self.assertStartsWith(message, "pending merges:\n")
192
self.assertTrue("Empty commit 3" in message)
193
self.assertEndsWith(message, "...\n")
195
def test_tree_status_ignores(self):
196
"""Tests branch status with ignores"""
197
wt = self.make_branch_and_tree('.')
198
self.run_bzr('ignore *~')
199
wt.commit('commit .bzrignore')
200
self.build_tree(['foo.c', 'foo.c~'])
211
def test_tree_status_specific_files(self):
212
"""Tests branch status with given specific files"""
213
wt = self.make_branch_and_tree('.')
216
self.build_tree(['directory/', 'directory/hello.c',
217
'bye.c', 'test.c', 'dir2/',
231
' directory/hello.c\n'
238
'? directory/hello.c\n',
244
self.assertRaises(errors.PathsDoNotExist,
246
wt, specific_files=['bye.c', 'test.c', 'absent.c'],
250
show_tree_status(wt, specific_files=['directory'], to_file=tof)
252
self.assertEqual(tof.readlines(),
254
' directory/hello.c\n'
257
show_tree_status(wt, specific_files=['directory'], to_file=tof,
260
self.assertEqual(tof.readlines(), ['? directory/hello.c\n'])
263
show_tree_status(wt, specific_files=['dir2'], to_file=tof)
265
self.assertEqual(tof.readlines(),
270
show_tree_status(wt, specific_files=['dir2'], to_file=tof, short=True)
272
self.assertEqual(tof.readlines(), ['? dir2/\n'])
275
revs = [RevisionSpec.from_string('0'), RevisionSpec.from_string('1')]
276
show_tree_status(wt, specific_files=['test.c'], to_file=tof,
277
short=True, revision=revs)
279
self.assertEqual(tof.readlines(), ['+N test.c\n'])
282
show_tree_status(wt, specific_files=['missing.c'], to_file=tof)
284
self.assertEqual(tof.readlines(),
289
show_tree_status(wt, specific_files=['missing.c'], to_file=tof,
292
self.assertEqual(tof.readlines(),
295
def test_specific_files_conflicts(self):
296
tree = self.make_branch_and_tree('.')
297
self.build_tree(['dir2/'])
299
tree.commit('added dir2')
300
tree.set_conflicts([conflicts.ContentsConflict('foo')])
302
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
303
self.assertEqualDiff(b'', tof.getvalue())
304
tree.set_conflicts([conflicts.ContentsConflict('dir2')])
306
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
307
self.assertEqualDiff('conflicts:\n Contents conflict in dir2\n',
310
tree.set_conflicts([conflicts.ContentsConflict('dir2/file1')])
312
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
313
self.assertEqualDiff('conflicts:\n Contents conflict in dir2/file1\n',
316
def _prepare_nonexistent(self):
317
wt = self.make_branch_and_tree('.')
318
self.assertStatus([], wt)
319
self.build_tree(['FILE_A', 'FILE_B', 'FILE_C', 'FILE_D', 'FILE_E', ])
325
wt.commit('Create five empty files.')
326
with open('FILE_B', 'w') as f:
327
f.write('Modification to file FILE_B.')
328
with open('FILE_C', 'w') as f:
329
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:
332
f.write('FILE_Q is added but not committed.')
333
wt.add('FILE_Q') # FILE_Q will be added but not committed
334
open('UNVERSIONED_BUT_EXISTING', 'w')
337
def test_status_nonexistent_file(self):
338
# files that don't exist in either the basis tree or working tree
339
# should give an error
340
wt = self._prepare_nonexistent()
350
' UNVERSIONED_BUT_EXISTING\n',
358
'? UNVERSIONED_BUT_EXISTING\n',
362
# Okay, everything's looking good with the existent files.
363
# Let's see what happens when we throw in non-existent files.
365
# brz st [--short] NONEXISTENT '
370
out, err = self.run_bzr('status NONEXISTENT', retcode=3)
371
self.assertEqual(expected, out.splitlines(True))
372
self.assertContainsRe(err,
373
r'.*ERROR: Path\(s\) do not exist: '
378
out, err = self.run_bzr('status --short NONEXISTENT', retcode=3)
379
self.assertContainsRe(err,
380
r'.*ERROR: Path\(s\) do not exist: '
383
def test_status_nonexistent_file_with_others(self):
384
# brz st [--short] NONEXISTENT ...others..
385
wt = self._prepare_nonexistent()
395
out, err = self.run_bzr('status NONEXISTENT '
396
'FILE_A FILE_B FILE_C FILE_D FILE_E',
398
self.assertEqual(expected, out.splitlines(True))
399
self.assertContainsRe(err,
400
r'.*ERROR: Path\(s\) do not exist: '
408
out, err = self.run_bzr('status --short NONEXISTENT '
409
'FILE_A FILE_B FILE_C FILE_D FILE_E',
411
self.assertEqual(expected, out.splitlines(True))
412
self.assertContainsRe(err,
413
r'.*ERROR: Path\(s\) do not exist: '
416
def test_status_multiple_nonexistent_files(self):
417
# brz st [--short] NONEXISTENT ... ANOTHER_NONEXISTENT ...
418
wt = self._prepare_nonexistent()
426
' ANOTHER_NONEXISTENT\n',
429
out, err = self.run_bzr('status NONEXISTENT '
430
'FILE_A FILE_B ANOTHER_NONEXISTENT '
431
'FILE_C FILE_D FILE_E', retcode=3)
432
self.assertEqual(expected, out.splitlines(True))
433
self.assertContainsRe(err,
434
r'.*ERROR: Path\(s\) do not exist: '
435
'ANOTHER_NONEXISTENT NONEXISTENT.*')
440
'X ANOTHER_NONEXISTENT\n',
443
out, err = self.run_bzr('status --short NONEXISTENT '
444
'FILE_A FILE_B ANOTHER_NONEXISTENT '
445
'FILE_C FILE_D FILE_E', retcode=3)
446
self.assertEqual(expected, out.splitlines(True))
447
self.assertContainsRe(err,
448
r'.*ERROR: Path\(s\) do not exist: '
449
'ANOTHER_NONEXISTENT NONEXISTENT.*')
451
def test_status_nonexistent_file_with_unversioned(self):
452
# brz st [--short] NONEXISTENT A B UNVERSIONED_BUT_EXISTING C D E Q
453
wt = self._prepare_nonexistent()
463
' UNVERSIONED_BUT_EXISTING\n',
467
out, err = self.run_bzr('status NONEXISTENT '
468
'FILE_A FILE_B UNVERSIONED_BUT_EXISTING '
469
'FILE_C FILE_D FILE_E FILE_Q', retcode=3)
470
self.assertEqual(expected, out.splitlines(True))
471
self.assertContainsRe(err,
472
r'.*ERROR: Path\(s\) do not exist: '
476
'? UNVERSIONED_BUT_EXISTING\n',
482
out, err = self.run_bzr('status --short NONEXISTENT '
483
'FILE_A FILE_B UNVERSIONED_BUT_EXISTING '
484
'FILE_C FILE_D FILE_E FILE_Q', retcode=3)
485
actual = out.splitlines(True)
487
self.assertEqual(expected, actual)
488
self.assertContainsRe(err,
489
r'.*ERROR: Path\(s\) do not exist: '
492
def test_status_out_of_date(self):
493
"""Simulate status of out-of-date tree after remote push"""
494
tree = self.make_branch_and_tree('.')
495
self.build_tree_contents([('a', b'foo\n')])
496
with tree.lock_write():
498
tree.commit('add test file')
499
# simulate what happens after a remote push
500
tree.set_last_revision(b"0")
501
out, err = self.run_bzr('status')
502
self.assertEqual("working tree is out of date, run 'brz update'\n",
505
def test_status_on_ignored(self):
506
"""Tests branch status on an unversioned file which is considered ignored.
508
See https://bugs.launchpad.net/bzr/+bug/40103
510
tree = self.make_branch_and_tree('.')
512
self.build_tree(['test1.c', 'test1.c~', 'test2.c~'])
513
result = self.run_bzr('status')[0]
514
self.assertContainsRe(result, "unknown:\n test1.c\n")
515
short_result = self.run_bzr('status --short')[0]
516
self.assertContainsRe(short_result, "\\? test1.c\n")
518
result = self.run_bzr('status test1.c')[0]
519
self.assertContainsRe(result, "unknown:\n test1.c\n")
520
short_result = self.run_bzr('status --short test1.c')[0]
521
self.assertContainsRe(short_result, "\\? test1.c\n")
523
result = self.run_bzr('status test1.c~')[0]
524
self.assertContainsRe(result, "ignored:\n test1.c~\n")
525
short_result = self.run_bzr('status --short test1.c~')[0]
526
self.assertContainsRe(short_result, "I test1.c~\n")
528
result = self.run_bzr('status test1.c~ test2.c~')[0]
529
self.assertContainsRe(result, "ignored:\n test1.c~\n test2.c~\n")
530
short_result = self.run_bzr('status --short test1.c~ test2.c~')[0]
531
self.assertContainsRe(short_result, "I test1.c~\nI test2.c~\n")
533
result = self.run_bzr('status test1.c test1.c~ test2.c~')[0]
534
self.assertContainsRe(
535
result, "unknown:\n test1.c\nignored:\n test1.c~\n test2.c~\n")
536
short_result = self.run_bzr(
537
'status --short test1.c test1.c~ test2.c~')[0]
538
self.assertContainsRe(
539
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(
713
'kind changed:\n file \\(file => directory\\)')
714
tree.rename_one('file', 'directory')
715
self.assertStatusContains('renamed:\n file => directory/\n'
716
'modified:\n directory/\n')
718
self.assertStatusContains('removed:\n file\n')
720
def test_kind_change_short(self):
721
tree = self.make_branch_and_tree('.')
722
self.build_tree(['file'])
724
tree.commit('added file')
726
self.build_tree(['file/'])
727
self.assertStatusContains('K file => file/',
729
tree.rename_one('file', 'directory')
730
self.assertStatusContains('RK file => directory/',
733
self.assertStatusContains('RD file => directory',
736
def test_status_illegal_revision_specifiers(self):
737
out, err = self.run_bzr('status -r 1..23..123', retcode=3)
738
self.assertContainsRe(err, 'one or two revision specifiers')
740
def test_status_no_pending(self):
741
a_tree = self.make_branch_and_tree('a')
742
self.build_tree(['a/a'])
745
b_tree = a_tree.controldir.sprout('b').open_workingtree()
746
self.build_tree(['b/b'])
750
self.run_bzr('merge ../b', working_dir='a')
751
out, err = self.run_bzr('status --no-pending', working_dir='a')
752
self.assertEqual(out, "added:\n b\n")
754
def test_pending_specific_files(self):
755
"""With a specific file list, pending merges are not shown."""
756
tree = self.make_branch_and_tree('tree')
757
self.build_tree_contents([('tree/a', b'content of a\n')])
759
r1_id = tree.commit('one')
760
alt = tree.controldir.sprout('alt').open_workingtree()
761
self.build_tree_contents([('alt/a', b'content of a\nfrom alt\n')])
762
alt_id = alt.commit('alt')
763
tree.merge_from_branch(alt.branch)
764
output = self.make_utf8_encoded_stringio()
765
show_tree_status(tree, to_file=output)
766
self.assertContainsRe(output.getvalue(), b'pending merge')
767
out, err = self.run_bzr('status tree/a')
768
self.assertNotContainsRe(out, 'pending merge')
771
class TestStatusEncodings(TestCaseWithTransport):
773
def make_uncommitted_tree(self):
774
"""Build a branch with uncommitted unicode named changes in the cwd."""
775
working_tree = self.make_branch_and_tree(u'.')
776
filename = u'hell\u00d8'
778
self.build_tree_contents([(filename, b'contents of hello')])
779
except UnicodeEncodeError:
780
raise TestSkipped("can't build unicode working tree in "
781
"filesystem encoding %s" % sys.getfilesystemencoding())
782
working_tree.add(filename)
785
def test_stdout_ascii(self):
786
self.overrideAttr(osutils, '_cached_user_encoding', 'ascii')
787
working_tree = self.make_uncommitted_tree()
788
stdout, stderr = self.run_bzr("status")
790
self.assertEqual(stdout, """\
795
def test_stdout_latin1(self):
796
self.overrideAttr(osutils, '_cached_user_encoding', 'latin-1')
797
working_tree = self.make_uncommitted_tree()
798
stdout, stderr = self.run_bzr('status')
804
self.assertEqual(stdout, expected)