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 (
46
from ...status import show_tree_status
47
from .. import TestCaseWithTransport, TestSkipped
48
from ...workingtree import WorkingTree
51
class BranchStatus(TestCaseWithTransport):
54
super(BranchStatus, self).setUp()
55
# As TestCase.setUp clears all hooks, we install this default
56
# post_status hook handler for the test.
57
status.hooks.install_named_hook('post_status',
58
status._show_shelve_summary,
61
def assertStatus(self, expected_lines, working_tree, specific_files=None,
62
revision=None, short=False, pending=True, verbose=False):
63
"""Run status in working_tree and look for output.
65
:param expected_lines: The lines to look for.
66
:param working_tree: The tree to run status in.
68
output_string = self.status_string(working_tree, specific_files, revision, short,
70
self.assertEqual(expected_lines, output_string.splitlines(True))
72
def status_string(self, wt, specific_files=None, revision=None,
73
short=False, pending=True, verbose=False):
74
uio = self.make_utf8_encoded_stringio()
75
show_tree_status(wt, specific_files=specific_files, to_file=uio,
76
revision=revision, short=short, show_pending=pending,
78
return uio.getvalue().decode('utf-8')
80
def test_branch_status(self):
81
"""Test basic branch status"""
82
wt = self.make_branch_and_tree('.')
84
# status with no commits or files - it must
85
# work and show no output. We do this with no
86
# commits to be sure that it's not going to fail
88
self.assertStatus([], wt)
90
self.build_tree(['hello.c', 'bye.c'])
103
# add a commit to allow showing pending merges.
104
wt.commit('create a parent to allow testing merge output')
106
wt.add_parent_tree_id(b'pending@pending-0-0')
111
'pending merge tips: (use -v to see all merge revisions)\n',
112
' (ghost) pending@pending-0-0\n',
120
' (ghost) pending@pending-0-0\n',
126
'P (ghost) pending@pending-0-0\n',
139
wt, short=True, pending=False)
141
def test_branch_status_revisions(self):
142
"""Tests branch status with revisions"""
143
wt = self.make_branch_and_tree('.')
145
self.build_tree(['hello.c', 'bye.c'])
148
wt.commit('Test message')
150
revs = [RevisionSpec.from_string('0')]
159
self.build_tree(['more.c'])
161
wt.commit('Another test message')
163
revs.append(RevisionSpec.from_string('1'))
172
def test_pending(self):
173
"""Pending merges display works, including Unicode"""
175
wt = self.make_branch_and_tree('branch')
177
wt.commit("Empty commit 1")
178
b_2_dir = b.controldir.sprout('./copy')
179
b_2 = b_2_dir.open_branch()
180
wt2 = b_2_dir.open_workingtree()
181
wt.commit(u"\N{TIBETAN DIGIT TWO} Empty commit 2")
182
wt2.merge_from_branch(wt.branch)
183
message = self.status_string(wt2, verbose=True)
184
self.assertStartsWith(message, "pending merges:\n")
185
self.assertEndsWith(message, "Empty commit 2\n")
187
# must be long to make sure we see elipsis at the end
188
wt.commit("Empty commit 3 "
189
+ "blah blah blah blah " * 100)
190
wt2.merge_from_branch(wt.branch)
191
message = self.status_string(wt2, verbose=True)
192
self.assertStartsWith(message, "pending merges:\n")
193
self.assertTrue("Empty commit 3" in message)
194
self.assertEndsWith(message, "...\n")
196
def test_tree_status_ignores(self):
197
"""Tests branch status with ignores"""
198
wt = self.make_branch_and_tree('.')
199
self.run_bzr('ignore *~')
200
wt.commit('commit .bzrignore')
201
self.build_tree(['foo.c', 'foo.c~'])
212
def test_tree_status_specific_files(self):
213
"""Tests branch status with given specific files"""
214
wt = self.make_branch_and_tree('.')
217
self.build_tree(['directory/', 'directory/hello.c',
218
'bye.c', 'test.c', 'dir2/',
232
' directory/hello.c\n'
239
'? directory/hello.c\n',
245
self.assertRaises(errors.PathsDoNotExist,
247
wt, specific_files=['bye.c', 'test.c', 'absent.c'],
251
show_tree_status(wt, specific_files=['directory'], to_file=tof)
253
self.assertEqual(tof.readlines(),
255
' directory/hello.c\n'
258
show_tree_status(wt, specific_files=['directory'], to_file=tof,
261
self.assertEqual(tof.readlines(), ['? directory/hello.c\n'])
264
show_tree_status(wt, specific_files=['dir2'], to_file=tof)
266
self.assertEqual(tof.readlines(),
271
show_tree_status(wt, specific_files=['dir2'], to_file=tof, short=True)
273
self.assertEqual(tof.readlines(), ['? dir2/\n'])
276
revs = [RevisionSpec.from_string('0'), RevisionSpec.from_string('1')]
277
show_tree_status(wt, specific_files=['test.c'], to_file=tof,
278
short=True, revision=revs)
280
self.assertEqual(tof.readlines(), ['+N test.c\n'])
283
show_tree_status(wt, specific_files=['missing.c'], to_file=tof)
285
self.assertEqual(tof.readlines(),
290
show_tree_status(wt, specific_files=['missing.c'], to_file=tof,
293
self.assertEqual(tof.readlines(),
296
def test_specific_files_conflicts(self):
297
tree = self.make_branch_and_tree('.')
298
self.build_tree(['dir2/'])
300
tree.commit('added dir2')
301
tree.set_conflicts(conflicts.ConflictList(
302
[conflicts.ContentsConflict('foo')]))
304
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
305
self.assertEqualDiff(b'', tof.getvalue())
306
tree.set_conflicts(conflicts.ConflictList(
307
[conflicts.ContentsConflict('dir2')]))
309
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
310
self.assertEqualDiff('conflicts:\n Contents conflict in dir2\n',
313
tree.set_conflicts(conflicts.ConflictList(
314
[conflicts.ContentsConflict('dir2/file1')]))
316
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
317
self.assertEqualDiff('conflicts:\n Contents conflict in dir2/file1\n',
320
def _prepare_nonexistent(self):
321
wt = self.make_branch_and_tree('.')
322
self.assertStatus([], wt)
323
self.build_tree(['FILE_A', 'FILE_B', 'FILE_C', 'FILE_D', 'FILE_E', ])
329
wt.commit('Create five empty files.')
330
with open('FILE_B', 'w') as f:
331
f.write('Modification to file FILE_B.')
332
with open('FILE_C', 'w') as f:
333
f.write('Modification to file FILE_C.')
334
unlink('FILE_E') # FILE_E will be versioned but missing
335
with open('FILE_Q', 'w') as f:
336
f.write('FILE_Q is added but not committed.')
337
wt.add('FILE_Q') # FILE_Q will be added but not committed
338
open('UNVERSIONED_BUT_EXISTING', 'w')
341
def test_status_nonexistent_file(self):
342
# files that don't exist in either the basis tree or working tree
343
# should give an error
344
wt = self._prepare_nonexistent()
354
' UNVERSIONED_BUT_EXISTING\n',
362
'? UNVERSIONED_BUT_EXISTING\n',
366
# Okay, everything's looking good with the existent files.
367
# Let's see what happens when we throw in non-existent files.
369
# brz st [--short] NONEXISTENT '
374
out, err = self.run_bzr('status NONEXISTENT', retcode=3)
375
self.assertEqual(expected, out.splitlines(True))
376
self.assertContainsRe(err,
377
r'.*ERROR: Path\(s\) do not exist: '
382
out, err = self.run_bzr('status --short NONEXISTENT', retcode=3)
383
self.assertContainsRe(err,
384
r'.*ERROR: Path\(s\) do not exist: '
387
def test_status_nonexistent_file_with_others(self):
388
# brz st [--short] NONEXISTENT ...others..
389
wt = self._prepare_nonexistent()
399
out, err = self.run_bzr('status NONEXISTENT '
400
'FILE_A FILE_B FILE_C FILE_D FILE_E',
402
self.assertEqual(expected, out.splitlines(True))
403
self.assertContainsRe(err,
404
r'.*ERROR: Path\(s\) do not exist: '
412
out, err = self.run_bzr('status --short NONEXISTENT '
413
'FILE_A FILE_B FILE_C FILE_D FILE_E',
415
self.assertEqual(expected, out.splitlines(True))
416
self.assertContainsRe(err,
417
r'.*ERROR: Path\(s\) do not exist: '
420
def test_status_multiple_nonexistent_files(self):
421
# brz st [--short] NONEXISTENT ... ANOTHER_NONEXISTENT ...
422
wt = self._prepare_nonexistent()
430
' ANOTHER_NONEXISTENT\n',
433
out, err = self.run_bzr('status NONEXISTENT '
434
'FILE_A FILE_B ANOTHER_NONEXISTENT '
435
'FILE_C FILE_D FILE_E', retcode=3)
436
self.assertEqual(expected, out.splitlines(True))
437
self.assertContainsRe(err,
438
r'.*ERROR: Path\(s\) do not exist: '
439
'ANOTHER_NONEXISTENT NONEXISTENT.*')
444
'X ANOTHER_NONEXISTENT\n',
447
out, err = self.run_bzr('status --short NONEXISTENT '
448
'FILE_A FILE_B ANOTHER_NONEXISTENT '
449
'FILE_C FILE_D FILE_E', retcode=3)
450
self.assertEqual(expected, out.splitlines(True))
451
self.assertContainsRe(err,
452
r'.*ERROR: Path\(s\) do not exist: '
453
'ANOTHER_NONEXISTENT NONEXISTENT.*')
455
def test_status_nonexistent_file_with_unversioned(self):
456
# brz st [--short] NONEXISTENT A B UNVERSIONED_BUT_EXISTING C D E Q
457
wt = self._prepare_nonexistent()
467
' UNVERSIONED_BUT_EXISTING\n',
471
out, err = self.run_bzr('status NONEXISTENT '
472
'FILE_A FILE_B UNVERSIONED_BUT_EXISTING '
473
'FILE_C FILE_D FILE_E FILE_Q', retcode=3)
474
self.assertEqual(expected, out.splitlines(True))
475
self.assertContainsRe(err,
476
r'.*ERROR: Path\(s\) do not exist: '
480
'? UNVERSIONED_BUT_EXISTING\n',
486
out, err = self.run_bzr('status --short NONEXISTENT '
487
'FILE_A FILE_B UNVERSIONED_BUT_EXISTING '
488
'FILE_C FILE_D FILE_E FILE_Q', retcode=3)
489
actual = out.splitlines(True)
491
self.assertEqual(expected, actual)
492
self.assertContainsRe(err,
493
r'.*ERROR: Path\(s\) do not exist: '
496
def test_status_out_of_date(self):
497
"""Simulate status of out-of-date tree after remote push"""
498
tree = self.make_branch_and_tree('.')
499
self.build_tree_contents([('a', b'foo\n')])
503
tree.commit('add test file')
504
# simulate what happens after a remote push
505
tree.set_last_revision(b"0")
507
# before run another commands we should unlock tree
509
out, err = self.run_bzr('status')
510
self.assertEqual("working tree is out of date, run 'brz update'\n",
513
def test_status_on_ignored(self):
514
"""Tests branch status on an unversioned file which is considered ignored.
516
See https://bugs.launchpad.net/bzr/+bug/40103
518
tree = self.make_branch_and_tree('.')
520
self.build_tree(['test1.c', 'test1.c~', 'test2.c~'])
521
result = self.run_bzr('status')[0]
522
self.assertContainsRe(result, "unknown:\n test1.c\n")
523
short_result = self.run_bzr('status --short')[0]
524
self.assertContainsRe(short_result, "\\? test1.c\n")
526
result = self.run_bzr('status test1.c')[0]
527
self.assertContainsRe(result, "unknown:\n test1.c\n")
528
short_result = self.run_bzr('status --short test1.c')[0]
529
self.assertContainsRe(short_result, "\\? test1.c\n")
531
result = self.run_bzr('status test1.c~')[0]
532
self.assertContainsRe(result, "ignored:\n test1.c~\n")
533
short_result = self.run_bzr('status --short test1.c~')[0]
534
self.assertContainsRe(short_result, "I test1.c~\n")
536
result = self.run_bzr('status test1.c~ test2.c~')[0]
537
self.assertContainsRe(result, "ignored:\n test1.c~\n test2.c~\n")
538
short_result = self.run_bzr('status --short test1.c~ test2.c~')[0]
539
self.assertContainsRe(short_result, "I test1.c~\nI test2.c~\n")
541
result = self.run_bzr('status test1.c test1.c~ test2.c~')[0]
542
self.assertContainsRe(
543
result, "unknown:\n test1.c\nignored:\n test1.c~\n test2.c~\n")
544
short_result = self.run_bzr(
545
'status --short test1.c test1.c~ test2.c~')[0]
546
self.assertContainsRe(
547
short_result, "\\? test1.c\nI test1.c~\nI test2.c~\n")
549
def test_status_write_lock(self):
550
"""Test that status works without fetching history and
553
See https://bugs.launchpad.net/bzr/+bug/149270
556
wt = self.make_branch_and_tree('branch1')
558
wt.commit('Empty commit 1')
559
wt2 = b.controldir.sprout('branch2').open_workingtree()
560
wt2.commit('Empty commit 2')
561
out, err = self.run_bzr('status branch1 -rbranch:branch2')
562
self.assertEqual('', out)
564
def test_status_with_shelves(self):
565
"""Ensure that _show_shelve_summary handler works.
567
wt = self.make_branch_and_tree('.')
568
self.build_tree(['hello.c'])
570
self.run_bzr(['shelve', '--all', '-m', 'foo'])
571
self.build_tree(['bye.c'])
576
'1 shelf exists. See "brz shelve --list" for details.\n',
579
self.run_bzr(['shelve', '--all', '-m', 'bar'])
580
self.build_tree(['eggs.c', 'spam.c'])
587
'2 shelves exist. See "brz shelve --list" for details.\n',
595
specific_files=['spam.c'])
598
class CheckoutStatus(BranchStatus):
601
super(CheckoutStatus, self).setUp()
605
def make_branch_and_tree(self, relpath):
606
source = self.make_branch(pathjoin('..', relpath))
607
checkout = bzrdir.BzrDirMetaFormat1().initialize(relpath)
608
checkout.set_branch_reference(source)
609
return checkout.create_workingtree()
612
class TestStatus(TestCaseWithTransport):
614
def test_status_plain(self):
615
tree = self.make_branch_and_tree('.')
617
self.build_tree(['hello.txt'])
618
result = self.run_bzr("status")[0]
619
self.assertContainsRe(result, "unknown:\n hello.txt\n")
621
tree.add("hello.txt")
622
result = self.run_bzr("status")[0]
623
self.assertContainsRe(result, "added:\n hello.txt\n")
625
tree.commit(message="added")
626
result = self.run_bzr("status -r 0..1")[0]
627
self.assertContainsRe(result, "added:\n hello.txt\n")
629
result = self.run_bzr("status -c 1")[0]
630
self.assertContainsRe(result, "added:\n hello.txt\n")
632
self.build_tree(['world.txt'])
633
result = self.run_bzr("status -r 0")[0]
634
self.assertContainsRe(result, "added:\n hello.txt\n"
635
"unknown:\n world.txt\n")
636
result2 = self.run_bzr("status -r 0..")[0]
637
self.assertEqual(result2, result)
639
def test_status_short(self):
640
tree = self.make_branch_and_tree('.')
642
self.build_tree(['hello.txt'])
643
result = self.run_bzr("status --short")[0]
644
self.assertContainsRe(result, "[?] hello.txt\n")
646
tree.add("hello.txt")
647
result = self.run_bzr("status --short")[0]
648
self.assertContainsRe(result, "[+]N hello.txt\n")
650
tree.commit(message="added")
651
result = self.run_bzr("status --short -r 0..1")[0]
652
self.assertContainsRe(result, "[+]N hello.txt\n")
654
self.build_tree(['world.txt'])
655
result = self.run_bzr("status -S -r 0")[0]
656
self.assertContainsRe(result, "[+]N hello.txt\n"
658
result2 = self.run_bzr("status -S -r 0..")[0]
659
self.assertEqual(result2, result)
661
def test_status_versioned(self):
662
tree = self.make_branch_and_tree('.')
664
self.build_tree(['hello.txt'])
665
result = self.run_bzr("status --versioned")[0]
666
self.assertNotContainsRe(result, "unknown:\n hello.txt\n")
668
tree.add("hello.txt")
669
result = self.run_bzr("status --versioned")[0]
670
self.assertContainsRe(result, "added:\n hello.txt\n")
673
result = self.run_bzr("status --versioned -r 0..1")[0]
674
self.assertContainsRe(result, "added:\n hello.txt\n")
676
self.build_tree(['world.txt'])
677
result = self.run_bzr("status --versioned -r 0")[0]
678
self.assertContainsRe(result, "added:\n hello.txt\n")
679
self.assertNotContainsRe(result, "unknown:\n world.txt\n")
680
result2 = self.run_bzr("status --versioned -r 0..")[0]
681
self.assertEqual(result2, result)
683
def test_status_SV(self):
684
tree = self.make_branch_and_tree('.')
686
self.build_tree(['hello.txt'])
687
result = self.run_bzr("status -SV")[0]
688
self.assertNotContainsRe(result, "hello.txt")
690
tree.add("hello.txt")
691
result = self.run_bzr("status -SV")[0]
692
self.assertContainsRe(result, "[+]N hello.txt\n")
694
tree.commit(message="added")
695
result = self.run_bzr("status -SV -r 0..1")[0]
696
self.assertContainsRe(result, "[+]N hello.txt\n")
698
self.build_tree(['world.txt'])
699
result = self.run_bzr("status -SV -r 0")[0]
700
self.assertContainsRe(result, "[+]N hello.txt\n")
702
result2 = self.run_bzr("status -SV -r 0..")[0]
703
self.assertEqual(result2, result)
705
def assertStatusContains(self, pattern, short=False):
706
"""Run status, and assert it contains the given pattern"""
708
result = self.run_bzr("status --short")[0]
710
result = self.run_bzr("status")[0]
711
self.assertContainsRe(result, pattern)
713
def test_kind_change_plain(self):
714
tree = self.make_branch_and_tree('.')
715
self.build_tree(['file'])
717
tree.commit('added file')
719
self.build_tree(['file/'])
720
self.assertStatusContains(
721
'kind changed:\n file \\(file => directory\\)')
722
tree.rename_one('file', 'directory')
723
self.assertStatusContains('renamed:\n file/ => directory/\n'
724
'modified:\n directory/\n')
726
self.assertStatusContains('removed:\n file\n')
728
def test_kind_change_short(self):
729
tree = self.make_branch_and_tree('.')
730
self.build_tree(['file'])
732
tree.commit('added file')
734
self.build_tree(['file/'])
735
self.assertStatusContains('K file => file/',
737
tree.rename_one('file', 'directory')
738
self.assertStatusContains('RK file => directory/',
741
self.assertStatusContains('RD file => directory',
744
def test_status_illegal_revision_specifiers(self):
745
out, err = self.run_bzr('status -r 1..23..123', retcode=3)
746
self.assertContainsRe(err, 'one or two revision specifiers')
748
def test_status_no_pending(self):
749
a_tree = self.make_branch_and_tree('a')
750
self.build_tree(['a/a'])
753
b_tree = a_tree.controldir.sprout('b').open_workingtree()
754
self.build_tree(['b/b'])
758
self.run_bzr('merge ../b', working_dir='a')
759
out, err = self.run_bzr('status --no-pending', working_dir='a')
760
self.assertEqual(out, "added:\n b\n")
762
def test_pending_specific_files(self):
763
"""With a specific file list, pending merges are not shown."""
764
tree = self.make_branch_and_tree('tree')
765
self.build_tree_contents([('tree/a', b'content of a\n')])
767
r1_id = tree.commit('one')
768
alt = tree.controldir.sprout('alt').open_workingtree()
769
self.build_tree_contents([('alt/a', b'content of a\nfrom alt\n')])
770
alt_id = alt.commit('alt')
771
tree.merge_from_branch(alt.branch)
772
output = self.make_utf8_encoded_stringio()
773
show_tree_status(tree, to_file=output)
774
self.assertContainsRe(output.getvalue(), b'pending merge')
775
out, err = self.run_bzr('status tree/a')
776
self.assertNotContainsRe(out, 'pending merge')
779
class TestStatusEncodings(TestCaseWithTransport):
781
def make_uncommitted_tree(self):
782
"""Build a branch with uncommitted unicode named changes in the cwd."""
783
working_tree = self.make_branch_and_tree(u'.')
784
filename = u'hell\u00d8'
786
self.build_tree_contents([(filename, b'contents of hello')])
787
except UnicodeEncodeError:
788
raise TestSkipped("can't build unicode working tree in "
789
"filesystem encoding %s" % sys.getfilesystemencoding())
790
working_tree.add(filename)
793
def test_stdout_ascii(self):
794
self.overrideAttr(osutils, '_cached_user_encoding', 'ascii')
795
working_tree = self.make_uncommitted_tree()
796
stdout, stderr = self.run_bzr("status")
798
self.assertEqual(stdout, """\
803
def test_stdout_latin1(self):
804
self.overrideAttr(osutils, '_cached_user_encoding', 'latin-1')
805
working_tree = self.make_uncommitted_tree()
806
stdout, stderr = self.run_bzr('status')
813
expected = expected.encode('latin-1')
814
self.assertEqual(stdout, expected)