~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_smart_add.py

  • Committer: John Arbash Meinel
  • Date: 2007-03-01 21:56:19 UTC
  • mto: (2255.7.84 dirstate)
  • mto: This revision was merged to the branch mainline in revision 2322.
  • Revision ID: john@arbash-meinel.com-20070301215619-wpt6kz8yem3ypu1b
Update to dirstate locking.
Move all of WT4.lock_* functions locally, so that they can
properly interact and cleanup around when we lock/unlock the
dirstate file.
Change all Lock objects to be non-blocking. So that if someone
grabs a lock on the DirState we find out immediately, rather
than blocking.
Change WT4.unlock() so that if the dirstate is dirty, it will
save the contents even if it only has a read lock.
It does this by trying to take a write lock, if it fails
we just ignore it. If it succeeds, then we can flush to disk.
This is more important now that DirState tracks file changes.
It allows 'bzr status' to update the cached stat and sha values.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
16
 
 
17
from cStringIO import StringIO
 
18
import os
 
19
import unittest
 
20
 
 
21
from bzrlib import errors, ignores, osutils
 
22
from bzrlib.add import (
 
23
    AddAction,
 
24
    AddFromBaseAction,
 
25
    smart_add,
 
26
    smart_add_tree,
 
27
    )
 
28
from bzrlib.tests import TestCase, TestCaseWithTransport, TestSkipped
 
29
from bzrlib.errors import NoSuchFile
 
30
from bzrlib.inventory import InventoryFile, Inventory
 
31
from bzrlib.workingtree import WorkingTree
 
32
 
 
33
 
 
34
class TestSmartAdd(TestCaseWithTransport):
 
35
 
 
36
    def test_add_dot_from_root(self):
 
37
        """Test adding . from the root of the tree.""" 
 
38
        from bzrlib.add import smart_add
 
39
        paths = ("original/", "original/file1", "original/file2")
 
40
        self.build_tree(paths)
 
41
        wt = self.make_branch_and_tree('.')
 
42
        smart_add_tree(wt, (u".",))
 
43
        for path in paths:
 
44
            self.assertNotEqual(wt.path2id(path), None)
 
45
 
 
46
    def test_add_dot_from_subdir(self):
 
47
        """Test adding . from a subdir of the tree.""" 
 
48
        from bzrlib.add import smart_add
 
49
        paths = ("original/", "original/file1", "original/file2")
 
50
        self.build_tree(paths)
 
51
        wt = self.make_branch_and_tree('.')
 
52
        os.chdir("original")
 
53
        smart_add_tree(wt, (u".",))
 
54
        for path in paths:
 
55
            self.assertNotEqual(wt.path2id(path), None)
 
56
 
 
57
    def test_add_tree_from_above_tree(self):
 
58
        """Test adding a tree from above the tree.""" 
 
59
        from bzrlib.add import smart_add
 
60
        paths = ("original/", "original/file1", "original/file2")
 
61
        branch_paths = ("branch/", "branch/original/", "branch/original/file1",
 
62
                        "branch/original/file2")
 
63
        self.build_tree(branch_paths)
 
64
        wt = self.make_branch_and_tree('branch')
 
65
        smart_add_tree(wt, ("branch",))
 
66
        for path in paths:
 
67
            self.assertNotEqual(wt.path2id(path), None)
 
68
 
 
69
    def test_add_above_tree_preserves_tree(self):
 
70
        """Test nested trees are not affect by an add above them."""
 
71
        from bzrlib.add import smart_add
 
72
        paths = ("original/", "original/file1", "original/file2")
 
73
        child_paths = ("path",)
 
74
        full_child_paths = ("original/child", "original/child/path")
 
75
        build_paths = ("original/", "original/file1", "original/file2", 
 
76
                       "original/child/", "original/child/path")
 
77
        
 
78
        self.build_tree(build_paths)
 
79
        wt = self.make_branch_and_tree('.')
 
80
        child_tree = self.make_branch_and_tree('original/child')
 
81
        smart_add_tree(wt, (".",))
 
82
        for path in paths:
 
83
            self.assertNotEqual((path, wt.path2id(path)),
 
84
                                (path, None))
 
85
        for path in full_child_paths:
 
86
            self.assertEqual((path, wt.path2id(path)),
 
87
                             (path, None))
 
88
        for path in child_paths:
 
89
            self.assertEqual(child_tree.path2id(path), None)
 
90
 
 
91
    def test_add_paths(self):
 
