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
37
from ...osutils import pathjoin
38
from ...revisionspec import RevisionSpec
39
from ...sixish import (
42
from ...status import show_tree_status
43
from .. import TestCaseWithTransport, TestSkipped
44
from ...workingtree import WorkingTree
47
class BranchStatus(TestCaseWithTransport):
50
super(BranchStatus, self).setUp()
51
# As TestCase.setUp clears all hooks, we install this default
52
# post_status hook handler for the test.
53
status.hooks.install_named_hook('post_status',
54
status._show_shelve_summary,
57
def assertStatus(self, expected_lines, working_tree, specific_files=None,
58
revision=None, short=False, pending=True, verbose=False):
59
"""Run status in working_tree and look for output.
61
:param expected_lines: The lines to look for.
62
:param working_tree: The tree to run status in.
64
output_string = self.status_string(working_tree, specific_files, revision, short,
66
self.assertEqual(expected_lines, output_string.splitlines(True))
68
def status_string(self, wt, specific_files=None, revision=None,
69
short=False, pending=True, verbose=False):
70
uio = self.make_utf8_encoded_stringio()
71
show_tree_status(wt, specific_files=specific_files, to_file=uio,
72
revision=revision, short=short, show_pending=pending,
74
return uio.getvalue().decode('utf-8')
76
def test_branch_status(self):
77
"""Test basic branch status"""
78
wt = self.make_branch_and_tree('.')
80
# status with no commits or files - it must
81
# work and show no output. We do this with no
82
# commits to be sure that it's not going to fail
84
self.assertStatus([], wt)
86
self.build_tree(['hello.c', 'bye.c'])
99
# add a commit to allow showing pending merges.
100
wt.commit('create a parent to allow testing merge output')
102
wt.add_parent_tree_id('pending@pending-0-0')
107
'pending merge tips: (use -v to see all merge revisions)\n',
108
' (ghost) pending@pending-0-0\n',
116
' (ghost) pending@pending-0-0\n',
122
'P (ghost) pending@pending-0-0\n',
135
wt, short=True, pending=False)
137
def test_branch_status_revisions(self):
138
"""Tests branch status with revisions"""
139
wt = self.make_branch_and_tree('.')
141
self.build_tree(['hello.c', 'bye.c'])
144
wt.commit('Test message')
146
revs = [RevisionSpec.from_string('0')]
155
self.build_tree(['more.c'])
157
wt.commit('Another test message')
159
revs.append(RevisionSpec.from_string('1'))
168
def test_pending(self):
169
"""Pending merges display works, including Unicode"""
171
wt = self.make_branch_and_tree('branch')
173
wt.commit("Empty commit 1")
174
b_2_dir = b.bzrdir.sprout('./copy')
175
b_2 = b_2_dir.open_branch()
176
wt2 = b_2_dir.open_workingtree()
177
wt.commit(u"\N{TIBETAN DIGIT TWO} Empty commit 2")
178
wt2.merge_from_branch(wt.branch)
179
message = self.status_string(wt2, verbose=True)
180
self.assertStartsWith(message, "pending merges:\n")
181
self.assertEndsWith(message, "Empty commit 2\n")
183
# must be long to make sure we see elipsis at the end
184
wt.commit("Empty commit 3 " +
185
"blah blah blah blah " * 100)
186
wt2.merge_from_branch(wt.branch)
187
message = self.status_string(wt2, verbose=True)
188
self.assertStartsWith(message, "pending merges:\n")
189
self.assertTrue("Empty commit 3" in message)
190
self.assertEndsWith(message, "...\n")
192
def test_tree_status_ignores(self):
193
"""Tests branch status with ignores"""
194
wt = self.make_branch_and_tree('.')
195
self.run_bzr('ignore *~')
196
wt.commit('commit .bzrignore')
197
self.build_tree(['foo.c', 'foo.c~'])
208
def test_tree_status_specific_files(self):
209
"""Tests branch status with given specific files"""
210
wt = self.make_branch_and_tree('.')
213
self.build_tree(['directory/','directory/hello.c',
214
'bye.c','test.c','dir2/',
228
' directory/hello.c\n'
236
'? directory/hello.c\n'
241
self.assertRaises(errors.PathsDoNotExist,
243
wt, specific_files=['bye.c','test.c','absent.c'],
247
show_tree_status(wt, specific_files=['directory'], to_file=tof)
249
self.assertEqual(tof.readlines(),
251
' directory/hello.c\n'
254
show_tree_status(wt, specific_files=['directory'], to_file=tof,
257
self.assertEqual(tof.readlines(), ['? directory/hello.c\n'])
260
show_tree_status(wt, specific_files=['dir2'], to_file=tof)
262
self.assertEqual(tof.readlines(),
267
show_tree_status(wt, specific_files=['dir2'], to_file=tof, short=True)
269
self.assertEqual(tof.readlines(), ['? dir2/\n'])
272
revs = [RevisionSpec.from_string('0'), RevisionSpec.from_string('1')]
273
show_tree_status(wt, specific_files=['test.c'], to_file=tof,
274
short=True, revision=revs)
276
self.assertEqual(tof.readlines(), ['+N test.c\n'])
279
show_tree_status(wt, specific_files=['missing.c'], to_file=tof)
281
self.assertEqual(tof.readlines(),
286
show_tree_status(wt, specific_files=['missing.c'], to_file=tof,
289
self.assertEqual(tof.readlines(),
292
def test_specific_files_conflicts(self):
293
tree = self.make_branch_and_tree('.')
294
self.build_tree(['dir2/'])
296
tree.commit('added dir2')
297
tree.set_conflicts(conflicts.ConflictList(
298
[conflicts.ContentsConflict('foo')]))
300
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
301
self.assertEqualDiff('', tof.getvalue())
302
tree.set_conflicts(conflicts.ConflictList(
303
[conflicts.ContentsConflict('dir2')]))
305
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
306
self.assertEqualDiff('conflicts:\n Contents conflict in dir2\n',
309
tree.set_conflicts(conflicts.ConflictList(
310
[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: f.write('Modification to file FILE_B.')
327
with open('FILE_C', 'w') as f: f.write('Modification to file FILE_C.')
328
unlink('FILE_E') # FILE_E will be versioned but missing
329
with open('FILE_Q', 'w') as f: f.write('FILE_Q is added but not committed.')
330
wt.add('FILE_Q') # FILE_Q will be added but not committed
331
open('UNVERSIONED_BUT_EXISTING', 'w')
334
def test_status_nonexistent_file(self):
335
# files that don't exist in either the basis tree or working tree
336
# should give an error
337
wt = self._prepare_nonexistent()
347
' UNVERSIONED_BUT_EXISTING\n',
355
'? UNVERSIONED_BUT_EXISTING\n',
359
# Okay, everything's looking good with the existent files.
360
# Let's see what happens when we throw in non-existent files.
362
# brz st [--short] NONEXISTENT '
367
out, err = self.run_bzr('status NONEXISTENT', retcode=3)
368
self.assertEqual(expected, out.splitlines(True))
369
self.assertContainsRe(err,
370
r'.*ERROR: Path\(s\) do not exist: '
375
out, err = self.run_bzr('status --short NONEXISTENT', retcode=3)
376
self.assertContainsRe(err,
377
r'.*ERROR: Path\(s\) do not exist: '
380
def test_status_nonexistent_file_with_others(self):
381
# brz st [--short] NONEXISTENT ...others..
382
wt = self._prepare_nonexistent()
392
out, err = self.run_bzr('status NONEXISTENT '
393
'FILE_A FILE_B FILE_C FILE_D FILE_E',
395
self.assertEqual(expected, out.splitlines(True))
396
self.assertContainsRe(err,
397
r'.*ERROR: Path\(s\) do not exist: '
405
out, err = self.run_bzr('status --short NONEXISTENT '
406
'FILE_A FILE_B FILE_C FILE_D FILE_E',
408
self.assertEqual(expected, out.splitlines(True))
409
self.assertContainsRe(err,
410
r'.*ERROR: Path\(s\) do not exist: '
413
def test_status_multiple_nonexistent_files(self):
414
# brz st [--short] NONEXISTENT ... ANOTHER_NONEXISTENT ...
415
wt = self._prepare_nonexistent()
423
' ANOTHER_NONEXISTENT\n',
426
out, err = self.run_bzr('status NONEXISTENT '
427
'FILE_A FILE_B ANOTHER_NONEXISTENT '
428
'FILE_C FILE_D FILE_E', retcode=3)
429
self.assertEqual(expected, out.splitlines(True))
430
self.assertContainsRe(err,
431
r'.*ERROR: Path\(s\) do not exist: '
432
'ANOTHER_NONEXISTENT NONEXISTENT.*')
437
'X ANOTHER_NONEXISTENT\n',
440
out, err = self.run_bzr('status --short NONEXISTENT '
441
'FILE_A FILE_B ANOTHER_NONEXISTENT '
442
'FILE_C FILE_D FILE_E', retcode=3)
443
self.assertEqual(expected, out.splitlines(True))
444
self.assertContainsRe(err,
445
r'.*ERROR: Path\(s\) do not exist: '
446
'ANOTHER_NONEXISTENT NONEXISTENT.*')
448
def test_status_nonexistent_file_with_unversioned(self):
449
# brz st [--short] NONEXISTENT A B UNVERSIONED_BUT_EXISTING C D E Q
450
wt = self._prepare_nonexistent()
460
' UNVERSIONED_BUT_EXISTING\n',
464
out, err = self.run_bzr('status NONEXISTENT '
465
'FILE_A FILE_B UNVERSIONED_BUT_EXISTING '
466
'FILE_C FILE_D FILE_E FILE_Q', retcode=3)
467
self.assertEqual(expected, out.splitlines(True))
468
self.assertContainsRe(err,
469
r'.*ERROR: Path\(s\) do not exist: '
473
'? UNVERSIONED_BUT_EXISTING\n',
479
out, err = self.run_bzr('status --short NONEXISTENT '
480
'FILE_A FILE_B UNVERSIONED_BUT_EXISTING '
481
'FILE_C FILE_D FILE_E FILE_Q', retcode=3)
482
actual = out.splitlines(True)
484
self.assertEqual(expected, actual)
485
self.assertContainsRe(err,
486
r'.*ERROR: Path\(s\) do not exist: '
489
def test_status_out_of_date(self):
490
"""Simulate status of out-of-date tree after remote push"""
491
tree = self.make_branch_and_tree('.')
492
self.build_tree_contents([('a', 'foo\n')])
496
tree.commit('add test file')
497
# simulate what happens after a remote push
498
tree.set_last_revision("0")
500
# before run another commands we should unlock tree
502
out, err = self.run_bzr('status')
503
self.assertEqual("working tree is out of date, run 'brz update'\n",
506
def test_status_on_ignored(self):
507
"""Tests branch status on an unversioned file which is considered ignored.
509
See https://bugs.launchpad.net/bzr/+bug/40103
511
tree = self.make_branch_and_tree('.')
513
self.build_tree(['test1.c', 'test1.c~', 'test2.c~'])
514
result = self.run_bzr('status')[0]
515
self.assertContainsRe(result, "unknown:\n test1.c\n")
516
short_result = self.run_bzr('status --short')[0]
517
self.assertContainsRe(short_result, "\? test1.c\n")
519
result = self.run_bzr('status test1.c')[0]
520
self.assertContainsRe(result, "unknown:\n test1.c\n")
521
short_result = self.run_bzr('status --short test1.c')[0]
522
self.assertContainsRe(short_result, "\? test1.c\n")
524
result = self.run_bzr('status test1.c~')[0]
525
self.assertContainsRe(result, "ignored:\n test1.c~\n")
526
short_result = self.run_bzr('status --short test1.c~')[0]
527
self.assertContainsRe(short_result, "I test1.c~\n")
529
result = self.run_bzr('status test1.c~ test2.c~')[0]
530
self.assertContainsRe(result, "ignored:\n test1.c~\n test2.c~\n")
531
short_result = self.run_bzr('status --short test1.c~ test2.c~')[0]
532
self.assertContainsRe(short_result, "I test1.c~\nI test2.c~\n")
534
result = self.run_bzr('status test1.c test1.c~ test2.c~')[0]
535
self.assertContainsRe(result, "unknown:\n test1.c\nignored:\n test1.c~\n test2.c~\n")
536
short_result = self.run_bzr('status --short test1.c test1.c~ test2.c~')[0]
537
self.assertContainsRe(short_result, "\? test1.c\nI test1.c~\nI test2.c~\n")
539
def test_status_write_lock(self):
540
"""Test that status works without fetching history and
543
See https://bugs.launchpad.net/bzr/+bug/149270
546
wt = self.make_branch_and_tree('branch1')
548
wt.commit('Empty commit 1')
549
wt2 = b.bzrdir.sprout('branch2').open_workingtree()
550
wt2.commit('Empty commit 2')
551
out, err = self.run_bzr('status branch1 -rbranch:branch2')
552
self.assertEqual('', out)
554
def test_status_with_shelves(self):
555
"""Ensure that _show_shelve_summary handler works.
557
wt = self.make_branch_and_tree('.')
558
self.build_tree(['hello.c'])
560
self.run_bzr(['shelve', '--all', '-m', 'foo'])
561
self.build_tree(['bye.c'])
566
'1 shelf exists. See "brz shelve --list" for details.\n',
569
self.run_bzr(['shelve', '--all', '-m', 'bar'])
570
self.build_tree(['eggs.c', 'spam.c'])
577
'2 shelves exist. See "brz shelve --list" for details.\n',
585
specific_files=['spam.c'])
588
class CheckoutStatus(BranchStatus):
591
super(CheckoutStatus, self).setUp()
595
def make_branch_and_tree(self, relpath):
596
source = self.make_branch(pathjoin('..', relpath))
597
checkout = bzrdir.BzrDirMetaFormat1().initialize(relpath)
598
checkout.set_branch_reference(source)
599
return checkout.create_workingtree()
602
class TestStatus(TestCaseWithTransport):
604
def test_status_plain(self):
605
tree = self.make_branch_and_tree('.')
607
self.build_tree(['hello.txt'])
608
result = self.run_bzr("status")[0]
609
self.assertContainsRe(result, "unknown:\n hello.txt\n")
611
tree.add("hello.txt")
612
result = self.run_bzr("status")[0]
613
self.assertContainsRe(result, "added:\n hello.txt\n")
615
tree.commit(message="added")
616
result = self.run_bzr("status -r 0..1")[0]
617
self.assertContainsRe(result, "added:\n hello.txt\n")
619
result = self.run_bzr("status -c 1")[0]
620
self.assertContainsRe(result, "added:\n hello.txt\n")
622
self.build_tree(['world.txt'])
623
result = self.run_bzr("status -r 0")[0]
624
self.assertContainsRe(result, "added:\n hello.txt\n" \
625
"unknown:\n world.txt\n")
626
result2 = self.run_bzr("status -r 0..")[0]
627
self.assertEqual(result2, result)
629
def test_status_short(self):
630
tree = self.make_branch_and_tree('.')
632
self.build_tree(['hello.txt'])
633
result = self.run_bzr("status --short")[0]
634
self.assertContainsRe(result, "[?] hello.txt\n")
636
tree.add("hello.txt")
637
result = self.run_bzr("status --short")[0]
638
self.assertContainsRe(result, "[+]N hello.txt\n")
640
tree.commit(message="added")
641
result = self.run_bzr("status --short -r 0..1")[0]
642
self.assertContainsRe(result, "[+]N hello.txt\n")
644
self.build_tree(['world.txt'])
645
result = self.run_bzr("status -S -r 0")[0]
646
self.assertContainsRe(result, "[+]N hello.txt\n" \
648
result2 = self.run_bzr("status -S -r 0..")[0]
649
self.assertEqual(result2, result)
651
def test_status_versioned(self):
652
tree = self.make_branch_and_tree('.')
654
self.build_tree(['hello.txt'])
655
result = self.run_bzr("status --versioned")[0]
656
self.assertNotContainsRe(result, "unknown:\n hello.txt\n")
658
tree.add("hello.txt")
659
result = self.run_bzr("status --versioned")[0]
660
self.assertContainsRe(result, "added:\n hello.txt\n")
663
result = self.run_bzr("status --versioned -r 0..1")[0]
664
self.assertContainsRe(result, "added:\n hello.txt\n")
666
self.build_tree(['world.txt'])
667
result = self.run_bzr("status --versioned -r 0")[0]
668
self.assertContainsRe(result, "added:\n hello.txt\n")
669
self.assertNotContainsRe(result, "unknown:\n world.txt\n")
670
result2 = self.run_bzr("status --versioned -r 0..")[0]
671
self.assertEqual(result2, result)
673
def test_status_SV(self):
674
tree = self.make_branch_and_tree('.')
676
self.build_tree(['hello.txt'])
677
result = self.run_bzr("status -SV")[0]
678
self.assertNotContainsRe(result, "hello.txt")
680
tree.add("hello.txt")
681
result = self.run_bzr("status -SV")[0]
682
self.assertContainsRe(result, "[+]N hello.txt\n")
684
tree.commit(message="added")
685
result = self.run_bzr("status -SV -r 0..1")[0]
686
self.assertContainsRe(result, "[+]N hello.txt\n")
688
self.build_tree(['world.txt'])
689
result = self.run_bzr("status -SV -r 0")[0]
690
self.assertContainsRe(result, "[+]N hello.txt\n")
692
result2 = self.run_bzr("status -SV -r 0..")[0]
693
self.assertEqual(result2, result)
695
def assertStatusContains(self, pattern, short=False):
696
"""Run status, and assert it contains the given pattern"""
698
result = self.run_bzr("status --short")[0]
700
result = self.run_bzr("status")[0]
701
self.assertContainsRe(result, pattern)
703
def test_kind_change_plain(self):
704
tree = self.make_branch_and_tree('.')
705
self.build_tree(['file'])
707
tree.commit('added file')
709
self.build_tree(['file/'])
710
self.assertStatusContains('kind changed:\n file \(file => directory\)')
711
tree.rename_one('file', 'directory')
712
self.assertStatusContains('renamed:\n file/ => directory/\n' \
713
'modified:\n directory/\n')
715
self.assertStatusContains('removed:\n file\n')
717
def test_kind_change_short(self):
718
tree = self.make_branch_and_tree('.')
719
self.build_tree(['file'])
721
tree.commit('added file')
723
self.build_tree(['file/'])
724
self.assertStatusContains('K file => file/',
726
tree.rename_one('file', 'directory')
727
self.assertStatusContains('RK file => directory/',
730
self.assertStatusContains('RD file => directory',
733
def test_status_illegal_revision_specifiers(self):
734
out, err = self.run_bzr('status -r 1..23..123', retcode=3)
735
self.assertContainsRe(err, 'one or two revision specifiers')
737
def test_status_no_pending(self):
738
a_tree = self.make_branch_and_tree('a')
739
self.build_tree(['a/a'])
742
b_tree = a_tree.bzrdir.sprout('b').open_workingtree()
743
self.build_tree(['b/b'])
747
self.run_bzr('merge ../b', working_dir='a')
748
out, err = self.run_bzr('status --no-pending', working_dir='a')
749
self.assertEqual(out, "added:\n b\n")
751
def test_pending_specific_files(self):
752
"""With a specific file list, pending merges are not shown."""
753
tree = self.make_branch_and_tree('tree')
754
self.build_tree_contents([('tree/a', 'content of a\n')])
756
r1_id = tree.commit('one')
757
alt = tree.bzrdir.sprout('alt').open_workingtree()
758
self.build_tree_contents([('alt/a', 'content of a\nfrom alt\n')])
759
alt_id = alt.commit('alt')
760
tree.merge_from_branch(alt.branch)
761
output = self.make_utf8_encoded_stringio()
762
show_tree_status(tree, to_file=output)
763
self.assertContainsRe(output.getvalue(), 'pending merge')
764
out, err = self.run_bzr('status tree/a')
765
self.assertNotContainsRe(out, 'pending merge')
768
class TestStatusEncodings(TestCaseWithTransport):
770
def make_uncommitted_tree(self):
771
"""Build a branch with uncommitted unicode named changes in the cwd."""
772
working_tree = self.make_branch_and_tree(u'.')
773
filename = u'hell\u00d8'
775
self.build_tree_contents([(filename, 'contents of hello')])
776
except UnicodeEncodeError:
777
raise TestSkipped("can't build unicode working tree in "
778
"filesystem encoding %s" % sys.getfilesystemencoding())
779
working_tree.add(filename)
782
def test_stdout_ascii(self):
783
self.overrideAttr(osutils, '_cached_user_encoding', 'ascii')
784
working_tree = self.make_uncommitted_tree()
785
stdout, stderr = self.run_bzr("status")
787
self.assertEqual(stdout, """\
792
def test_stdout_latin1(self):
793
self.overrideAttr(osutils, '_cached_user_encoding', 'latin-1')
794
working_tree = self.make_uncommitted_tree()
795
stdout, stderr = self.run_bzr('status')
797
self.assertEqual(stdout, u"""\
800
""".encode('latin-1'))