269
242
        self.assertEqual(list(tree.conflicts()), [expected])
 
272
 
class InstrumentedTree(object):
 
273
 
    """A instrumented tree to check the needs_tree_write_lock decorator."""
 
278
 
    def lock_tree_write(self):
 
279
 
        self._locks.append('t')
 
281
 
    @needs_tree_write_lock
 
282
 
    def method_with_tree_write_lock(self, *args, **kwargs):
 
283
 
        """A lock_tree_write decorated method that returns its arguments."""
 
286
 
    @needs_tree_write_lock
 
287
 
    def method_that_raises(self):
 
288
 
        """This method causes an exception when called with parameters.
 
290
 
        This allows the decorator code to be checked - it should still call
 
295
 
        self._locks.append('u')
 
298
 
class TestInstrumentedTree(TestCase):
 
300
 
    def test_needs_tree_write_lock(self):
 
301
 
        """@needs_tree_write_lock should be semantically transparent."""
 
302
 
        tree = InstrumentedTree()
 
304
 
            'method_with_tree_write_lock',
 
305
 
            tree.method_with_tree_write_lock.__name__)
 
306
 
        self.assertDocstring(
 
307
 
            "A lock_tree_write decorated method that returns its arguments.",
 
308
 
            tree.method_with_tree_write_lock)
 
311
 
        result = tree.method_with_tree_write_lock(1,2,3, a='b')
 
312
 
        self.assertEqual((args, kwargs), result)
 
313
 
        self.assertEqual(['t', 'u'], tree._locks)
 
314
 
        self.assertRaises(TypeError, tree.method_that_raises, 'foo')
 
315
 
        self.assertEqual(['t', 'u', 't', 'u'], tree._locks)
 
318
 
class TestRevert(TestCaseWithTransport):
 
320
 
    def test_revert_conflicts_recursive(self):
 
321
 
        this_tree = self.make_branch_and_tree('this-tree')
 
322
 
        self.build_tree_contents([('this-tree/foo/',),
 
323
 
                                  ('this-tree/foo/bar', 'bar')])
 
324
 
        this_tree.add(['foo', 'foo/bar'])
 
325
 
        this_tree.commit('created foo/bar')
 
326
 
        other_tree = this_tree.bzrdir.sprout('other-tree').open_workingtree()
 
327
 
        self.build_tree_contents([('other-tree/foo/bar', 'baz')])
 
328
 
        other_tree.commit('changed bar')
 
329
 
        self.build_tree_contents([('this-tree/foo/bar', 'qux')])
 
330
 
        this_tree.commit('changed qux')
 
331
 
        this_tree.merge_from_branch(other_tree.branch)
 
332
 
        self.assertEqual(1, len(this_tree.conflicts()))
 
333
 
        this_tree.revert(['foo'])
 
334
 
        self.assertEqual(0, len(this_tree.conflicts()))
 
337
 
class TestAutoResolve(TestCaseWithTransport):
 
339
 
    def test_auto_resolve(self):
 
340
 
        base = self.make_branch_and_tree('base')
 
341
 
        self.build_tree_contents([('base/hello', 'Hello')])
 
342
 
        base.add('hello', 'hello_id')
 
344
 
        other = base.bzrdir.sprout('other').open_workingtree()
 
345
 
        self.build_tree_contents([('other/hello', 'hELLO')])
 
346
 
        other.commit('Case switch')
 
347
 
        this = base.bzrdir.sprout('this').open_workingtree()
 
348
 
        self.failUnlessExists('this/hello')
 
349
 
        self.build_tree_contents([('this/hello', 'Hello World')])
 
350
 
        this.commit('Add World')
 
351
 
        this.merge_from_branch(other.branch)
 
352
 
        self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
 
355
 
        self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
 
357
 
        self.build_tree_contents([('this/hello', '<<<<<<<')])
 
359
 
        self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
 
361
 
        self.build_tree_contents([('this/hello', '=======')])
 
363
 
        self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
 
365
 
        self.build_tree_contents([('this/hello', '\n>>>>>>>')])
 
366
 
        remaining, resolved = this.auto_resolve()
 
367
 
        self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
 
369
 
        self.assertEqual([], resolved)
 
370
 
        self.build_tree_contents([('this/hello', 'hELLO wORLD')])
 
371
 
        remaining, resolved = this.auto_resolve()
 
372
 
        self.assertEqual([], this.conflicts())
 
373
 
        self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
 
375
 
        self.failIfExists('this/hello.BASE')
 
377
 
    def test_auto_resolve_dir(self):
 
378
 
        tree = self.make_branch_and_tree('tree')
 
379
 
        self.build_tree(['tree/hello/'])
 
380
 
        tree.add('hello', 'hello-id')
 
381
 
        file_conflict = conflicts.TextConflict('file', None, 'hello-id')
 
382
 
        tree.set_conflicts(conflicts.ConflictList([file_conflict]))
 
386
 
class TestFindTrees(TestCaseWithTransport):
 
388
 
    def test_find_trees(self):
 
389
 
        self.make_branch_and_tree('foo')
 
390
 
        self.make_branch_and_tree('foo/bar')
 
391
 
        # Sticking a tree inside a control dir is heinous, so let's skip it
 
392
 
        self.make_branch_and_tree('foo/.bzr/baz')
 
393
 
        self.make_branch('qux')
 
394
 
        trees = workingtree.WorkingTree.find_trees('.')
 
395
 
        self.assertEqual(2, len(list(trees)))
 
 
245
class TestNonFormatSpecificCode(TestCaseWithTransport):
 
 
246
    """This class contains tests of workingtree that are not format specific."""
 
 
249
    def test_gen_file_id(self):
 
 
250
        gen_file_id = bzrlib.workingtree.gen_file_id
 
 
252
        # We try to use the filename if possible
 
 
253
        self.assertStartsWith(gen_file_id('bar'), 'bar-')
 
 
255
        # but we squash capitalization, and remove non word characters
 
 
256
        self.assertStartsWith(gen_file_id('Mwoo oof\t m'), 'mwoooofm-')
 
 
258
        # We also remove leading '.' characters to prevent hidden file-ids
 
 
259
        self.assertStartsWith(gen_file_id('..gam.py'), 'gam.py-')
 
 
260
        self.assertStartsWith(gen_file_id('..Mwoo oof\t m'), 'mwoooofm-')
 
 
262
        # we remove unicode characters, and still don't end up with a 
 
 
264
        self.assertStartsWith(gen_file_id(u'\xe5\xb5.txt'), 'txt-')
 
 
266
        # Our current method of generating unique ids adds 33 characters
 
 
267
        # plus an serial number (log10(N) characters)
 
 
268
        # to the end of the filename. We now restrict the filename portion to
 
 
269
        # be <= 20 characters, so the maximum length should now be approx < 60
 
 
271
        # Test both case squashing and length restriction
 
 
272
        fid = gen_file_id('A'*50 + '.txt')
 
 
273
        self.assertStartsWith(fid, 'a'*20 + '-')
 
 
274
        self.failUnless(len(fid) < 60)
 
 
276
        # restricting length happens after the other actions, so
 
 
277
        # we preserve as much as possible
 
 
278
        fid = gen_file_id('\xe5\xb5..aBcd\tefGhijKLMnop\tqrstuvwxyz')
 
 
279
        self.assertStartsWith(fid, 'abcdefghijklmnopqrst-')
 
 
280
        self.failUnless(len(fid) < 60)
 
 
282
    def test_next_id_suffix(self):
 
 
283
        bzrlib.workingtree._gen_id_suffix = None
 
 
284
        bzrlib.workingtree._next_id_suffix()
 
 
285
        self.assertNotEqual(None, bzrlib.workingtree._gen_id_suffix)
 
 
286
        bzrlib.workingtree._gen_id_suffix = "foo-"
 
 
287
        bzrlib.workingtree._gen_id_serial = 1
 
 
288
        self.assertEqual("foo-2", bzrlib.workingtree._next_id_suffix())
 
 
289
        self.assertEqual("foo-3", bzrlib.workingtree._next_id_suffix())
 
 
290
        self.assertEqual("foo-4", bzrlib.workingtree._next_id_suffix())
 
 
291
        self.assertEqual("foo-5", bzrlib.workingtree._next_id_suffix())
 
 
292
        self.assertEqual("foo-6", bzrlib.workingtree._next_id_suffix())
 
 
293
        self.assertEqual("foo-7", bzrlib.workingtree._next_id_suffix())
 
 
294
        self.assertEqual("foo-8", bzrlib.workingtree._next_id_suffix())
 
 
295
        self.assertEqual("foo-9", bzrlib.workingtree._next_id_suffix())
 
 
296
        self.assertEqual("foo-10", bzrlib.workingtree._next_id_suffix())
 
 
298
    def test__translate_ignore_rule(self):
 
 
299
        tree = self.make_branch_and_tree('.')
 
 
300
        # translation should return the regex, the number of groups in it,
 
 
301
        # and the original rule in a tuple.
 
 
302
        # there are three sorts of ignore rules:
 
 
303
        # root only - regex is the rule itself without the leading ./
 
 
306
            tree._translate_ignore_rule("./rootdirrule"))
 
 
307
        # full path - regex is the rule itself
 
 
309
            "(path\\/to\\/file$)",
 
 
310
            tree._translate_ignore_rule("path/to/file"))
 
 
311
        # basename only rule - regex is a rule that ignores everything up
 
 
312
        # to the last / in the filename
 
 
314
            "((?:.*/)?(?!.*/)basenamerule$)",
 
 
315
            tree._translate_ignore_rule("basenamerule"))
 
 
317
    def test__combine_ignore_rules(self):
 
 
318
        tree = self.make_branch_and_tree('.')
 
 
319
        # the combined ignore regexs need the outer group indices
 
 
320
        # placed in a dictionary with the rules that were combined.
 
 
321
        # an empty set of rules
 
 
322
        # this is returned as a list of combined regex,rule sets, because
 
 
323
        # python has a limit of 100 combined regexes.
 
 
324
        compiled_rules = tree._combine_ignore_rules([])
 
 
325
        self.assertEqual([], compiled_rules)
 
 
326
        # one of each type of rule.
 
 
327
        compiled_rules = tree._combine_ignore_rules(
 
 
328
            ["rule1", "rule/two", "./three"])[0]
 
 
329
        # what type *is* the compiled regex to do an isinstance of ?
 
 
330
        self.assertEqual(3, compiled_rules[0].groups)
 
 
332
            {0:"rule1",1:"rule/two",2:"./three"},
 
 
335
    def test__combine_ignore_rules_grouping(self):
 
 
336
        tree = self.make_branch_and_tree('.')
 
 
337
        # when there are too many rules, the output is split into groups of 100
 
 
339
        for index in range(198):
 
 
341
        self.assertEqual(2, len(tree._combine_ignore_rules(rules)))
 
 
343
    def test__get_ignore_rules_as_regex(self):
 
 
344
        tree = self.make_branch_and_tree('.')
 
 
345
        # Setup the default ignore list to be empty
 
 
346
        ignores._set_user_ignores([])
 
 
348
        # some plugins (shelf) modifies the DEFAULT_IGNORE list in memory
 
 
349
        # which causes this test to fail so force the DEFAULT_IGNORE
 
 
351
        orig_default = bzrlib.DEFAULT_IGNORE
 
 
352
        # Also make sure the runtime ignore list is empty
 
 
353
        orig_runtime = ignores._runtime_ignores
 
 
355
            bzrlib.DEFAULT_IGNORE = []
 
 
356
            ignores._runtime_ignores = set()
 
 
358
            self.build_tree_contents([('.bzrignore', 'CVS\n.hg\n')])
 
 
359
            reference_output = tree._combine_ignore_rules(
 
 
360
                                    set(['CVS', '.hg']))[0]
 
 
361
            regex_rules = tree._get_ignore_rules_as_regex()[0]
 
 
362
            self.assertEqual(len(reference_output[1]), regex_rules[0].groups)
 
 
363
            self.assertEqual(reference_output[1], regex_rules[1])
 
 
365
            bzrlib.DEFAULT_IGNORE = orig_default
 
 
366
            ignores._runtime_ignores = orig_runtime