1
# Copyright (C) 2005, 2006 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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.
 
 
25
from cStringIO import StringIO
 
 
27
from os import mkdir, chdir, rmdir, unlink
 
 
29
from tempfile import TemporaryFile
 
 
37
from bzrlib.osutils import pathjoin
 
 
38
from bzrlib.revisionspec import RevisionSpec
 
 
39
from bzrlib.status import show_tree_status
 
 
40
from bzrlib.tests import TestCaseWithTransport, TestSkipped
 
 
41
from bzrlib.workingtree import WorkingTree
 
 
44
class BranchStatus(TestCaseWithTransport):
 
 
46
    def assertStatus(self, expected_lines, working_tree,
 
 
47
        revision=None, short=False):
 
 
48
        """Run status in working_tree and look for output.
 
 
50
        :param expected_lines: The lines to look for.
 
 
51
        :param working_tree: The tree to run status in.
 
 
53
        output_string = self.status_string(working_tree, revision, short)
 
 
54
        self.assertEqual(expected_lines, output_string.splitlines(True))
 
 
56
    def status_string(self, wt, revision=None, short=False):
 
 
57
        # use a real file rather than StringIO because it doesn't handle
 
 
59
        tof = codecs.getwriter('utf-8')(TemporaryFile())
 
 
60
        show_tree_status(wt, to_file=tof, revision=revision, short=short)
 
 
62
        return tof.read().decode('utf-8')
 
 
64
    def test_branch_status(self):
 
 
65
        """Test basic branch status"""
 
 
66
        wt = self.make_branch_and_tree('.')
 
 
68
        # status with no commits or files - it must
 
 
69
        # work and show no output. We do this with no
 
 
70
        # commits to be sure that it's not going to fail
 
 
72
        self.assertStatus([], wt)
 
 
74
        self.build_tree(['hello.c', 'bye.c'])
 
 
87
        # add a commit to allow showing pending merges.
 
 
88
        wt.commit('create a parent to allow testing merge output')
 
 
90
        wt.add_parent_tree_id('pending@pending-0-0')
 
 
96
                '  pending@pending-0-0\n',
 
 
102
                'P   pending@pending-0-0\n',
 
 
106
    def test_branch_status_revisions(self):
 
 
107
        """Tests branch status with revisions"""
 
 
108
        wt = self.make_branch_and_tree('.')
 
 
110
        self.build_tree(['hello.c', 'bye.c'])
 
 
113
        wt.commit('Test message')
 
 
115
        revs = [RevisionSpec.from_string('0')]
 
 
124
        self.build_tree(['more.c'])
 
 
126
        wt.commit('Another test message')
 
 
128
        revs.append(RevisionSpec.from_string('1'))
 
 
137
    def test_pending(self):
 
 
138
        """Pending merges display works, including Unicode"""
 
 
140
        wt = self.make_branch_and_tree('branch')
 
 
142
        wt.commit("Empty commit 1")
 
 
143
        b_2_dir = b.bzrdir.sprout('./copy')
 
 
144
        b_2 = b_2_dir.open_branch()
 
 
145
        wt2 = b_2_dir.open_workingtree()
 
 
146
        wt.commit(u"\N{TIBETAN DIGIT TWO} Empty commit 2")
 
 
147
        wt2.merge_from_branch(wt.branch)
 
 
148
        message = self.status_string(wt2)
 
 
149
        self.assertStartsWith(message, "pending merges:\n")
 
 
150
        self.assertEndsWith(message, "Empty commit 2\n")
 
 
152
        # must be long to make sure we see elipsis at the end
 
 
153
        wt.commit("Empty commit 3 " +
 
 
154
                   "blah blah blah blah " * 100)
 
 
155
        wt2.merge_from_branch(wt.branch)
 
 
156
        message = self.status_string(wt2)
 
 
157
        self.assertStartsWith(message, "pending merges:\n")
 
 
158
        self.assert_("Empty commit 3" in message)
 
 
159
        self.assertEndsWith(message, "...\n")
 
 
161
    def test_tree_status_ignores(self):
 
 
162
        """Tests branch status with ignores"""
 
 
163
        wt = self.make_branch_and_tree('.')
 
 
164
        self.run_bzr('ignore *~')
 
 
165
        wt.commit('commit .bzrignore')
 
 
166
        self.build_tree(['foo.c', 'foo.c~'])
 
 
177
    def test_tree_status_specific_files(self):
 
 
178
        """Tests branch status with given specific files"""
 
 
179
        wt = self.make_branch_and_tree('.')
 
 
182
        self.build_tree(['directory/','directory/hello.c', 'bye.c','test.c','dir2/'])
 
 
191
                '  directory/hello.c\n'
 
 
198
                '?   directory/hello.c\n'
 
 
203
        self.assertRaises(errors.PathsDoNotExist,
 
 
205
                          wt, specific_files=['bye.c','test.c','absent.c'], 
 
 
209
        show_tree_status(wt, specific_files=['directory'], to_file=tof)
 
 
211
        self.assertEquals(tof.readlines(),
 
 
213
                           '  directory/hello.c\n'
 
 
216
        show_tree_status(wt, specific_files=['directory'], to_file=tof,
 
 
219
        self.assertEquals(tof.readlines(), ['?   directory/hello.c\n'])
 
 
222
        show_tree_status(wt, specific_files=['dir2'], to_file=tof)
 
 
224
        self.assertEquals(tof.readlines(),
 
 
229
        show_tree_status(wt, specific_files=['dir2'], to_file=tof, short=True)
 
 
231
        self.assertEquals(tof.readlines(), ['?   dir2/\n'])
 
 
233
    def test_specific_files_conflicts(self):
 
 
234
        tree = self.make_branch_and_tree('.')
 
 
235
        self.build_tree(['dir2/'])
 
 
237
        tree.commit('added dir2')
 
 
238
        tree.set_conflicts(conflicts.ConflictList(
 
 
239
            [conflicts.ContentsConflict('foo')]))
 
 
241
        show_tree_status(tree, specific_files=['dir2'], to_file=tof)
 
 
242
        self.assertEqualDiff('', tof.getvalue())
 
 
243
        tree.set_conflicts(conflicts.ConflictList(
 
 
244
            [conflicts.ContentsConflict('dir2')]))
 
 
246
        show_tree_status(tree, specific_files=['dir2'], to_file=tof)
 
 
247
        self.assertEqualDiff('conflicts:\n  Contents conflict in dir2\n',
 
 
250
        tree.set_conflicts(conflicts.ConflictList(
 
 
251
            [conflicts.ContentsConflict('dir2/file1')]))
 
 
253
        show_tree_status(tree, specific_files=['dir2'], to_file=tof)
 
 
254
        self.assertEqualDiff('conflicts:\n  Contents conflict in dir2/file1\n',
 
 
257
    def test_status_nonexistent_file(self):
 
 
258
        # files that don't exist in either the basis tree or working tree
 
 
259
        # should give an error
 
 
260
        wt = self.make_branch_and_tree('.')
 
 
261
        out, err = self.run_bzr('status does-not-exist', retcode=3)
 
 
262
        self.assertContainsRe(err, r'do not exist.*does-not-exist')
 
 
264
    def test_status_out_of_date(self):
 
 
265
        """Simulate status of out-of-date tree after remote push"""
 
 
266
        tree = self.make_branch_and_tree('.')
 
 
267
        self.build_tree_contents([('a', 'foo\n')])
 
 
271
            tree.commit('add test file')
 
 
272
            # simulate what happens after a remote push
 
 
273
            tree.set_last_revision("0")
 
 
275
            # before run another commands we should unlock tree
 
 
277
        out, err = self.run_bzr('status')
 
 
278
        self.assertEqual("working tree is out of date, run 'bzr update'\n",
 
 
282
class CheckoutStatus(BranchStatus):
 
 
285
        super(CheckoutStatus, self).setUp()
 
 
289
    def make_branch_and_tree(self, relpath):
 
 
290
        source = self.make_branch(pathjoin('..', relpath))
 
 
291
        checkout = bzrdir.BzrDirMetaFormat1().initialize(relpath)
 
 
292
        bzrlib.branch.BranchReferenceFormat().initialize(checkout, source)
 
 
293
        return checkout.create_workingtree()
 
 
296
class TestStatus(TestCaseWithTransport):
 
 
298
    def test_status_plain(self):
 
 
301
        self.build_tree(['hello.txt'])
 
 
302
        result = self.run_bzr("status")[0]
 
 
303
        self.assertContainsRe(result, "unknown:\n  hello.txt\n")
 
 
305
        self.run_bzr("add hello.txt")
 
 
306
        result = self.run_bzr("status")[0]
 
 
307
        self.assertContainsRe(result, "added:\n  hello.txt\n")
 
 
309
        self.run_bzr("commit -m added")
 
 
310
        result = self.run_bzr("status -r 0..1")[0]
 
 
311
        self.assertContainsRe(result, "added:\n  hello.txt\n")
 
 
313
        self.build_tree(['world.txt'])
 
 
314
        result = self.run_bzr("status -r 0")[0]
 
 
315
        self.assertContainsRe(result, "added:\n  hello.txt\n" \
 
 
316
                                      "unknown:\n  world.txt\n")
 
 
317
        result2 = self.run_bzr("status -r 0..")[0]
 
 
318
        self.assertEquals(result2, result)
 
 
320
    def test_status_short(self):
 
 
323
        self.build_tree(['hello.txt'])
 
 
324
        result = self.run_bzr("status --short")[0]
 
 
325
        self.assertContainsRe(result, "[?]   hello.txt\n")
 
 
327
        self.run_bzr("add hello.txt")
 
 
328
        result = self.run_bzr("status --short")[0]
 
 
329
        self.assertContainsRe(result, "[+]N  hello.txt\n")
 
 
331
        self.run_bzr("commit -m added")
 
 
332
        result = self.run_bzr("status --short -r 0..1")[0]
 
 
333
        self.assertContainsRe(result, "[+]N  hello.txt\n")
 
 
335
        self.build_tree(['world.txt'])
 
 
336
        result = self.run_bzr("status --short -r 0")[0]
 
 
337
        self.assertContainsRe(result, "[+]N  hello.txt\n" \
 
 
339
        result2 = self.run_bzr("status --short -r 0..")[0]
 
 
340
        self.assertEquals(result2, result)
 
 
342
    def test_status_versioned(self):
 
 
345
        self.build_tree(['hello.txt'])
 
 
346
        result = self.run_bzr("status --versioned")[0]
 
 
347
        self.assertNotContainsRe(result, "unknown:\n  hello.txt\n")
 
 
349
        self.run_bzr("add hello.txt")
 
 
350
        result = self.run_bzr("status --versioned")[0]
 
 
351
        self.assertContainsRe(result, "added:\n  hello.txt\n")
 
 
353
        self.run_bzr("commit -m added")
 
 
354
        result = self.run_bzr("status --versioned -r 0..1")[0]
 
 
355
        self.assertContainsRe(result, "added:\n  hello.txt\n")
 
 
357
        self.build_tree(['world.txt'])
 
 
358
        result = self.run_bzr("status --versioned -r 0")[0]
 
 
359
        self.assertContainsRe(result, "added:\n  hello.txt\n")
 
 
360
        self.assertNotContainsRe(result, "unknown:\n  world.txt\n")
 
 
361
        result2 = self.run_bzr("status --versioned -r 0..")[0]
 
 
362
        self.assertEquals(result2, result)
 
 
364
    def assertStatusContains(self, pattern):
 
 
365
        """Run status, and assert it contains the given pattern"""
 
 
366
        result = self.run_bzr("status --short")[0]
 
 
367
        self.assertContainsRe(result, pattern)
 
 
369
    def test_kind_change_short(self):
 
 
370
        tree = self.make_branch_and_tree('.')
 
 
371
        self.build_tree(['file'])
 
 
373
        tree.commit('added file')
 
 
375
        self.build_tree(['file/'])
 
 
376
        self.assertStatusContains('K  file => file/')
 
 
377
        tree.rename_one('file', 'directory')
 
 
378
        self.assertStatusContains('RK  file => directory/')
 
 
380
        self.assertStatusContains('RD  file => directory')
 
 
383
class TestStatusEncodings(TestCaseWithTransport):
 
 
386
        TestCaseWithTransport.setUp(self)
 
 
387
        self.user_encoding = bzrlib.user_encoding
 
 
388
        self.stdout = sys.stdout
 
 
391
        bzrlib.user_encoding = self.user_encoding
 
 
392
        sys.stdout = self.stdout
 
 
393
        TestCaseWithTransport.tearDown(self)
 
 
395
    def make_uncommitted_tree(self):
 
 
396
        """Build a branch with uncommitted unicode named changes in the cwd."""
 
 
397
        working_tree = self.make_branch_and_tree(u'.')
 
 
398
        filename = u'hell\u00d8'
 
 
400
            self.build_tree_contents([(filename, 'contents of hello')])
 
 
401
        except UnicodeEncodeError:
 
 
402
            raise TestSkipped("can't build unicode working tree in "
 
 
403
                "filesystem encoding %s" % sys.getfilesystemencoding())
 
 
404
        working_tree.add(filename)
 
 
407
    def test_stdout_ascii(self):
 
 
408
        sys.stdout = StringIO()
 
 
409
        bzrlib.user_encoding = 'ascii'
 
 
410
        working_tree = self.make_uncommitted_tree()
 
 
411
        stdout, stderr = self.run_bzr("status")
 
 
413
        self.assertEquals(stdout, """\
 
 
418
    def test_stdout_latin1(self):
 
 
419
        sys.stdout = StringIO()
 
 
420
        bzrlib.user_encoding = 'latin-1'
 
 
421
        working_tree = self.make_uncommitted_tree()
 
 
422
        stdout, stderr = self.run_bzr('status')
 
 
424
        self.assertEquals(stdout, u"""\
 
 
427
""".encode('latin-1'))