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
39
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.ConflictList(
301
[conflicts.ContentsConflict('foo')]))
303
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
304
self.assertEqualDiff(b'', tof.getvalue())
305
tree.set_conflicts(conflicts.ConflictList(
306
[conflicts.ContentsConflict('dir2')]))
308
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
309
self.assertEqualDiff('conflicts:\n Contents conflict in dir2\n',
312
tree.set_conflicts(conflicts.ConflictList(
313
[conflicts.ContentsConflict('dir2/file1')]))
315
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
316
self.assertEqualDiff('conflicts:\n Contents conflict in dir2/file1\n',
319
def _prepare_nonexistent(self):
320
wt = self.make_branch_and_tree('.')
321
self.assertStatus([], wt)
322
self.build_tree(['FILE_A', 'FILE_B', 'FILE_C', 'FILE_D', 'FILE_E', ])
328
wt.commit('Create five empty files.')
329
with open('FILE_B', 'w') as f:
330
f.write('Modification to file FILE_B.')
331
with open('FILE_C', 'w') as f:
332
f.write('Modification to file FILE_C.')
333
unlink('FILE_E') # FILE_E will be versioned but missing
334
with open('FILE_Q', 'w') as f:
335
f.write('FILE_Q is added but not committed.')
336
wt.add('FILE_Q') # FILE_Q will be added but not committed
337
open('UNVERSIONED_BUT_EXISTING', 'w')
340
def test_status_nonexistent_file(self):
341
# files that don't exist in either the basis tree or working tree
342
# should give an error
343
wt = self._prepare_nonexistent()
353
' UNVERSIONED_BUT_EXISTING\n',
361
'? UNVERSIONED_BUT_EXISTING\n',
365
# Okay, everything's looking good with the existent files.
366
# Let's see what happens when we throw in non-existent files.
368
# brz st [--short] NONEXISTENT '
373
out, err = self.run_bzr('status NONEXISTENT', retcode=3)
374
self.assertEqual(expected, out.splitlines(True))
375
self.assertContainsRe(err,
376
r'.*ERROR: Path\(s\) do not exist: '
381
out, err = self.run_bzr('status --short NONEXISTENT', retcode=3)
382
self.assertContainsRe(err,
383
r'.*ERROR: Path\(s\) do not exist: '
386
def test_status_nonexistent_file_with_others(self):
387
# brz st [--short] NONEXISTENT ...others..
388
wt = self._prepare_nonexistent()
398
out, err = self.run_bzr('status NONEXISTENT '
399
'FILE_A FILE_B FILE_C FILE_D FILE_E',
401
self.assertEqual(expected, out.splitlines(True))
402
self.assertContainsRe(err,
403
r'.*ERROR: Path\(s\) do not exist: '
411
out, err = self.run_bzr('status --short NONEXISTENT '
412
'FILE_A FILE_B FILE_C FILE_D FILE_E',
414
self.assertEqual(expected, out.splitlines(True))
415
self.assertContainsRe(err,
416
r'.*ERROR: Path\(s\) do not exist: '
419
def test_status_multiple_nonexistent_files(self):
420
# brz st [--short] NONEXISTENT ... ANOTHER_NONEXISTENT ...
421
wt = self._prepare_nonexistent()
429
' ANOTHER_NONEXISTENT\n',
432
out, err = self.run_bzr('status NONEXISTENT '
433
'FILE_A FILE_B ANOTHER_NONEXISTENT '
434
'FILE_C FILE_D FILE_E', retcode=3)
435
self.assertEqual(expected, out.splitlines(True))
436
self.assertContainsRe(err,
437
r'.*ERROR: Path\(s\) do not exist: '
438
'ANOTHER_NONEXISTENT NONEXISTENT.*')
443
'X ANOTHER_NONEXISTENT\n',
446
out, err = self.run_bzr('status --short NONEXISTENT '
447
'FILE_A FILE_B ANOTHER_NONEXISTENT '
448
'FILE_C FILE_D FILE_E', retcode=3)
449
self.assertEqual(expected, out.splitlines(True))
450
self.assertContainsRe(err,
451
r'.*ERROR: Path\(s\) do not exist: '
452
'ANOTHER_NONEXISTENT NONEXISTENT.*')
454
def test_status_nonexistent_file_with_unversioned(self):
455
# brz st [--short] NONEXISTENT A B UNVERSIONED_BUT_EXISTING C D E Q
456
wt = self._prepare_nonexistent()
466
' UNVERSIONED_BUT_EXISTING\n',
470
out, err = self.run_bzr('status NONEXISTENT '
471
'FILE_A FILE_B UNVERSIONED_BUT_EXISTING '
472
'FILE_C FILE_D FILE_E FILE_Q', retcode=3)
473
self.assertEqual(expected, out.splitlines(True))
474
self.assertContainsRe(err,
475
r'.*ERROR: Path\(s\) do not exist: '
479
'? UNVERSIONED_BUT_EXISTING\n',
485
out, err = self.run_bzr('status --short NONEXISTENT '
486
'FILE_A FILE_B UNVERSIONED_BUT_EXISTING '
487
'FILE_C FILE_D FILE_E FILE_Q', retcode=3)
488
actual = out.splitlines(True)
490
self.assertEqual(expected, actual)
491
self.assertContainsRe(err,
492
r'.*ERROR: Path\(s\) do not exist: '
495
def test_status_out_of_date(self):
496
"""Simulate status of out-of-date tree after remote push"""
497
tree = self.make_branch_and_tree('.')
498
self.build_tree_contents([('a', b'foo\n')])
499
with tree.lock_write():
501
tree.commit('add test file')
502
# simulate what happens after a remote push
503
tree.set_last_revision(b"0")
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(
538
result, "unknown:\n test1.c\nignored:\n test1.c~\n test2.c~\n")
539
short_result = self.run_bzr(
540
'status --short test1.c test1.c~ test2.c~')[0]
541
self.assertContainsRe(
542
short_result, "\\? test1.c\nI test1.c~\nI test2.c~\n")
544
def test_status_write_lock(self):
545
"""Test that status works without fetching history and
548
See https://bugs.launchpad.net/bzr/+bug/149270
551
wt = self.make_branch_and_tree('branch1')
553
wt.commit('Empty commit 1')
554
wt2 = b.controldir.sprout('branch2').open_workingtree()
555
wt2.commit('Empty commit 2')
556
out, err = self.run_bzr('status branch1 -rbranch:branch2')
557
self.assertEqual('', out)
559
def test_status_with_shelves(self):
560
"""Ensure that _show_shelve_summary handler works.
562
wt = self.make_branch_and_tree('.')
563
self.build_tree(['hello.c'])
565
self.run_bzr(['shelve', '--all', '-m', 'foo'])
566
self.build_tree(['bye.c'])
571
'1 shelf exists. See "brz shelve --list" for details.\n',
574
self.run_bzr(['shelve', '--all', '-m', 'bar'])
575
self.build_tree(['eggs.c', 'spam.c'])
582
'2 shelves exist. See "brz shelve --list" for details.\n',
590
specific_files=['spam.c'])
593
class CheckoutStatus(BranchStatus):
596
super(CheckoutStatus, self).setUp()
600
def make_branch_and_tree(self, relpath):
601
source = self.make_branch(pathjoin('..', relpath))
602
checkout = bzrdir.BzrDirMetaFormat1().initialize(relpath)
603
checkout.set_branch_reference(source)
604
return checkout.create_workingtree()
607
class TestStatus(TestCaseWithTransport):
609
def test_status_plain(self):
610
tree = self.make_branch_and_tree('.')
612
self.build_tree(['hello.txt'])
613
result = self.run_bzr("status")[0]
614
self.assertContainsRe(result, "unknown:\n hello.txt\n")
616
tree.add("hello.txt")
617
result = self.run_bzr("status")[0]
618
self.assertContainsRe(result, "added:\n hello.txt\n")
620
tree.commit(message="added")
621
result = self.run_bzr("status -r 0..1")[0]
622
self.assertContainsRe(result, "added:\n hello.txt\n")
624
result = self.run_bzr("status -c 1")[0]
625
self.assertContainsRe(result, "added:\n hello.txt\n")
627
self.build_tree(['world.txt'])
628
result = self.run_bzr("status -r 0")[0]
629
self.assertContainsRe(result, "added:\n hello.txt\n"
630
"unknown:\n world.txt\n")
631
result2 = self.run_bzr("status -r 0..")[0]
632
self.assertEqual(result2, result)
634
def test_status_short(self):
635
tree = self.make_branch_and_tree('.')
637
self.build_tree(['hello.txt'])
638
result = self.run_bzr("status --short")[0]
639
self.assertContainsRe(result, "[?] hello.txt\n")
641
tree.add("hello.txt")
642
result = self.run_bzr("status --short")[0]
643
self.assertContainsRe(result, "[+]N hello.txt\n")
645
tree.commit(message="added")
646
result = self.run_bzr("status --short -r 0..1")[0]
647
self.assertContainsRe(result, "[+]N hello.txt\n")
649
self.build_tree(['world.txt'])
650
result = self.run_bzr("status -S -r 0")[0]
651
self.assertContainsRe(result, "[+]N hello.txt\n"
653
result2 = self.run_bzr("status -S -r 0..")[0]
654
self.assertEqual(result2, result)
656
def test_status_versioned(self):
657
tree = self.make_branch_and_tree('.')
659
self.build_tree(['hello.txt'])
660
result = self.run_bzr("status --versioned")[0]
661
self.assertNotContainsRe(result, "unknown:\n hello.txt\n")
663
tree.add("hello.txt")
664
result = self.run_bzr("status --versioned")[0]
665
self.assertContainsRe(result, "added:\n hello.txt\n")
668
result = self.run_bzr("status --versioned -r 0..1")[0]
669
self.assertContainsRe(result, "added:\n hello.txt\n")
671
self.build_tree(['world.txt'])
672
result = self.run_bzr("status --versioned -r 0")[0]
673
self.assertContainsRe(result, "added:\n hello.txt\n")
674
self.assertNotContainsRe(result, "unknown:\n world.txt\n")
675
result2 = self.run_bzr("status --versioned -r 0..")[0]
676
self.assertEqual(result2, result)
678
def test_status_SV(self):
679
tree = self.make_branch_and_tree('.')
681
self.build_tree(['hello.txt'])
682
result = self.run_bzr("status -SV")[0]
683
self.assertNotContainsRe(result, "hello.txt")
685
tree.add("hello.txt")
686
result = self.run_bzr("status -SV")[0]
687
self.assertContainsRe(result, "[+]N hello.txt\n")
689
tree.commit(message="added")
690
result = self.run_bzr("status -SV -r 0..1")[0]
691
self.assertContainsRe(result, "[+]N hello.txt\n")
693
self.build_tree(['world.txt'])
694
result = self.run_bzr("status -SV -r 0")[0]
695
self.assertContainsRe(result, "[+]N hello.txt\n")
697
result2 = self.run_bzr("status -SV -r 0..")[0]
698
self.assertEqual(result2, result)
700
def assertStatusContains(self, pattern, short=False):
701
"""Run status, and assert it contains the given pattern"""
703
result = self.run_bzr("status --short")[0]
705
result = self.run_bzr("status")[0]
706
self.assertContainsRe(result, pattern)
708
def test_kind_change_plain(self):
709
tree = self.make_branch_and_tree('.')
710
self.build_tree(['file'])
712
tree.commit('added file')
714
self.build_tree(['file/'])
715
self.assertStatusContains(
716
'kind changed:\n file \\(file => directory\\)')
717
tree.rename_one('file', 'directory')
718
self.assertStatusContains('renamed:\n file => directory/\n'
719
'modified:\n directory/\n')
721
self.assertStatusContains('removed:\n file\n')
723
def test_kind_change_short(self):
724
tree = self.make_branch_and_tree('.')
725
self.build_tree(['file'])
727
tree.commit('added file')
729
self.build_tree(['file/'])
730
self.assertStatusContains('K file => file/',
732
tree.rename_one('file', 'directory')
733
self.assertStatusContains('RK file => directory/',
736
self.assertStatusContains('RD file => directory',
739
def test_status_illegal_revision_specifiers(self):
740
out, err = self.run_bzr('status -r 1..23..123', retcode=3)
741
self.assertContainsRe(err, 'one or two revision specifiers')
743
def test_status_no_pending(self):
744
a_tree = self.make_branch_and_tree('a')
745
self.build_tree(['a/a'])
748
b_tree = a_tree.controldir.sprout('b').open_workingtree()
749
self.build_tree(['b/b'])
753
self.run_bzr('merge ../b', working_dir='a')
754
out, err = self.run_bzr('status --no-pending', working_dir='a')
755
self.assertEqual(out, "added:\n b\n")
757
def test_pending_specific_files(self):
758
"""With a specific file list, pending merges are not shown."""
759
tree = self.make_branch_and_tree('tree')
760
self.build_tree_contents([('tree/a', b'content of a\n')])
762
r1_id = tree.commit('one')
763
alt = tree.controldir.sprout('alt').open_workingtree()
764
self.build_tree_contents([('alt/a', b'content of a\nfrom alt\n')])
765
alt_id = alt.commit('alt')
766
tree.merge_from_branch(alt.branch)
767
output = self.make_utf8_encoded_stringio()
768
show_tree_status(tree, to_file=output)
769
self.assertContainsRe(output.getvalue(), b'pending merge')
770
out, err = self.run_bzr('status tree/a')
771
self.assertNotContainsRe(out, 'pending merge')
774
class TestStatusEncodings(TestCaseWithTransport):
776
def make_uncommitted_tree(self):
777
"""Build a branch with uncommitted unicode named changes in the cwd."""
778
working_tree = self.make_branch_and_tree(u'.')
779
filename = u'hell\u00d8'
781
self.build_tree_contents([(filename, b'contents of hello')])
782
except UnicodeEncodeError:
783
raise TestSkipped("can't build unicode working tree in "
784
"filesystem encoding %s" % sys.getfilesystemencoding())
785
working_tree.add(filename)
788
def test_stdout_ascii(self):
789
self.overrideAttr(osutils, '_cached_user_encoding', 'ascii')
790
working_tree = self.make_uncommitted_tree()
791
stdout, stderr = self.run_bzr("status")
793
self.assertEqual(stdout, """\
798
def test_stdout_latin1(self):
799
self.overrideAttr(osutils, '_cached_user_encoding', 'latin-1')
800
working_tree = self.make_uncommitted_tree()
801
stdout, stderr = self.run_bzr('status')
807
self.assertEqual(stdout, expected)