92
        """Test smart-adding a list of paths."""
 
93
        from bzrlib.add import smart_add
 
94
        paths = ("file1", "file2")
 
95
        self.build_tree(paths)
 
96
        wt = self.make_branch_and_tree('.')
 
97
        smart_add_tree(wt, paths)
 
98
        for path in paths:
 
99
            self.assertNotEqual(wt.path2id(path), None)
 
100
    
 
101
    def test_add_ignored_nested_paths(self):
 
102
        """Test smart-adding a list of paths which includes ignored ones."""
 
103
        wt = self.make_branch_and_tree('.')
 
104
        tree_shape = ("adir/", "adir/CVS/", "adir/CVS/afile", "adir/CVS/afile2")
 
105
        add_paths = ("adir/CVS", "adir/CVS/afile", "adir")
 
106
        expected_paths = ("adir", "adir/CVS", "adir/CVS/afile", "adir/CVS/afile2")
 
107
        self.build_tree(tree_shape)
 
108
        smart_add_tree(wt, add_paths)
 
109
        for path in expected_paths:
 
110
            self.assertNotEqual(wt.path2id(path), None, "No id added for %s" % path)
 
111
 
 
112
    def test_save_false(self):
 
113
        """Test smart-adding a path with save set to false."""
 
114
        wt = self.make_branch_and_tree('.')
 
115
        self.build_tree(['file'])
 
116
        smart_add_tree(wt, ['file'], save=False)
 
117
        self.assertEqual(wt.path2id('file'), None)
 
118
 
 
119
    def test_add_dry_run(self):
 
120
        """Test a dry run add, make sure nothing is added."""
 
121
        from bzrlib.commands import run_bzr
 
122
        eq = self.assertEqual
 
123
        wt = self.make_branch_and_tree('.')
 
124
        self.build_tree(['inertiatic/', 'inertiatic/esp'])
 
125
        eq(list(wt.unknowns()), ['inertiatic'])
 
126
        self.capture('add --dry-run .')
 
127
        eq(list(wt.unknowns()), ['inertiatic'])
 
128
 
 
129
    def test_add_non_existant(self):
 
130
        """Test smart-adding a file that does not exist."""
 
131
        from bzrlib.add import smart_add
 
132
        wt = self.make_branch_and_tree('.')
 
133
        self.assertRaises(NoSuchFile, smart_add_tree, wt, 'non-existant-file')
 
134
 
 
135
    def test_returns_and_ignores(self):
 
136
        """Correctly returns added/ignored files"""
 
137
        from bzrlib.commands import run_bzr
 
138
        wt = self.make_branch_and_tree('.')
 
139
        # The default ignore list includes '*.py[co]', but not CVS
 
140
        ignores._set_user_ignores(['*.py[co]'])
 
141
        self.build_tree(['inertiatic/', 'inertiatic/esp', 'inertiatic/CVS',
 
142
                        'inertiatic/foo.pyc'])
 
143
        added, ignored = smart_add_tree(wt, u'.')
 
144
        self.assertSubset(('inertiatic', 'inertiatic/esp', 'inertiatic/CVS'),
 
145
                          added)
 
146
        self.assertSubset(('*.py[co]',), ignored)
 
147
        self.assertSubset(('inertiatic/foo.pyc',), ignored['*.py[co]'])
 
148
 
 
149
 
 
150
class AddCustomIDAction(AddAction):
 
151
 
 
152
    def __call__(self, inv, parent_ie, path, kind):
 
153
        # The first part just logs if appropriate
 
154
        # Now generate a custom id
 
155
        file_id = kind + '-' + path.raw_path.replace('/', '%')
 
156
        if self.should_print:
 
157
            self._to_file.write('added %s with id %s\n' 
 
158
                                % (path.raw_path, file_id))
 
159
        return file_id
 
160
 
 
161
 
 
162
class TestSmartAddTree(TestCaseWithTransport):
 
163
    """Test smart adds with a specified branch."""
 
164
 
 
165
    def test_add_dot_from_root(self):
 
166
        """Test adding . from the root of the tree.""" 
 
167
        paths = ("original/", "original/file1", "original/file2")
 
168
        self.build_tree(paths)
 
169
        wt = self.make_branch_and_tree('.')
 
170
        smart_add_tree(wt, (u".",))
 
171
        for path in paths:
 
172
            self.assertNotEqual(wt.path2id(path), None)
 
173
 
 
174
    def test_add_dot_from_subdir(self):
 
175
        """Test adding . from a subdir of the tree.""" 
 
