1
# Copyright (C) 2005, 2006 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
17
17
"""Tests of status command.
19
19
Most of these depend on the particular formatting used.
20
As such they really are blackbox tests even though some of the
20
As such they really are blackbox tests even though some of the
21
21
tests are not using self.capture. If we add tests for the programmatic
22
22
interface later, they will be non blackbox tests.
25
25
from cStringIO import StringIO
27
from os import mkdir, chdir
27
from os import mkdir, chdir, rmdir, unlink
29
29
from tempfile import TemporaryFile
31
38
import bzrlib.branch
32
from bzrlib.builtins import merge
33
import bzrlib.bzrdir as bzrdir
34
import bzrlib.errors as errors
35
39
from bzrlib.osutils import pathjoin
36
40
from bzrlib.revisionspec import RevisionSpec
37
41
from bzrlib.status import show_tree_status
42
46
class BranchStatus(TestCaseWithTransport):
44
def test_branch_status(self):
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,
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, revision, short,
65
self.assertEqual(expected_lines, output_string.splitlines(True))
67
def status_string(self, wt, revision=None, short=False, pending=True,
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, to_file=tof, revision=revision, short=short,
73
show_pending=pending, verbose=verbose)
75
return tof.read().decode('utf-8')
77
def test_branch_status(self):
45
78
"""Test basic branch status"""
46
79
wt = self.make_branch_and_tree('.')
51
show_tree_status(wt, to_file=tof)
52
self.assertEquals(tof.getvalue(), "")
81
# status with no commits or files - it must
82
# work and show no output. We do this with no
83
# commits to be sure that it's not going to fail
85
self.assertStatus([], wt)
55
87
self.build_tree(['hello.c', 'bye.c'])
56
wt.add_pending_merge('pending@pending-0-0')
57
show_tree_status(wt, to_file=tof)
59
self.assertEquals(tof.readlines(),
64
' pending@pending-0-0\n'
100
# add a commit to allow showing pending merges.
101
wt.commit('create a parent to allow testing merge output')
103
wt.add_parent_tree_id('pending@pending-0-0')
108
'pending merge tips: (use -v to see all merge revisions)\n',
109
' (ghost) pending@pending-0-0\n',
117
' (ghost) pending@pending-0-0\n',
123
'P (ghost) pending@pending-0-0\n',
136
wt, short=True, pending=False)
67
138
def test_branch_status_revisions(self):
68
139
"""Tests branch status with revisions"""
69
140
wt = self.make_branch_and_tree('.')
73
142
self.build_tree(['hello.c', 'bye.c'])
76
145
wt.commit('Test message')
80
revs.append(RevisionSpec(0))
82
show_tree_status(wt, to_file=tof, revision=revs)
85
self.assertEquals(tof.readlines(),
147
revs = [RevisionSpec.from_string('0')]
90
156
self.build_tree(['more.c'])
92
158
wt.commit('Another test message')
95
revs.append(RevisionSpec(1))
97
show_tree_status(wt, to_file=tof, revision=revs)
100
self.assertEquals(tof.readlines(),
105
def status_string(self, wt):
106
# use a real file rather than StringIO because it doesn't handle
108
tof = codecs.getwriter('utf-8')(TemporaryFile())
109
show_tree_status(wt, to_file=tof)
111
return tof.read().decode('utf-8')
160
revs.append(RevisionSpec.from_string('1'))
113
169
def test_pending(self):
114
170
"""Pending merges display works, including Unicode"""
120
176
b_2 = b_2_dir.open_branch()
121
177
wt2 = b_2_dir.open_workingtree()
122
178
wt.commit(u"\N{TIBETAN DIGIT TWO} Empty commit 2")
123
merge(["./branch", -1], [None, None], this_dir = './copy')
124
message = self.status_string(wt2)
125
self.assert_(message.startswith("pending merges:\n"))
126
self.assert_(message.endswith("Empty commit 2\n"))
179
wt2.merge_from_branch(wt.branch)
180
message = self.status_string(wt2, verbose=True)
181
self.assertStartsWith(message, "pending merges:\n")
182
self.assertEndsWith(message, "Empty commit 2\n")
127
183
wt2.commit("merged")
128
184
# must be long to make sure we see elipsis at the end
129
wt.commit("Empty commit 3 " +
130
"blah blah blah blah " * 10)
131
merge(["./branch", -1], [None, None], this_dir = './copy')
132
message = self.status_string(wt2)
133
self.assert_(message.startswith("pending merges:\n"))
185
wt.commit("Empty commit 3 " +
186
"blah blah blah blah " * 100)
187
wt2.merge_from_branch(wt.branch)
188
message = self.status_string(wt2, verbose=True)
189
self.assertStartsWith(message, "pending merges:\n")
134
190
self.assert_("Empty commit 3" in message)
135
self.assert_(message.endswith("...\n"))
137
def test_branch_status_specific_files(self):
191
self.assertEndsWith(message, "...\n")
193
def test_tree_status_ignores(self):
194
"""Tests branch status with ignores"""
195
wt = self.make_branch_and_tree('.')
196
self.run_bzr('ignore *~')
197
wt.commit('commit .bzrignore')
198
self.build_tree(['foo.c', 'foo.c~'])
209
def test_tree_status_specific_files(self):
138
210
"""Tests branch status with given specific files"""
139
211
wt = self.make_branch_and_tree('.')
167
245
' directory/hello.c\n'
248
show_tree_status(wt, specific_files=['directory'], to_file=tof,
251
self.assertEquals(tof.readlines(), ['? directory/hello.c\n'])
170
254
show_tree_status(wt, specific_files=['dir2'], to_file=tof)
172
256
self.assertEquals(tof.readlines(),
261
show_tree_status(wt, specific_files=['dir2'], to_file=tof, short=True)
263
self.assertEquals(tof.readlines(), ['? dir2/\n'])
266
revs = [RevisionSpec.from_string('0'), RevisionSpec.from_string('1')]
267
show_tree_status(wt, specific_files=['test.c'], to_file=tof,
268
short=True, revision=revs)
270
self.assertEquals(tof.readlines(), ['+N test.c\n'])
272
def test_specific_files_conflicts(self):
273
tree = self.make_branch_and_tree('.')
274
self.build_tree(['dir2/'])
276
tree.commit('added dir2')
277
tree.set_conflicts(conflicts.ConflictList(
278
[conflicts.ContentsConflict('foo')]))
280
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
281
self.assertEqualDiff('', tof.getvalue())
282
tree.set_conflicts(conflicts.ConflictList(
283
[conflicts.ContentsConflict('dir2')]))
285
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
286
self.assertEqualDiff('conflicts:\n Contents conflict in dir2\n',
289
tree.set_conflicts(conflicts.ConflictList(
290
[conflicts.ContentsConflict('dir2/file1')]))
292
show_tree_status(tree, specific_files=['dir2'], to_file=tof)
293
self.assertEqualDiff('conflicts:\n Contents conflict in dir2/file1\n',
296
def _prepare_nonexistent(self):
297
wt = self.make_branch_and_tree('.')
298
self.assertStatus([], wt)
299
self.build_tree(['FILE_A', 'FILE_B', 'FILE_C', 'FILE_D', 'FILE_E', ])
305
wt.commit('Create five empty files.')
306
open('FILE_B', 'w').write('Modification to file FILE_B.')
307
open('FILE_C', 'w').write('Modification to file FILE_C.')
308
unlink('FILE_E') # FILE_E will be versioned but missing
309
open('FILE_Q', 'w').write('FILE_Q is added but not committed.')
310
wt.add('FILE_Q') # FILE_Q will be added but not committed
311
open('UNVERSIONED_BUT_EXISTING', 'w')
177
314
def test_status_nonexistent_file(self):
178
315
# files that don't exist in either the basis tree or working tree
179
316
# should give an error
317
wt = self._prepare_nonexistent()
327
' UNVERSIONED_BUT_EXISTING\n',
335
'? UNVERSIONED_BUT_EXISTING\n',
339
# Okay, everything's looking good with the existent files.
340
# Let's see what happens when we throw in non-existent files.
342
# bzr st [--short] NONEXISTENT '
347
out, err = self.run_bzr('status NONEXISTENT', retcode=3)
348
self.assertEqual(expected, out.splitlines(True))
349
self.assertContainsRe(err,
350
r'.*ERROR: Path\(s\) do not exist: '
355
out, err = self.run_bzr('status --short NONEXISTENT', retcode=3)
356
self.assertContainsRe(err,
357
r'.*ERROR: Path\(s\) do not exist: '
360
def test_status_nonexistent_file_with_others(self):
361
# bzr st [--short] NONEXISTENT ...others..
362
wt = self._prepare_nonexistent()
372
out, err = self.run_bzr('status NONEXISTENT '
373
'FILE_A FILE_B FILE_C FILE_D FILE_E',
375
self.assertEqual(expected, out.splitlines(True))
376
self.assertContainsRe(err,
377
r'.*ERROR: Path\(s\) do not exist: '
385
out, err = self.run_bzr('status --short NONEXISTENT '
386
'FILE_A FILE_B FILE_C FILE_D FILE_E',
388
self.assertEqual(expected, out.splitlines(True))
389
self.assertContainsRe(err,
390
r'.*ERROR: Path\(s\) do not exist: '
393
def test_status_multiple_nonexistent_files(self):
394
# bzr st [--short] NONEXISTENT ... ANOTHER_NONEXISTENT ...
395
wt = self._prepare_nonexistent()
403
' ANOTHER_NONEXISTENT\n',
406
out, err = self.run_bzr('status NONEXISTENT '
407
'FILE_A FILE_B ANOTHER_NONEXISTENT '
408
'FILE_C FILE_D FILE_E', retcode=3)
409
self.assertEqual(expected, out.splitlines(True))
410
self.assertContainsRe(err,
411
r'.*ERROR: Path\(s\) do not exist: '
412
'ANOTHER_NONEXISTENT NONEXISTENT.*')
417
'X ANOTHER_NONEXISTENT\n',
420
out, err = self.run_bzr('status --short NONEXISTENT '
421
'FILE_A FILE_B ANOTHER_NONEXISTENT '
422
'FILE_C FILE_D FILE_E', retcode=3)
423
self.assertEqual(expected, out.splitlines(True))
424
self.assertContainsRe(err,
425
r'.*ERROR: Path\(s\) do not exist: '
426
'ANOTHER_NONEXISTENT NONEXISTENT.*')
428
def test_status_nonexistent_file_with_unversioned(self):
429
# bzr st [--short] NONEXISTENT A B UNVERSIONED_BUT_EXISTING C D E Q
430
wt = self._prepare_nonexistent()
440
' UNVERSIONED_BUT_EXISTING\n',
444
out, err = self.run_bzr('status NONEXISTENT '
445
'FILE_A FILE_B UNVERSIONED_BUT_EXISTING '
446
'FILE_C FILE_D FILE_E FILE_Q', retcode=3)
447
self.assertEqual(expected, out.splitlines(True))
448
self.assertContainsRe(err,
449
r'.*ERROR: Path\(s\) do not exist: '
453
'? UNVERSIONED_BUT_EXISTING\n',
459
out, err = self.run_bzr('status --short NONEXISTENT '
460
'FILE_A FILE_B UNVERSIONED_BUT_EXISTING '
461
'FILE_C FILE_D FILE_E FILE_Q', retcode=3)
462
self.assertEqual(expected, out.splitlines(True))
463
self.assertContainsRe(err,
464
r'.*ERROR: Path\(s\) do not exist: '
467
def test_status_out_of_date(self):
468
"""Simulate status of out-of-date tree after remote push"""
469
tree = self.make_branch_and_tree('.')
470
self.build_tree_contents([('a', 'foo\n')])
474
tree.commit('add test file')
475
# simulate what happens after a remote push
476
tree.set_last_revision("0")
478
# before run another commands we should unlock tree
480
out, err = self.run_bzr('status')
481
self.assertEqual("working tree is out of date, run 'bzr update'\n",
484
def test_status_on_ignored(self):
485
"""Tests branch status on an unversioned file which is considered ignored.
487
See https://bugs.launchpad.net/bzr/+bug/40103
489
tree = self.make_branch_and_tree('.')
491
self.build_tree(['test1.c', 'test1.c~', 'test2.c~'])
492
result = self.run_bzr('status')[0]
493
self.assertContainsRe(result, "unknown:\n test1.c\n")
494
short_result = self.run_bzr('status --short')[0]
495
self.assertContainsRe(short_result, "\? test1.c\n")
497
result = self.run_bzr('status test1.c')[0]
498
self.assertContainsRe(result, "unknown:\n test1.c\n")
499
short_result = self.run_bzr('status --short test1.c')[0]
500
self.assertContainsRe(short_result, "\? test1.c\n")
502
result = self.run_bzr('status test1.c~')[0]
503
self.assertContainsRe(result, "ignored:\n test1.c~\n")
504
short_result = self.run_bzr('status --short test1.c~')[0]
505
self.assertContainsRe(short_result, "I test1.c~\n")
507
result = self.run_bzr('status test1.c~ test2.c~')[0]
508
self.assertContainsRe(result, "ignored:\n test1.c~\n test2.c~\n")
509
short_result = self.run_bzr('status --short test1.c~ test2.c~')[0]
510
self.assertContainsRe(short_result, "I test1.c~\nI test2.c~\n")
512
result = self.run_bzr('status test1.c test1.c~ test2.c~')[0]
513
self.assertContainsRe(result, "unknown:\n test1.c\nignored:\n test1.c~\n test2.c~\n")
514
short_result = self.run_bzr('status --short test1.c test1.c~ test2.c~')[0]
515
self.assertContainsRe(short_result, "\? test1.c\nI test1.c~\nI test2.c~\n")
517
def test_status_write_lock(self):
518
"""Test that status works without fetching history and
521
See https://bugs.launchpad.net/bzr/+bug/149270
524
wt = self.make_branch_and_tree('branch1')
526
wt.commit('Empty commit 1')
527
wt2 = b.bzrdir.sprout('branch2').open_workingtree()
528
wt2.commit('Empty commit 2')
529
out, err = self.run_bzr('status branch1 -rbranch:branch2')
530
self.assertEqual('', out)
532
def test_status_with_shelves(self):
533
"""Ensure that _show_shelve_summary handler works.
180
535
wt = self.make_branch_and_tree('.')
181
out, err = self.run_bzr('status', 'does-not-exist', retcode=3)
182
self.assertContainsRe(err, r'do not exist.*does-not-exist')
536
self.build_tree(['hello.c'])
538
self.run_bzr(['shelve', '--all', '-m', 'foo'])
539
self.build_tree(['bye.c'])
544
'1 shelves exist. See "bzr shelve --list" for details.\n',
185
549
class CheckoutStatus(BranchStatus):
188
552
super(CheckoutStatus, self).setUp()
192
556
def make_branch_and_tree(self, relpath):
193
557
source = self.make_branch(pathjoin('..', relpath))
194
558
checkout = bzrdir.BzrDirMetaFormat1().initialize(relpath)
195
bzrlib.branch.BranchReferenceFormat().initialize(checkout, source)
559
bzrlib.branch.BranchReferenceFormat().initialize(checkout,
560
target_branch=source)
196
561
return checkout.create_workingtree()
199
564
class TestStatus(TestCaseWithTransport):
201
def test_status(self):
203
self.build_tree(['hello.txt'])
204
result = self.run_bzr("status")[0]
205
self.assert_("unknown:\n hello.txt\n" in result, result)
206
self.run_bzr("add", "hello.txt")
207
result = self.run_bzr("status")[0]
208
self.assert_("added:\n hello.txt\n" in result, result)
209
self.run_bzr("commit", "-m", "added")
210
result = self.run_bzr("status", "-r", "0..1")[0]
211
self.assert_("added:\n hello.txt\n" in result, result)
212
self.build_tree(['world.txt'])
213
result = self.run_bzr("status", "-r", "0")[0]
214
self.assert_("added:\n hello.txt\n" \
215
"unknown:\n world.txt\n" in result, result)
217
result2 = self.run_bzr("status", "-r", "0..")[0]
218
self.assertEquals(result2, result)
566
def test_status_plain(self):
567
tree = self.make_branch_and_tree('.')
569
self.build_tree(['hello.txt'])
570
result = self.run_bzr("status")[0]
571
self.assertContainsRe(result, "unknown:\n hello.txt\n")
573
tree.add("hello.txt")
574
result = self.run_bzr("status")[0]
575
self.assertContainsRe(result, "added:\n hello.txt\n")
577
tree.commit(message="added")
578
result = self.run_bzr("status -r 0..1")[0]
579
self.assertContainsRe(result, "added:\n hello.txt\n")
581
result = self.run_bzr("status -c 1")[0]
582
self.assertContainsRe(result, "added:\n hello.txt\n")
584
self.build_tree(['world.txt'])
585
result = self.run_bzr("status -r 0")[0]
586
self.assertContainsRe(result, "added:\n hello.txt\n" \
587
"unknown:\n world.txt\n")
588
result2 = self.run_bzr("status -r 0..")[0]
589
self.assertEquals(result2, result)
591
def test_status_short(self):
592
tree = self.make_branch_and_tree('.')
594
self.build_tree(['hello.txt'])
595
result = self.run_bzr("status --short")[0]
596
self.assertContainsRe(result, "[?] hello.txt\n")
598
tree.add("hello.txt")
599
result = self.run_bzr("status --short")[0]
600
self.assertContainsRe(result, "[+]N hello.txt\n")
602
tree.commit(message="added")
603
result = self.run_bzr("status --short -r 0..1")[0]
604
self.assertContainsRe(result, "[+]N hello.txt\n")
606
self.build_tree(['world.txt'])
607
result = self.run_bzr("status --short -r 0")[0]
608
self.assertContainsRe(result, "[+]N hello.txt\n" \
610
result2 = self.run_bzr("status --short -r 0..")[0]
611
self.assertEquals(result2, result)
613
def test_status_versioned(self):
614
tree = self.make_branch_and_tree('.')
616
self.build_tree(['hello.txt'])
617
result = self.run_bzr("status --versioned")[0]
618
self.assertNotContainsRe(result, "unknown:\n hello.txt\n")
620
tree.add("hello.txt")
621
result = self.run_bzr("status --versioned")[0]
622
self.assertContainsRe(result, "added:\n hello.txt\n")
625
result = self.run_bzr("status --versioned -r 0..1")[0]
626
self.assertContainsRe(result, "added:\n hello.txt\n")
628
self.build_tree(['world.txt'])
629
result = self.run_bzr("status --versioned -r 0")[0]
630
self.assertContainsRe(result, "added:\n hello.txt\n")
631
self.assertNotContainsRe(result, "unknown:\n world.txt\n")
632
result2 = self.run_bzr("status --versioned -r 0..")[0]
633
self.assertEquals(result2, result)
635
def test_status_SV(self):
636
tree = self.make_branch_and_tree('.')
638
self.build_tree(['hello.txt'])
639
result = self.run_bzr("status -SV")[0]
640
self.assertNotContainsRe(result, "hello.txt")
642
tree.add("hello.txt")
643
result = self.run_bzr("status -SV")[0]
644
self.assertContainsRe(result, "[+]N hello.txt\n")
646
tree.commit(message="added")
647
result = self.run_bzr("status -SV -r 0..1")[0]
648
self.assertContainsRe(result, "[+]N hello.txt\n")
650
self.build_tree(['world.txt'])
651
result = self.run_bzr("status -SV -r 0")[0]
652
self.assertContainsRe(result, "[+]N hello.txt\n")
654
result2 = self.run_bzr("status -SV -r 0..")[0]
655
self.assertEquals(result2, result)
657
def assertStatusContains(self, pattern, short=False):
658
"""Run status, and assert it contains the given pattern"""
660
result = self.run_bzr("status --short")[0]
662
result = self.run_bzr("status")[0]
663
self.assertContainsRe(result, pattern)
665
def test_kind_change_plain(self):
666
tree = self.make_branch_and_tree('.')
667
self.build_tree(['file'])
669
tree.commit('added file')
671
self.build_tree(['file/'])
672
self.assertStatusContains('kind changed:\n file \(file => directory\)')
673
tree.rename_one('file', 'directory')
674
self.assertStatusContains('renamed:\n file/ => directory/\n' \
675
'modified:\n directory/\n')
677
self.assertStatusContains('removed:\n file\n')
679
def test_kind_change_short(self):
680
tree = self.make_branch_and_tree('.')
681
self.build_tree(['file'])
683
tree.commit('added file')
685
self.build_tree(['file/'])
686
self.assertStatusContains('K file => file/',
688
tree.rename_one('file', 'directory')
689
self.assertStatusContains('RK file => directory/',
692
self.assertStatusContains('RD file => directory',
695
def test_status_illegal_revision_specifiers(self):
696
out, err = self.run_bzr('status -r 1..23..123', retcode=3)
697
self.assertContainsRe(err, 'one or two revision specifiers')
699
def test_status_no_pending(self):
700
a_tree = self.make_branch_and_tree('a')
701
self.build_tree(['a/a'])
704
b_tree = a_tree.bzrdir.sprout('b').open_workingtree()
705
self.build_tree(['b/b'])
709
self.run_bzr('merge ../b', working_dir='a')
710
out, err = self.run_bzr('status --no-pending', working_dir='a')
711
self.assertEquals(out, "added:\n b\n")
713
def test_pending_specific_files(self):
714
"""With a specific file list, pending merges are not shown."""
715
tree = self.make_branch_and_tree('tree')
716
self.build_tree_contents([('tree/a', 'content of a\n')])
718
r1_id = tree.commit('one')
719
alt = tree.bzrdir.sprout('alt').open_workingtree()
720
self.build_tree_contents([('alt/a', 'content of a\nfrom alt\n')])
721
alt_id = alt.commit('alt')
722
tree.merge_from_branch(alt.branch)
723
output = self.make_utf8_encoded_stringio()
724
show_tree_status(tree, to_file=output)
725
self.assertContainsRe(output.getvalue(), 'pending merge')
726
out, err = self.run_bzr('status tree/a')
727
self.assertNotContainsRe(out, 'pending merge')
221
730
class TestStatusEncodings(TestCaseWithTransport):
224
TestCaseWithTransport.setUp(self)
225
self.user_encoding = bzrlib.user_encoding
226
self.stdout = sys.stdout
229
bzrlib.user_encoding = self.user_encoding
230
sys.stdout = self.stdout
231
TestCaseWithTransport.tearDown(self)
233
732
def make_uncommitted_tree(self):
234
733
"""Build a branch with uncommitted unicode named changes in the cwd."""