1
# Copyright (C) 2005-2010 Canonical Ltd
1
# Copyright (C) 2005 by 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18
"""Tests of status command.
19
20
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.
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
checkout.set_branch_reference(source)
602
return checkout.create_workingtree()
605
class TestStatus(TestCaseWithTransport):
607
def test_status_plain(self):
608
tree = self.make_branch_and_tree('.')
610
self.build_tree(['hello.txt'])
611
result = self.run_bzr("status")[0]
612
self.assertContainsRe(result, "unknown:\n hello.txt\n")
614
tree.add("hello.txt")
615
result = self.run_bzr("status")[0]
616
self.assertContainsRe(result, "added:\n hello.txt\n")
618
tree.commit(message="added")
619
result = self.run_bzr("status -r 0..1")[0]
620
self.assertContainsRe(result, "added:\n hello.txt\n")
622
result = self.run_bzr("status -c 1")[0]
623
self.assertContainsRe(result, "added:\n hello.txt\n")
625
self.build_tree(['world.txt'])
626
result = self.run_bzr("status -r 0")[0]
627
self.assertContainsRe(result, "added:\n hello.txt\n" \
628
"unknown:\n world.txt\n")
629
result2 = self.run_bzr("status -r 0..")[0]
630
self.assertEquals(result2, result)
632
def test_status_short(self):
633
tree = self.make_branch_and_tree('.')
635
self.build_tree(['hello.txt'])
636
result = self.run_bzr("status --short")[0]
637
self.assertContainsRe(result, "[?] hello.txt\n")
639
tree.add("hello.txt")
640
result = self.run_bzr("status --short")[0]
641
self.assertContainsRe(result, "[+]N hello.txt\n")
643
tree.commit(message="added")
644
result = self.run_bzr("status --short -r 0..1")[0]
645
self.assertContainsRe(result, "[+]N hello.txt\n")
647
self.build_tree(['world.txt'])
648
result = self.run_bzr("status -S -r 0")[0]
649
self.assertContainsRe(result, "[+]N hello.txt\n" \
651
result2 = self.run_bzr("status -S -r 0..")[0]
652
self.assertEquals(result2, result)
654
def test_status_versioned(self):
655
tree = self.make_branch_and_tree('.')
657
self.build_tree(['hello.txt'])
658
result = self.run_bzr("status --versioned")[0]
659
self.assertNotContainsRe(result, "unknown:\n hello.txt\n")
661
tree.add("hello.txt")
662
result = self.run_bzr("status --versioned")[0]
663
self.assertContainsRe(result, "added:\n hello.txt\n")
666
result = self.run_bzr("status --versioned -r 0..1")[0]
667
self.assertContainsRe(result, "added:\n hello.txt\n")
669
self.build_tree(['world.txt'])
670
result = self.run_bzr("status --versioned -r 0")[0]
671
self.assertContainsRe(result, "added:\n hello.txt\n")
672
self.assertNotContainsRe(result, "unknown:\n world.txt\n")
673
result2 = self.run_bzr("status --versioned -r 0..")[0]
674
self.assertEquals(result2, result)
676
def test_status_SV(self):
677
tree = self.make_branch_and_tree('.')
679
self.build_tree(['hello.txt'])
680
result = self.run_bzr("status -SV")[0]
681
self.assertNotContainsRe(result, "hello.txt")
683
tree.add("hello.txt")
684
result = self.run_bzr("status -SV")[0]
685
self.assertContainsRe(result, "[+]N hello.txt\n")
687
tree.commit(message="added")
688
result = self.run_bzr("status -SV -r 0..1")[0]
689
self.assertContainsRe(result, "[+]N hello.txt\n")
691
self.build_tree(['world.txt'])
692
result = self.run_bzr("status -SV -r 0")[0]
693
self.assertContainsRe(result, "[+]N hello.txt\n")
695
result2 = self.run_bzr("status -SV -r 0..")[0]
696
self.assertEquals(result2, result)
698
def assertStatusContains(self, pattern, short=False):
699
"""Run status, and assert it contains the given pattern"""
701
result = self.run_bzr("status --short")[0]
703
result = self.run_bzr("status")[0]
704
self.assertContainsRe(result, pattern)
706
def test_kind_change_plain(self):
707
tree = self.make_branch_and_tree('.')
708
self.build_tree(['file'])
710
tree.commit('added file')
712
self.build_tree(['file/'])
713
self.assertStatusContains('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.bzrdir.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.assertEquals(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', 'content of a\n')])
759
r1_id = tree.commit('one')
760
alt = tree.bzrdir.sprout('alt').open_workingtree()
761
self.build_tree_contents([('alt/a', '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(), '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, '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.assertEquals(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')
800
self.assertEquals(stdout, u"""\
803
""".encode('latin-1'))
24
from bzrlib.selftest import TestCaseInTempDir
26
class BranchStatus(TestCaseInTempDir):
28
def test_branch_status(self):
29
"""Basic 'bzr mkdir' operation"""
30
from cStringIO import StringIO
31
from bzrlib.status import show_status
32
from bzrlib.branch import Branch
34
b = Branch('.', init=True)
38
show_status(b, to_file=tof)
39
self.assertEquals(tof.getvalue(), "")
42
self.build_tree(['hello.c', 'bye.c'])
43
show_status(b, to_file=tof)
45
self.assertEquals(tof.readlines(),