176
        paths = ("original/", "original/file1", "original/file2")
 
177
        self.build_tree(paths)
 
178
        wt = self.make_branch_and_tree('.')
 
179
        os.chdir("original")
 
180
        smart_add_tree(wt, (u".",))
 
181
        for path in paths:
 
182
            self.assertNotEqual(wt.path2id(path), None)
 
183
 
 
184
    def test_add_tree_from_above_tree(self):
 
185
        """Test adding a tree from above the tree.""" 
 
186
        paths = ("original/", "original/file1", "original/file2")
 
187
        branch_paths = ("branch/", "branch/original/", "branch/original/file1",
 
188
                        "branch/original/file2")
 
189
        self.build_tree(branch_paths)
 
190
        tree = self.make_branch_and_tree('branch')
 
191
        smart_add_tree(tree, ("branch",))
 
192
        for path in paths:
 
193
            self.assertNotEqual(tree.path2id(path), None)
 
194
 
 
195
    def test_add_above_tree_preserves_tree(self):
 
196
        """Test nested trees are not affect by an add above them."""
 
197
        paths = ("original/", "original/file1", "original/file2")
 
198
        child_paths = ("path")
 
199
        full_child_paths = ("original/child", "original/child/path")
 
200
        build_paths = ("original/", "original/file1", "original/file2", 
 
201
                       "original/child/", "original/child/path")
 
202
        self.build_tree(build_paths)
 
203
        tree = self.make_branch_and_tree('.')
 
204
        child_tree = self.make_branch_and_tree("original/child")
 
205
        smart_add_tree(tree, (u".",))
 
206
        for path in paths:
 
207
            self.assertNotEqual((path, tree.path2id(path)),
 
208
                                (path, None))
 
209
        for path in full_child_paths:
 
210
            self.assertEqual((path, tree.path2id(path)),
 
211
                             (path, None))
 
212
        for path in child_paths:
 
213
            self.assertEqual(child_tree.path2id(path), None)
 
214
 
 
215
    def test_add_paths(self):
 
216
        """Test smart-adding a list of paths."""
 
217
        paths = ("file1", "file2")
 
218
        self.build_tree(paths)
 
219
        wt = self.make_branch_and_tree('.')
 
220
        smart_add_tree(wt, paths)
 
221
        for path in paths:
 
222
            self.assertNotEqual(wt.path2id(path), None)
 
223
 
 
224
    def test_add_multiple_dirs(self):
 
225
        """Test smart adding multiple directories at once."""
 
226
        added_paths = ['file1', 'file2',
 
227
                       'dir1/', 'dir1/file3',
 
228
                       'dir1/subdir2/', 'dir1/subdir2/file4',
 
229
                       'dir2/', 'dir2/file5',
 
230
                      ]
 
231
        not_added = ['file6', 'dir3/', 'dir3/file7', 'dir3/file8']
 
232
        self.build_tree(added_paths)
 
233
        self.build_tree(not_added)
 
234
 
 
235
        wt = self.make_branch_and_tree('.')
 
236
        smart_add_tree(wt, ['file1', 'file2', 'dir1', 'dir2'])
 
237
 
 
238
        for path in added_paths:
 
239
            self.assertNotEqual(None, wt.path2id(path.rstrip('/')),
 
240
                    'Failed to add path: %s' % (path,))
 
241
        for path in not_added:
 
242
            self.assertEqual(None, wt.path2id(path.rstrip('/')),
 
243
                    'Accidentally added path: %s' % (path,))
 
244
 
 
245
    def test_custom_ids(self):
 
246
        sio = StringIO()
 
247
        action = AddCustomIDAction(to_file=sio, should_print=True)
 
248
        self.build_tree(['file1', 'dir1/', 'dir1/file2'])
 
249
 
 
250
        wt = self.make_branch_and_tree('.')
 
251
        smart_add_tree(wt, ['.'], action=action)
 
252
        # The order of adds is not strictly fixed:
 
253
        sio.seek(0)
 
254
        lines = sorted(sio.readlines())
 
255
        self.assertEqualDiff(['added dir1 with id directory-dir1\n',
 
256
                              'added dir1/file2 with id file-dir1%file2\n',
 
257
                              'added file1 with id file-file1\n',
 
258
                             ], lines)
 
259
        wt.lock_read()
 
260
        self.addCleanup(wt.unlock)
 
