1
# Copyright (C) 2005 by Canonical Ltd
1
# Copyright (C) 2005-2010 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
17
"""Tests of status command.
20
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.
24
from bzrlib.selftest import InTempDir
26
class BranchStatus(InTempDir):
28
"""Basic 'bzr mkdir' operation"""
29
from cStringIO import StringIO
30
from bzrlib.status import show_status
31
from bzrlib.branch import Branch
33
b = Branch('.', init=True)
37
show_status(b, to_file=tof)
38
self.assertEquals(tof.getvalue(), "")
41
self.build_tree(['hello.c', 'bye.c'])
42
show_status(b, to_file=tof)
44
self.assertEquals(tof.readlines(),
25
from cStringIO import StringIO
27
from os import mkdir, chdir, rmdir, unlink
29
from tempfile import TemporaryFile
39
from bzrlib.osutils import pathjoin
40
from bzrlib.revisionspec import RevisionSpec
41
from bzrlib.status import show_tree_status
42
from bzrlib.tests import TestCaseWithTransport, TestSkipped
43
from bzrlib.workingtree import WorkingTree
46
class BranchStatus(TestCaseWithTransport):
49
super(BranchStatus, self).setUp()
50
# As TestCase.setUp clears all hooks, we install this default
51
# post_status hook handler for the test.
52
status.hooks.install_named_hook('post_status',
53
status._show_shelve_summary,
56
def assertStatus(self, expected_lines, working_tree, specific_files=None,
57
revision=None, short=False, pending=True, verbose=False):
58
"""Run status in working_tree and look for output.
60
:param expected_lines: The lines to look for.
61
:param working_tree: The tree to run status in.
63
output_string = self.status_string(working_tree, specific_files, revision, short,
65
self.assertEqual(expected_lines, output_string.splitlines(True))
67
def status_string(self, wt, specific_files=None, revision=None,
68
short=False, pending=True, verbose=False):
69
# use a real file rather than StringIO because it doesn't handle
71
tof = codecs.getwriter('utf-8')(TemporaryFile())
72
show_tree_status(wt, specific_files=specific_files, to_file=tof,
73
revision=revision, short=short, show_pending=pending,
76
return tof.read().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.bzrdir.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.assert_("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.assertEquals(tof.readlines(),
253
' directory/hello.c\n'
256
show_tree_status(wt, specific_files=['directory'], to_file=tof,
259
self.assertEquals(tof.readlines(), ['? directory/hello.c\n'])
262
show_tree_status(wt, specific_files=['dir2'], to_file=tof)
264
self.assertEquals(tof.readlines(),
269
show_tree_status(wt, specific_files=['dir2'], to_file=tof, short=True)
271
self.assertEquals(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.assertEquals(tof.readlines(), ['+N test.c\n'])
281
show_tree_status(wt, specific_files=['missing.c'], to_file=tof)
283
self.assertEquals(tof.readlines(),
288
show_tree_status(wt, specific_files=['missing.c'], to_file=tof,
291
self.assertEquals(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
# bzr 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
# bzr 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
# bzr 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
# bzr 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',
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', 'foo\n')])
499
tree.commit('add test file')
500
# simulate what happens after a remote push
501
tree.set_last_revision("0")
503
# before run another commands we should unlock tree
505
out, err = self.run_bzr('status')
506
self.assertEqual("working tree is out of date, run 'bzr update'\n",
509
def test_status_on_ignored(self):
510
"""Tests branch status on an unversioned file which is considered ignored.
512
See https://bugs.launchpad.net/bzr/+bug/40103
514
tree = self.make_branch_and_tree('.')
516
self.build_tree(['test1.c', 'test1.c~', 'test2.c~'])
517
result = self.run_bzr('status')[0]
518
self.assertContainsRe(result, "unknown:\n test1.c\n")
519
short_result = self.run_bzr('status --short')[0]
520
self.assertContainsRe(short_result, "\? test1.c\n")
522
result = self.run_bzr('status test1.c')[0]
523
self.assertContainsRe(result, "unknown:\n test1.c\n")
524
short_result = self.run_bzr('status --short test1.c')[0]
525
self.assertContainsRe(short_result, "\? test1.c\n")
527
result = self.run_bzr('status test1.c~')[0]
528
self.assertContainsRe(result, "ignored:\n test1.c~\n")
529
short_result = self.run_bzr('status --short test1.c~')[0]
530
self.assertContainsRe(short_result, "I test1.c~\n")
532
result = self.run_bzr('status test1.c~ test2.c~')[0]
533
self.assertContainsRe(result, "ignored:\n test1.c~\n test2.c~\n")
534
short_result = self.run_bzr('status --short test1.c~ test2.c~')[0]
535
self.assertContainsRe(short_result, "I test1.c~\nI test2.c~\n")
537
result = self.run_bzr('status test1.c test1.c~ test2.c~')[0]
538
self.assertContainsRe(result, "unknown:\n test1.c\nignored:\n test1.c~\n test2.c~\n")
539
short_result = self.run_bzr('status --short test1.c test1.c~ test2.c~')[0]
540
self.assertContainsRe(short_result, "\? test1.c\nI test1.c~\nI test2.c~\n")
542
def test_status_write_lock(self):
543
"""Test that status works without fetching history and
546
See https://bugs.launchpad.net/bzr/+bug/149270
549
wt = self.make_branch_and_tree('branch1')
551
wt.commit('Empty commit 1')
552
wt2 = b.bzrdir.sprout('branch2').open_workingtree()
553
wt2.commit('Empty commit 2')
554
out, err = self.run_bzr('status branch1 -rbranch:branch2')
555
self.assertEqual('', out)
557
def test_status_with_shelves(self):
558
"""Ensure that _show_shelve_summary handler works.
560
wt = self.make_branch_and_tree('.')
561
self.build_tree(['hello.c'])
563
self.run_bzr(['shelve', '--all', '-m', 'foo'])
564
self.build_tree(['bye.c'])
569
'1 shelf exists. See "bzr shelve --list" for details.\n',
572
self.run_bzr(['shelve', '--all', '-m', 'bar'])
573
self.build_tree(['eggs.c', 'spam.c'])
580
'2 shelves exist. See "bzr shelve --list" for details.\n',
588
specific_files=['spam.c'])
591
class CheckoutStatus(BranchStatus):
594
super(CheckoutStatus, self).setUp()
598
def make_branch_and_tree(self, relpath):
599
source = self.make_branch(pathjoin('..', relpath))
600
checkout = bzrdir.BzrDirMetaFormat1().initialize(relpath)
601
bzrlib.branch.BranchReferenceFormat().initialize(checkout,
602
target_branch=source)
603
return checkout.create_workingtree()
606
class TestStatus(TestCaseWithTransport):
608
def test_status_plain(self):
609
tree = self.make_branch_and_tree('.')
611
self.build_tree(['hello.txt'])
612
result = self.run_bzr("status")[0]
613
self.assertContainsRe(result, "unknown:\n hello.txt\n")
615
tree.add("hello.txt")
616
result = self.run_bzr("status")[0]
617
self.assertContainsRe(result, "added:\n hello.txt\n")
619
tree.commit(message="added")
620
result = self.run_bzr("status -r 0..1")[0]
621
self.assertContainsRe(result, "added:\n hello.txt\n")
623
result = self.run_bzr("status -c 1")[0]
624
self.assertContainsRe(result, "added:\n hello.txt\n")
626
self.build_tree(['world.txt'])
627
result = self.run_bzr("status -r 0")[0]
628
self.assertContainsRe(result, "added:\n hello.txt\n" \
629
"unknown:\n world.txt\n")
630
result2 = self.run_bzr("status -r 0..")[0]
631
self.assertEquals(result2, result)
633
def test_status_short(self):
634
tree = self.make_branch_and_tree('.')
636
self.build_tree(['hello.txt'])
637
result = self.run_bzr("status --short")[0]
638
self.assertContainsRe(result, "[?] hello.txt\n")
640
tree.add("hello.txt")
641
result = self.run_bzr("status --short")[0]
642
self.assertContainsRe(result, "[+]N hello.txt\n")
644
tree.commit(message="added")
645
result = self.run_bzr("status --short -r 0..1")[0]
646
self.assertContainsRe(result, "[+]N hello.txt\n")
648
self.build_tree(['world.txt'])
649
result = self.run_bzr("status -S -r 0")[0]
650
self.assertContainsRe(result, "[+]N hello.txt\n" \
652
result2 = self.run_bzr("status -S -r 0..")[0]
653
self.assertEquals(result2, result)
655
def test_status_versioned(self):
656
tree = self.make_branch_and_tree('.')
658
self.build_tree(['hello.txt'])
659
result = self.run_bzr("status --versioned")[0]
660
self.assertNotContainsRe(result, "unknown:\n hello.txt\n")
662
tree.add("hello.txt")
663
result = self.run_bzr("status --versioned")[0]
664
self.assertContainsRe(result, "added:\n hello.txt\n")
667
result = self.run_bzr("status --versioned -r 0..1")[0]
668
self.assertContainsRe(result, "added:\n hello.txt\n")
670
self.build_tree(['world.txt'])
671
result = self.run_bzr("status --versioned -r 0")[0]
672
self.assertContainsRe(result, "added:\n hello.txt\n")
673
self.assertNotContainsRe(result, "unknown:\n world.txt\n")
674
result2 = self.run_bzr("status --versioned -r 0..")[0]
675
self.assertEquals(result2, result)
677
def test_status_SV(self):
678
tree = self.make_branch_and_tree('.')
680
self.build_tree(['hello.txt'])
681
result = self.run_bzr("status -SV")[0]
682
self.assertNotContainsRe(result, "hello.txt")
684
tree.add("hello.txt")
685
result = self.run_bzr("status -SV")[0]
686
self.assertContainsRe(result, "[+]N hello.txt\n")
688
tree.commit(message="added")
689
result = self.run_bzr("status -SV -r 0..1")[0]
690
self.assertContainsRe(result, "[+]N hello.txt\n")
692
self.build_tree(['world.txt'])
693
result = self.run_bzr("status -SV -r 0")[0]
694
self.assertContainsRe(result, "[+]N hello.txt\n")
696
result2 = self.run_bzr("status -SV -r 0..")[0]
697
self.assertEquals(result2, result)
699
def assertStatusContains(self, pattern, short=False):
700
"""Run status, and assert it contains the given pattern"""
702
result = self.run_bzr("status --short")[0]
704
result = self.run_bzr("status")[0]
705
self.assertContainsRe(result, pattern)
707
def test_kind_change_plain(self):
708
tree = self.make_branch_and_tree('.')
709
self.build_tree(['file'])
711
tree.commit('added file')
713
self.build_tree(['file/'])
714
self.assertStatusContains('kind changed:\n file \(file => directory\)')
715
tree.rename_one('file', 'directory')
716
self.assertStatusContains('renamed:\n file/ => directory/\n' \
717
'modified:\n directory/\n')
719
self.assertStatusContains('removed:\n file\n')
721
def test_kind_change_short(self):
722
tree = self.make_branch_and_tree('.')
723
self.build_tree(['file'])
725
tree.commit('added file')
727
self.build_tree(['file/'])
728
self.assertStatusContains('K file => file/',
730
tree.rename_one('file', 'directory')
731
self.assertStatusContains('RK file => directory/',
734
self.assertStatusContains('RD file => directory',
737
def test_status_illegal_revision_specifiers(self):
738
out, err = self.run_bzr('status -r 1..23..123', retcode=3)
739
self.assertContainsRe(err, 'one or two revision specifiers')
741
def test_status_no_pending(self):
742
a_tree = self.make_branch_and_tree('a')
743
self.build_tree(['a/a'])
746
b_tree = a_tree.bzrdir.sprout('b').open_workingtree()
747
self.build_tree(['b/b'])
751
self.run_bzr('merge ../b', working_dir='a')
752
out, err = self.run_bzr('status --no-pending', working_dir='a')
753
self.assertEquals(out, "added:\n b\n")
755
def test_pending_specific_files(self):
756
"""With a specific file list, pending merges are not shown."""
757
tree = self.make_branch_and_tree('tree')
758
self.build_tree_contents([('tree/a', 'content of a\n')])
760
r1_id = tree.commit('one')
761
alt = tree.bzrdir.sprout('alt').open_workingtree()
762
self.build_tree_contents([('alt/a', 'content of a\nfrom alt\n')])
763
alt_id = alt.commit('alt')
764
tree.merge_from_branch(alt.branch)
765
output = self.make_utf8_encoded_stringio()
766
show_tree_status(tree, to_file=output)
767
self.assertContainsRe(output.getvalue(), 'pending merge')
768
out, err = self.run_bzr('status tree/a')
769
self.assertNotContainsRe(out, 'pending merge')
772
class TestStatusEncodings(TestCaseWithTransport):
774
def make_uncommitted_tree(self):
775
"""Build a branch with uncommitted unicode named changes in the cwd."""
776
working_tree = self.make_branch_and_tree(u'.')
777
filename = u'hell\u00d8'
779
self.build_tree_contents([(filename, 'contents of hello')])
780
except UnicodeEncodeError:
781
raise TestSkipped("can't build unicode working tree in "
782
"filesystem encoding %s" % sys.getfilesystemencoding())
783
working_tree.add(filename)
786
def test_stdout_ascii(self):
787
self.overrideAttr(osutils, '_cached_user_encoding', 'ascii')
788
working_tree = self.make_uncommitted_tree()
789
stdout, stderr = self.run_bzr("status")
791
self.assertEquals(stdout, """\
796
def test_stdout_latin1(self):
797
self.overrideAttr(osutils, '_cached_user_encoding', 'latin-1')
798
working_tree = self.make_uncommitted_tree()
799
stdout, stderr = self.run_bzr('status')
801
self.assertEquals(stdout, u"""\
804
""".encode('latin-1'))