261
        self.assertEqual([('', wt.path2id('')),
 
262
                          ('dir1', 'directory-dir1'),
 
263
                          ('dir1/file2', 'file-dir1%file2'),
 
264
                          ('file1', 'file-file1'),
 
265
                         ], [(path, ie.file_id) for path, ie
 
266
                                in wt.inventory.iter_entries()])
 
267
 
 
268
 
 
269
class TestAddFrom(TestCaseWithTransport):
 
270
    """Tests for AddFromBaseAction"""
 
271
 
 
272
    def make_base_tree(self):
 
273
        self.base_tree = self.make_branch_and_tree('base')
 
274
        self.build_tree(['base/a', 'base/b',
 
275
                         'base/dir/', 'base/dir/a',
 
276
                         'base/dir/subdir/',
 
277
                         'base/dir/subdir/b',
 
278
                        ])
 
279
        self.base_tree.add(['a', 'b', 'dir', 'dir/a',
 
280
                            'dir/subdir', 'dir/subdir/b'])
 
281
        self.base_tree.commit('creating initial tree.')
 
282
 
 
283
    def add_helper(self, base_tree, base_path, new_tree, file_list,
 
284
                   should_print=False):
 
285
        to_file = StringIO()
 
286
        base_tree.lock_read()
 
287
        try:
 
288
            new_tree.lock_write()
 
289
            try:
 
290
                action = AddFromBaseAction(base_tree, base_path,
 
291
                                           to_file=to_file,
 
292
                                           should_print=should_print)
 
293
                smart_add_tree(new_tree, file_list, action=action)
 
294
            finally:
 
295
                new_tree.unlock()
 
296
        finally:
 
297
            base_tree.unlock()
 
298
        return to_file.getvalue()
 
299
 
 
300
    def test_copy_all(self):
 
301
        self.make_base_tree()
 
302
        new_tree = self.make_branch_and_tree('new')
 
303
        files = ['a', 'b',
 
304
                 'dir/', 'dir/a',
 
305
                 'dir/subdir/',
 
306
                 'dir/subdir/b',
 
307
                ]
 
308
        self.build_tree(['new/' + fn for fn in files])
 
309
        self.add_helper(self.base_tree, '', new_tree, ['new'])
 
310
 
 
311
        for fn in files:
 
312
            base_file_id = self.base_tree.path2id(fn)
 
313
            new_file_id = new_tree.path2id(fn)
 
314
            self.assertEqual(base_file_id, new_file_id)
 
315
 
 
316
    def test_copy_from_dir(self):
 
317
        self.make_base_tree()
 
318
        new_tree = self.make_branch_and_tree('new')
 
319
 
 
320
        self.build_tree(['new/a', 'new/b', 'new/c',
 
321
                         'new/subdir/', 'new/subdir/b', 'new/subdir/d'])
 
322
        new_tree.set_root_id(self.base_tree.get_root_id())
 
323
        self.add_helper(self.base_tree, 'dir', new_tree, ['new'])
 
324
 
 
325
        # We know 'a' and 'b' exist in the root, and they are being added
 
326
        # in a new 'root'. Since ROOT ids have been set as the same, we will
 
327
        # use those ids
 
328
        self.assertEqual(self.base_tree.path2id('a'),
 
329
                         new_tree.path2id('a'))
 
330
        self.assertEqual(self.base_tree.path2id('b'),
 
331
                         new_tree.path2id('b'))
 
332
 
 
333
        # Because we specified 'dir/' as the base path, and we have
 
334
        # nothing named 'subdir' in the base tree, we should grab the
 
335
        # ids from there
 
336
        self.assertEqual(self.base_tree.path2id('dir/subdir'),
 
337
                         new_tree.path2id('subdir'))
 
338
        self.assertEqual(self.base_tree.path2id('dir/subdir/b'),
 
339
                         new_tree.path2id('subdir/b'))
 
340
 
 
341
        # These should get newly generated ids
 
342
        c_id = new_tree.path2id('c')
 
343
        self.assertNotEqual(None, c_id)
 
344
        self.base_tree.lock_read()
 
345
        self.addCleanup(self.base_tree.unlock)
 
346
        self.failIf(c_id in self.base_tree)
 
347
 
 
348
        d_id = new_tree.path2id('subdir/d')
 
349
        self.assertNotEqual(None, d_id)
 
350
        self.failIf(d_id in self.base_tree)
 
351
 
 
352
    def test_copy_existing_dir(self):
 
353
        self.make_base_tree()
 
354
        new_tree = self.make_branch_and_tree('new')
 
355
        self.build_tree(['new/subby/', 'new/subby/a', 'new/subby/b'])
 
356
 
 
357
        subdir_file_id = self.base_tree.path2id('dir/subdir')
 
358
        new_tree.add(['subby'], [subdir_file_id])
 
359
        self.add_helper(self.base_tree, '', new_tree, ['new'])
 
360
        # Because 'subby' already points to subdir, we should add
 
361
        # 'b' with the same id
 
362
        self.assertEqual(self.base_tree.path2id('dir/subdir/b'),
 
363
                         new_tree.path2id('subby/b'))
 
364
 
 
365
        # 'subby/a' should be added with a new id because there is no
 
366
        # matching path or child of 'subby'.
 
367
        a_id = new_tree.path2id('subby/a')
 
368
        self.assertNotEqual(None, a_id)
 
369
        self.base_tree.lock_read()
 
370
        self.addCleanup(self.base_tree.unlock)
 
371
        self.failIf(a_id in self.base_tree)
 
372
 
 
373
 
 
374
class TestAddNonNormalized(TestCaseWithTransport):
 
375
 
 
376
    def make(self):
 
377
        try:
 
378
            self.build_tree([u'a\u030a'])
 
379
        except UnicodeError:
 
380
            raise TestSkipped('Filesystem cannot create unicode filenames')
 
381
 
 
382
        self.wt = self.make_branch_and_tree('.')
 
383
 
 
384
    def test_accessible_explicit(self):
 
385
        self.make()
 
386
        orig = osutils.normalized_filename
 
387
        osutils.normalized_filename = osutils._accessible_normalized_filename
 
388
        try:
 
389
            smart_add_tree(self.wt, [u'a\u030a'])
 
390
            self.wt.lock_read()
 
391
            self.addCleanup(self.wt.unlock)
 
392
            self.assertEqual([('', 'directory'), (u'\xe5', 'file')],
 
393
                    [(path, ie.kind) for path,ie in 
 
394
                        self.wt.inventory.iter_entries()])
 
395
        finally:
 
396
            osutils.normalized_filename = orig
 
397
 
 
398
    def test_accessible_implicit(self):
 
399
        self.make()
 
400
        orig = osutils.normalized_filename
 
401
        osutils.normalized_filename = osutils._accessible_normalized_filename
 
402
        try:
 
403
            smart_add_tree(self.wt, [])
 
404
            self.wt.lock_read()
 
405
            self.addCleanup(self.wt.unlock)
 
406
            self.assertEqual([('', 'directory'), (u'\xe5', 'file')],
 
407
                    [(path, ie.kind) for path,ie in 
 
408
                        self.wt.inventory.iter_entries()])
 
409
        finally:
 
410
            osutils.normalized_filename = orig
 
411
 
 
412
    def test_inaccessible_explicit(self):
 
413
        self.make()
 
414
        orig = osutils.normalized_filename
 
415
        osutils.normalized_filename = osutils._inaccessible_normalized_filename
 
416
        try:
 
417
            self.assertRaises(errors.InvalidNormalization,
 
418
                    smart_add_tree, self.wt, [u'a\u030a'])
 
419
        finally:
 
420
            osutils.normalized_filename = orig
 
421
 
 
422
    def test_inaccessible_implicit(self):
 
423
        self.make()
 
424
        orig = osutils.normalized_filename
 
425
        osutils.normalized_filename = osutils._inaccessible_normalized_filename
 
426
        try:
 
427
            # TODO: jam 20060701 In the future, this should probably
 
428
            #       just ignore files that don't fit the normalization
 
429
            #       rules, rather than exploding
 
430
            self.assertRaises(errors.InvalidNormalization,
 
431
                    smart_add_tree, self.wt, [])
 
432
        finally:
 
433
            osutils.normalized_filename = orig
 
434
 
 
435
 
 
436
class TestAddActions(TestCase):
 
437
 
 
438
    def test_quiet(self):
 
439
        self.run_action("")
 
440
 
 
441
    def test__print(self):
 
442
        self.run_action("added path\n")
 
443
 
 
444
    def run_action(self, output):
 
445
        from bzrlib.add import AddAction, FastPath
 
446
        inv = Inventory()
 
447
        stdout = StringIO()
 
448
        action = AddAction(to_file=stdout, should_print=bool(output))
 
449
 
 
450
        self.apply_redirected(None, stdout, None, action, inv, None, FastPath('path'), 'file')
 
451
        self.assertEqual(stdout.getvalue(), output)