1# Copyright (C) 2006-2012, 2016 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17from io import BytesIO
18import os
19
20from ... import (
21    revision as _mod_revision,
22    tests,
23    trace,
24    )
25from ...diff import show_diff_trees
26from ...merge import Merger, Merge3Merger
27from ...transform import (
28    ROOT_PARENT,
29    resolve_conflicts,
30    )
31from ...tree import (
32    find_previous_path,
33    TreeChange,
34    )
35
36from breezy.bzr.inventorytree import InventoryTreeChange
37
38from breezy.tests.per_tree import TestCaseWithTree
39
40
41from ..features import (
42    HardlinkFeature,
43    SymlinkFeature,
44    UnicodeFilenameFeature,
45    )
46
47
48
49class TestTransformPreview(TestCaseWithTree):
50
51    def create_tree(self):
52        tree = self.make_branch_and_tree('.')
53        self.build_tree_contents([('a', b'content 1')])
54        tree.add('a')
55        revid1 = tree.commit('rev1')
56        return tree.branch.repository.revision_tree(revid1)
57
58    def get_empty_preview(self):
59        repository = self.make_repository('repo')
60        tree = repository.revision_tree(_mod_revision.NULL_REVISION)
61        preview = tree.preview_transform()
62        self.addCleanup(preview.finalize)
63        return preview
64
65    def test_transform_preview(self):
66        revision_tree = self.create_tree()
67        preview = revision_tree.preview_transform()
68        self.addCleanup(preview.finalize)
69
70    def test_transform_preview_tree(self):
71        revision_tree = self.create_tree()
72        preview = revision_tree.preview_transform()
73        self.addCleanup(preview.finalize)
74        preview.get_preview_tree()
75
76    def test_transform_new_file(self):
77        revision_tree = self.create_tree()
78        preview = revision_tree.preview_transform()
79        self.addCleanup(preview.finalize)
80        preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
81        preview_tree = preview.get_preview_tree()
82        self.assertEqual(preview_tree.kind('file2'), 'file')
83        with preview_tree.get_file('file2') as f:
84            self.assertEqual(f.read(), b'content B\n')
85
86    def test_diff_preview_tree(self):
87        revision_tree = self.create_tree()
88        preview = revision_tree.preview_transform()
89        self.addCleanup(preview.finalize)
90        preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
91        preview_tree = preview.get_preview_tree()
92        out = BytesIO()
93        show_diff_trees(revision_tree, preview_tree, out)
94        lines = out.getvalue().splitlines()
95        self.assertEqual(lines[0], b"=== added file 'file2'")
96        # 3 lines of diff administrivia
97        self.assertEqual(lines[4], b"+content B")
98
99    def test_unsupported_symlink_diff(self):
100        self.requireFeature(SymlinkFeature)
101        tree = self.make_branch_and_tree('.')
102        self.build_tree_contents([('a', 'content 1')])
103        tree.add('a')
104        os.symlink('a', 'foo')
105        tree.add('foo')
106        revid1 = tree.commit('rev1')
107        revision_tree = tree.branch.repository.revision_tree(revid1)
108        preview = revision_tree.preview_transform()
109        self.addCleanup(preview.finalize)
110        preview.delete_versioned(preview.trans_id_tree_path('foo'))
111        preview_tree = preview.get_preview_tree()
112        out = BytesIO()
113        log = BytesIO()
114        trace.push_log_file(log)
115        os_symlink = getattr(os, 'symlink', None)
116        os.symlink = None
117        try:
118            show_diff_trees(revision_tree, preview_tree, out)
119            lines = out.getvalue().splitlines()
120        finally:
121            os.symlink = os_symlink
122        self.assertContainsRe(
123            log.getvalue(),
124            b'Ignoring "foo" as symlinks are not supported on this filesystem')
125
126    def test_transform_conflicts(self):
127        revision_tree = self.create_tree()
128        preview = revision_tree.preview_transform()
129        self.addCleanup(preview.finalize)
130        preview.new_file('a', preview.root, [b'content 2'])
131        resolve_conflicts(preview)
132        trans_id = preview.trans_id_tree_path('a')
133        self.assertEqual('a.moved', preview.final_name(trans_id))
134
135    def get_tree_and_preview_tree(self):
136        revision_tree = self.create_tree()
137        preview = revision_tree.preview_transform()
138        self.addCleanup(preview.finalize)
139        a_trans_id = preview.trans_id_tree_path('a')
140        preview.delete_contents(a_trans_id)
141        preview.create_file([b'b content'], a_trans_id)
142        preview_tree = preview.get_preview_tree()
143        return revision_tree, preview_tree
144
145    def test_iter_changes(self):
146        revision_tree, preview_tree = self.get_tree_and_preview_tree()
147        root = revision_tree.path2id('')
148        self.assertEqual([(revision_tree.path2id('a'), ('a', 'a'), True, (True, True),
149                           (root, root), ('a', 'a'), ('file', 'file'),
150                           (False, False), False)],
151                         list(preview_tree.iter_changes(revision_tree)))
152
153    def assertTreeChanges(self, expected, actual, tree):
154        # TODO(jelmer): Turn this into a matcher?
155        actual = list(actual)
156        if tree.supports_setting_file_ids():
157            self.assertEqual(expected, actual)
158        else:
159            expected = [
160                TreeChange(path=c.path, changed_content=c.changed_content,
161                           versioned=c.versioned, name=c.name,
162                           kind=c.kind, executable=c.executable,
163                           copied=c.copied) for c in expected]
164            actual = [
165                TreeChange(path=c.path, changed_content=c.changed_content,
166                           versioned=c.versioned, name=c.name,
167                           kind=c.kind, executable=c.executable,
168                           copied=c.copied) for c in actual]
169            self.assertEqual(expected, actual)
170
171    def test_include_unchanged_succeeds(self):
172        revision_tree, preview_tree = self.get_tree_and_preview_tree()
173        changes = preview_tree.iter_changes(revision_tree,
174                                            include_unchanged=True)
175
176        root_id = revision_tree.path2id('')
177        root_entry = InventoryTreeChange(
178            root_id, ('', ''), False, (True, True), (None, None),
179            ('', ''), ('directory', 'directory'), (False, False), False)
180        a_entry = InventoryTreeChange(
181            revision_tree.path2id('a'), ('a', 'a'), True, (True, True),
182            (root_id, root_id), ('a', 'a'), ('file', 'file'),
183            (False, False), False)
184
185        self.assertTreeChanges([root_entry, a_entry], changes, preview_tree)
186
187    def test_specific_files(self):
188        revision_tree, preview_tree = self.get_tree_and_preview_tree()
189        changes = preview_tree.iter_changes(revision_tree,
190                                            specific_files=[''])
191        root_id = revision_tree.path2id('')
192        a_entry = (revision_tree.path2id('a'), ('a', 'a'), True, (True, True),
193                   (root_id, root_id), ('a', 'a'), ('file', 'file'),
194                   (False, False), False)
195
196        self.assertEqual([a_entry], list(changes))
197
198    def test_want_unversioned(self):
199        revision_tree, preview_tree = self.get_tree_and_preview_tree()
200        changes = preview_tree.iter_changes(revision_tree,
201                                            want_unversioned=True)
202        root_id = revision_tree.path2id('')
203        a_entry = InventoryTreeChange(
204            revision_tree.path2id('a'), ('a', 'a'), True, (True, True),
205            (root_id, root_id), ('a', 'a'), ('file', 'file'),
206            (False, False), False)
207
208        self.assertEqual([a_entry], list(changes))
209
210    def test_ignore_extra_trees_no_specific_files(self):
211        # extra_trees is harmless without specific_files, so we'll silently
212        # accept it, even though we won't use it.
213        revision_tree, preview_tree = self.get_tree_and_preview_tree()
214        preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
215
216    def test_ignore_require_versioned_no_specific_files(self):
217        # require_versioned is meaningless without specific_files.
218        revision_tree, preview_tree = self.get_tree_and_preview_tree()
219        preview_tree.iter_changes(revision_tree, require_versioned=False)
220
221    def test_ignore_pb(self):
222        # pb could be supported, but TT.iter_changes doesn't support it.
223        revision_tree, preview_tree = self.get_tree_and_preview_tree()
224        preview_tree.iter_changes(revision_tree)
225
226    def test_kind(self):
227        revision_tree = self.create_tree()
228        preview = revision_tree.preview_transform()
229        self.addCleanup(preview.finalize)
230        preview.new_file('file', preview.root, [b'contents'], b'file-id')
231        preview.new_directory('directory', preview.root, b'dir-id')
232        preview_tree = preview.get_preview_tree()
233        self.assertEqual('file', preview_tree.kind('file'))
234        self.assertEqual('directory', preview_tree.kind('directory'))
235
236    def test_get_file_mtime(self):
237        preview = self.get_empty_preview()
238        file_trans_id = preview.new_file('file', preview.root, [b'contents'],
239                                         b'file-id')
240        limbo_path = preview._limbo_name(file_trans_id)
241        preview_tree = preview.get_preview_tree()
242        self.assertEqual(os.stat(limbo_path).st_mtime,
243                         preview_tree.get_file_mtime('file'))
244
245    def test_get_file_mtime_renamed(self):
246        work_tree = self.make_branch_and_tree('tree')
247        self.build_tree(['tree/file'])
248        work_tree.add('file')
249        preview = work_tree.preview_transform()
250        self.addCleanup(preview.finalize)
251        file_trans_id = preview.trans_id_tree_path('file')
252        preview.adjust_path('renamed', preview.root, file_trans_id)
253        preview_tree = preview.get_preview_tree()
254        preview_mtime = preview_tree.get_file_mtime('renamed')
255        work_mtime = work_tree.get_file_mtime('file')
256
257    def test_get_file_size(self):
258        work_tree = self.make_branch_and_tree('tree')
259        self.build_tree_contents([('tree/old', b'old')])
260        work_tree.add('old')
261        preview = work_tree.preview_transform()
262        self.addCleanup(preview.finalize)
263        preview.new_file('name', preview.root, [b'contents'], b'new-id',
264                         'executable')
265        tree = preview.get_preview_tree()
266        self.assertEqual(len('old'), tree.get_file_size('old'))
267        self.assertEqual(len('contents'), tree.get_file_size('name'))
268
269    def test_get_file(self):
270        preview = self.get_empty_preview()
271        preview.new_file('file', preview.root, [b'contents'], b'file-id')
272        preview_tree = preview.get_preview_tree()
273        with preview_tree.get_file('file') as tree_file:
274            self.assertEqual(b'contents', tree_file.read())
275
276    def test_get_symlink_target(self):
277        self.requireFeature(SymlinkFeature)
278        preview = self.get_empty_preview()
279        preview.new_symlink('symlink', preview.root, 'target', b'symlink-id')
280        preview_tree = preview.get_preview_tree()
281        self.assertEqual('target',
282                         preview_tree.get_symlink_target('symlink'))
283
284    def test_all_file_ids(self):
285        if not self.workingtree_format.supports_setting_file_ids:
286            raise tests.TestNotApplicable(
287                'format does not support setting file ids')
288        tree = self.make_branch_and_tree('tree')
289        self.build_tree(['tree/a', 'tree/b', 'tree/c'])
290        tree.add(['a', 'b', 'c'], [b'a-id', b'b-id', b'c-id'])
291        preview = tree.preview_transform()
292        self.addCleanup(preview.finalize)
293        preview.unversion_file(preview.trans_id_file_id(b'b-id'))
294        c_trans_id = preview.trans_id_file_id(b'c-id')
295        preview.unversion_file(c_trans_id)
296        preview.version_file(c_trans_id, file_id=b'c-id')
297        preview_tree = preview.get_preview_tree()
298        self.assertEqual({b'a-id', b'c-id', tree.path2id('')},
299                         preview_tree.all_file_ids())
300
301    def test_path2id_deleted_unchanged(self):
302        tree = self.make_branch_and_tree('tree')
303        self.build_tree(['tree/unchanged', 'tree/deleted'])
304        tree.add(['unchanged', 'deleted'])
305        preview = tree.preview_transform()
306        self.addCleanup(preview.finalize)
307        preview.unversion_file(preview.trans_id_tree_path('deleted'))
308        preview_tree = preview.get_preview_tree()
309        self.assertEqual(
310            'unchanged',
311            find_previous_path(preview_tree, tree, 'unchanged'))
312        self.assertFalse(preview_tree.is_versioned('deleted'))
313
314    def test_path2id_created(self):
315        tree = self.make_branch_and_tree('tree')
316        self.build_tree(['tree/unchanged'])
317        tree.add(['unchanged'])
318        preview = tree.preview_transform()
319        self.addCleanup(preview.finalize)
320        preview.new_file('new', preview.trans_id_tree_path('unchanged'),
321                         [b'contents'], b'new-id')
322        preview_tree = preview.get_preview_tree()
323        self.assertTrue(preview_tree.is_versioned('unchanged/new'))
324        if self.workingtree_format.supports_setting_file_ids:
325            self.assertEqual(b'new-id', preview_tree.path2id('unchanged/new'))
326
327    def test_path2id_moved(self):
328        tree = self.make_branch_and_tree('tree')
329        self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
330        tree.add(['old_parent', 'old_parent/child'])
331        preview = tree.preview_transform()
332        self.addCleanup(preview.finalize)
333        new_parent = preview.new_directory('new_parent', preview.root,
334                                           b'new_parent-id')
335        preview.adjust_path('child', new_parent,
336                            preview.trans_id_tree_path('old_parent/child'))
337        preview_tree = preview.get_preview_tree()
338        self.assertFalse(preview_tree.is_versioned('old_parent/child'))
339        self.assertEqual(
340            'new_parent/child',
341            find_previous_path(tree, preview_tree, 'old_parent/child'))
342        if self.workingtree_format.supports_setting_file_ids:
343            self.assertEqual(
344                tree.path2id('old_parent/child'),
345                preview_tree.path2id('new_parent/child'))
346
347    def test_path2id_renamed_parent(self):
348        tree = self.make_branch_and_tree('tree')
349        self.build_tree(['tree/old_name/', 'tree/old_name/child'])
350        tree.add(['old_name', 'old_name/child'])
351        preview = tree.preview_transform()
352        self.addCleanup(preview.finalize)
353        preview.adjust_path('new_name', preview.root,
354                            preview.trans_id_tree_path('old_name'))
355        preview_tree = preview.get_preview_tree()
356        self.assertFalse(preview_tree.is_versioned('old_name/child'))
357        self.assertEqual(
358            'new_name/child',
359            find_previous_path(tree, preview_tree, 'old_name/child'))
360        if tree.supports_setting_file_ids():
361            self.assertEqual(
362                tree.path2id('old_name/child'),
363                preview_tree.path2id('new_name/child'))
364
365    def assertMatchingIterEntries(self, tt, specific_files=None):
366        preview_tree = tt.get_preview_tree()
367        preview_result = list(preview_tree.iter_entries_by_dir(
368                              specific_files=specific_files))
369        tree = tt._tree
370        tt.apply()
371        actual_result = list(tree.iter_entries_by_dir(
372            specific_files=specific_files))
373        self.assertEqual(actual_result, preview_result)
374
375    def test_iter_entries_by_dir_new(self):
376        tree = self.make_branch_and_tree('tree')
377        tt = tree.transform()
378        tt.new_file('new', tt.root, [b'contents'], b'new-id')
379        self.assertMatchingIterEntries(tt)
380
381    def test_iter_entries_by_dir_deleted(self):
382        tree = self.make_branch_and_tree('tree')
383        self.build_tree(['tree/deleted'])
384        tree.add('deleted')
385        tt = tree.transform()
386        tt.delete_contents(tt.trans_id_tree_path('deleted'))
387        self.assertMatchingIterEntries(tt)
388
389    def test_iter_entries_by_dir_unversioned(self):
390        tree = self.make_branch_and_tree('tree')
391        self.build_tree(['tree/removed'])
392        tree.add('removed')
393        tt = tree.transform()
394        tt.unversion_file(tt.trans_id_tree_path('removed'))
395        self.assertMatchingIterEntries(tt)
396
397    def test_iter_entries_by_dir_moved(self):
398        tree = self.make_branch_and_tree('tree')
399        self.build_tree(['tree/moved', 'tree/new_parent/'])
400        tree.add(['moved', 'new_parent'])
401        tt = tree.transform()
402        tt.adjust_path(
403            'moved', tt.trans_id_tree_path('new_parent'),
404            tt.trans_id_tree_path('moved'))
405        self.assertMatchingIterEntries(tt)
406
407    def test_iter_entries_by_dir_specific_files(self):
408        tree = self.make_branch_and_tree('tree')
409        self.build_tree(['tree/parent/', 'tree/parent/child'])
410        tree.add(['parent', 'parent/child'])
411        tt = tree.transform()
412        self.assertMatchingIterEntries(tt, ['', 'parent/child'])
413
414    def test_symlink_content_summary(self):
415        self.requireFeature(SymlinkFeature)
416        preview = self.get_empty_preview()
417        preview.new_symlink('path', preview.root, 'target', b'path-id')
418        summary = preview.get_preview_tree().path_content_summary('path')
419        self.assertEqual(('symlink', None, None, 'target'), summary)
420
421    def test_missing_content_summary(self):
422        preview = self.get_empty_preview()
423        summary = preview.get_preview_tree().path_content_summary('path')
424        self.assertEqual(('missing', None, None, None), summary)
425
426    def test_deleted_content_summary(self):
427        tree = self.make_branch_and_tree('tree')
428        self.build_tree(['tree/path/'])
429        tree.add('path')
430        preview = tree.preview_transform()
431        self.addCleanup(preview.finalize)
432        preview.delete_contents(preview.trans_id_tree_path('path'))
433        summary = preview.get_preview_tree().path_content_summary('path')
434        self.assertEqual(('missing', None, None, None), summary)
435
436    def test_file_content_summary_executable(self):
437        preview = self.get_empty_preview()
438        path_id = preview.new_file('path', preview.root, [
439                                   b'contents'], b'path-id')
440        preview.set_executability(True, path_id)
441        summary = preview.get_preview_tree().path_content_summary('path')
442        self.assertEqual(4, len(summary))
443        self.assertEqual('file', summary[0])
444        # size must be known
445        self.assertEqual(len('contents'), summary[1])
446        # executable
447        self.assertEqual(True, summary[2])
448        # will not have hash (not cheap to determine)
449        self.assertIs(None, summary[3])
450
451    def test_change_executability(self):
452        tree = self.make_branch_and_tree('tree')
453        self.build_tree(['tree/path'])
454        tree.add('path')
455        preview = tree.preview_transform()
456        self.addCleanup(preview.finalize)
457        path_id = preview.trans_id_tree_path('path')
458        preview.set_executability(True, path_id)
459        summary = preview.get_preview_tree().path_content_summary('path')
460        self.assertEqual(True, summary[2])
461
462    def test_file_content_summary_non_exec(self):
463        preview = self.get_empty_preview()
464        preview.new_file('path', preview.root, [b'contents'], b'path-id')
465        summary = preview.get_preview_tree().path_content_summary('path')
466        self.assertEqual(4, len(summary))
467        self.assertEqual('file', summary[0])
468        # size must be known
469        self.assertEqual(len('contents'), summary[1])
470        # not executable
471        self.assertEqual(False, summary[2])
472        # will not have hash (not cheap to determine)
473        self.assertIs(None, summary[3])
474
475    def test_dir_content_summary(self):
476        preview = self.get_empty_preview()
477        preview.new_directory('path', preview.root, b'path-id')
478        summary = preview.get_preview_tree().path_content_summary('path')
479        self.assertEqual(('directory', None, None, None), summary)
480
481    def test_tree_content_summary(self):
482        preview = self.get_empty_preview()
483        path = preview.new_directory('path', preview.root, b'path-id')
484        preview.set_tree_reference(b'rev-1', path)
485        summary = preview.get_preview_tree().path_content_summary('path')
486        self.assertEqual(4, len(summary))
487        self.assertEqual('tree-reference', summary[0])
488
489    def test_annotate(self):
490        tree = self.make_branch_and_tree('tree')
491        self.build_tree_contents([('tree/file', b'a\n')])
492        tree.add('file')
493        revid1 = tree.commit('a')
494        self.build_tree_contents([('tree/file', b'a\nb\n')])
495        preview = tree.preview_transform()
496        self.addCleanup(preview.finalize)
497        file_trans_id = preview.trans_id_tree_path('file')
498        preview.delete_contents(file_trans_id)
499        preview.create_file([b'a\nb\nc\n'], file_trans_id)
500        preview_tree = preview.get_preview_tree()
501        expected = [
502            (revid1, b'a\n'),
503            (b'me:', b'b\n'),
504            (b'me:', b'c\n'),
505        ]
506        annotation = preview_tree.annotate_iter(
507            'file', default_revision=b'me:')
508        self.assertEqual(expected, annotation)
509
510    def test_annotate_missing(self):
511        preview = self.get_empty_preview()
512        preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
513        preview_tree = preview.get_preview_tree()
514        expected = [
515            (b'me:', b'a\n'),
516            (b'me:', b'b\n'),
517            (b'me:', b'c\n'),
518            ]
519        annotation = preview_tree.annotate_iter(
520            'file', default_revision=b'me:')
521        self.assertEqual(expected, annotation)
522
523    def test_annotate_rename(self):
524        tree = self.make_branch_and_tree('tree')
525        self.build_tree_contents([('tree/file', b'a\n')])
526        tree.add('file')
527        revid1 = tree.commit('a')
528        preview = tree.preview_transform()
529        self.addCleanup(preview.finalize)
530        file_trans_id = preview.trans_id_tree_path('file')
531        preview.adjust_path('newname', preview.root, file_trans_id)
532        preview_tree = preview.get_preview_tree()
533        expected = [
534            (revid1, b'a\n'),
535        ]
536        annotation = preview_tree.annotate_iter(
537            'newname', default_revision=b'me:')
538        self.assertEqual(expected, annotation)
539        annotation = preview_tree.annotate_iter(
540            'file', default_revision=b'me:')
541        self.assertIs(None, annotation)
542
543    def test_annotate_deleted(self):
544        tree = self.make_branch_and_tree('tree')
545        self.build_tree_contents([('tree/file', b'a\n')])
546        tree.add('file')
547        tree.commit('a')
548        self.build_tree_contents([('tree/file', b'a\nb\n')])
549        preview = tree.preview_transform()
550        self.addCleanup(preview.finalize)
551        file_trans_id = preview.trans_id_tree_path('file')
552        preview.delete_contents(file_trans_id)
553        preview_tree = preview.get_preview_tree()
554        annotation = preview_tree.annotate_iter(
555            'file', default_revision=b'me:')
556        self.assertIs(None, annotation)
557
558    def test_stored_kind(self):
559        preview = self.get_empty_preview()
560        preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
561        preview_tree = preview.get_preview_tree()
562        self.assertEqual('file', preview_tree.stored_kind('file'))
563
564    def test_is_executable(self):
565        preview = self.get_empty_preview()
566        trans_id = preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
567        preview.set_executability(True, trans_id)
568        preview_tree = preview.get_preview_tree()
569        self.assertEqual(True, preview_tree.is_executable('file'))
570
571    def test_get_set_parent_ids(self):
572        revision_tree, preview_tree = self.get_tree_and_preview_tree()
573        self.assertEqual([], preview_tree.get_parent_ids())
574        preview_tree.set_parent_ids([revision_tree.get_revision_id()])
575        self.assertEqual(
576            [revision_tree.get_revision_id()],
577            preview_tree.get_parent_ids())
578
579    def test_plan_file_merge(self):
580        work_a = self.make_branch_and_tree('wta')
581        self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
582        work_a.add('file')
583        base_id = work_a.commit('base version')
584        tree_b = work_a.controldir.sprout('wtb').open_workingtree()
585        preview = work_a.preview_transform()
586        self.addCleanup(preview.finalize)
587        trans_id = preview.trans_id_tree_path('file')
588        preview.delete_contents(trans_id)
589        preview.create_file([b'b\nc\nd\ne\n'], trans_id)
590        self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
591        tree_a = preview.get_preview_tree()
592        if not getattr(tree_a, 'plan_file_merge', None):
593            self.skipTest('tree does not support file merge planning')
594        tree_a.set_parent_ids([base_id])
595        self.addCleanup(tree_b.lock_read().unlock)
596        self.assertEqual([
597            ('killed-a', b'a\n'),
598            ('killed-b', b'b\n'),
599            ('unchanged', b'c\n'),
600            ('unchanged', b'd\n'),
601            ('new-a', b'e\n'),
602            ('new-b', b'f\n'),
603        ], list(tree_a.plan_file_merge('file', tree_b)))
604
605    def test_plan_file_merge_revision_tree(self):
606        work_a = self.make_branch_and_tree('wta')
607        self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
608        work_a.add('file')
609        base_id = work_a.commit('base version')
610        tree_b = work_a.controldir.sprout('wtb').open_workingtree()
611        preview = work_a.basis_tree().preview_transform()
612        self.addCleanup(preview.finalize)
613        trans_id = preview.trans_id_tree_path('file')
614        preview.delete_contents(trans_id)
615        preview.create_file([b'b\nc\nd\ne\n'], trans_id)
616        self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
617        tree_a = preview.get_preview_tree()
618        if not getattr(tree_a, 'plan_file_merge', None):
619            self.skipTest('tree does not support file merge planning')
620        tree_a.set_parent_ids([base_id])
621        self.addCleanup(tree_b.lock_read().unlock)
622        self.assertEqual([
623            ('killed-a', b'a\n'),
624            ('killed-b', b'b\n'),
625            ('unchanged', b'c\n'),
626            ('unchanged', b'd\n'),
627            ('new-a', b'e\n'),
628            ('new-b', b'f\n'),
629        ], list(tree_a.plan_file_merge('file', tree_b)))
630
631    def test_walkdirs(self):
632        preview = self.get_empty_preview()
633        preview.new_directory('', ROOT_PARENT, b'tree-root')
634        # FIXME: new_directory should mark root.
635        preview.fixup_new_roots()
636        preview_tree = preview.get_preview_tree()
637        preview.new_file('a', preview.root, [b'contents'], b'a-id')
638        expected = [('', [('a', 'a', 'file', None, 'file')])]
639        self.assertEqual(expected, list(preview_tree.walkdirs()))
640
641    def test_extras(self):
642        work_tree = self.make_branch_and_tree('tree')
643        self.build_tree(['tree/removed-file', 'tree/existing-file',
644                         'tree/not-removed-file'])
645        work_tree.add(['removed-file', 'not-removed-file'])
646        preview = work_tree.preview_transform()
647        self.addCleanup(preview.finalize)
648        preview.new_file('new-file', preview.root, [b'contents'])
649        preview.new_file('new-versioned-file', preview.root, [b'contents'],
650                         b'new-versioned-id')
651        tree = preview.get_preview_tree()
652        self.assertEquals({'existing-file'}, set(work_tree.extras()))
653        preview.unversion_file(preview.trans_id_tree_path('removed-file'))
654        self.assertEqual({'new-file', 'removed-file', 'existing-file'},
655                         set(tree.extras()))
656
657    def test_merge_into_preview(self):
658        work_tree = self.make_branch_and_tree('tree')
659        self.build_tree_contents([('tree/file', b'b\n')])
660        work_tree.add('file')
661        work_tree.commit('first commit')
662        child_tree = work_tree.controldir.sprout('child').open_workingtree()
663        self.build_tree_contents([('child/file', b'b\nc\n')])
664        child_tree.commit('child commit')
665        child_tree.lock_write()
666        self.addCleanup(child_tree.unlock)
667        work_tree.lock_write()
668        self.addCleanup(work_tree.unlock)
669        preview = work_tree.preview_transform()
670        self.addCleanup(preview.finalize)
671        file_trans_id = preview.trans_id_tree_path('file')
672        preview.delete_contents(file_trans_id)
673        preview.create_file([b'a\nb\n'], file_trans_id)
674        preview_tree = preview.get_preview_tree()
675        merger = Merger.from_revision_ids(preview_tree,
676                                          child_tree.branch.last_revision(),
677                                          other_branch=child_tree.branch,
678                                          tree_branch=work_tree.branch)
679        merger.merge_type = Merge3Merger
680        tt = merger.make_merger().make_preview_transform()
681        self.addCleanup(tt.finalize)
682        final_tree = tt.get_preview_tree()
683        self.assertEqual(
684            b'a\nb\nc\n',
685            final_tree.get_file_text('file'))
686
687    def test_merge_preview_into_workingtree(self):
688        tree = self.make_branch_and_tree('tree')
689        if tree.supports_setting_file_ids():
690            tree.set_root_id(b'TREE_ROOT')
691        tt = tree.preview_transform()
692        self.addCleanup(tt.finalize)
693        tt.new_file('name', tt.root, [b'content'], b'file-id')
694        tree2 = self.make_branch_and_tree('tree2')
695        if tree.supports_setting_file_ids():
696            tree2.set_root_id(b'TREE_ROOT')
697        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
698                                         tree.basis_tree())
699        merger.merge_type = Merge3Merger
700        merger.do_merge()
701
702    def test_merge_preview_into_workingtree_handles_conflicts(self):
703        tree = self.make_branch_and_tree('tree')
704        self.build_tree_contents([('tree/foo', b'bar')])
705        tree.add('foo')
706        tree.commit('foo')
707        tt = tree.preview_transform()
708        self.addCleanup(tt.finalize)
709        trans_id = tt.trans_id_tree_path('foo')
710        tt.delete_contents(trans_id)
711        tt.create_file([b'baz'], trans_id)
712        tree2 = tree.controldir.sprout('tree2').open_workingtree()
713        self.build_tree_contents([('tree2/foo', b'qux')])
714        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
715                                         tree.basis_tree())
716        merger.merge_type = Merge3Merger
717        merger.do_merge()
718
719    def test_has_filename(self):
720        wt = self.make_branch_and_tree('tree')
721        self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
722        tt = wt.preview_transform()
723        removed_id = tt.trans_id_tree_path('removed')
724        tt.delete_contents(removed_id)
725        tt.new_file('new', tt.root, [b'contents'])
726        modified_id = tt.trans_id_tree_path('modified')
727        tt.delete_contents(modified_id)
728        tt.create_file([b'modified-contents'], modified_id)
729        self.addCleanup(tt.finalize)
730        tree = tt.get_preview_tree()
731        self.assertTrue(tree.has_filename('unmodified'))
732        self.assertFalse(tree.has_filename('not-present'))
733        self.assertFalse(tree.has_filename('removed'))
734        self.assertTrue(tree.has_filename('new'))
735        self.assertTrue(tree.has_filename('modified'))
736
737    def test_is_executable2(self):
738        tree = self.make_branch_and_tree('tree')
739        preview = tree.preview_transform()
740        self.addCleanup(preview.finalize)
741        preview.new_file('foo', preview.root, [b'bar'], b'baz-id')
742        preview_tree = preview.get_preview_tree()
743        self.assertEqual(False, preview_tree.is_executable('tree/foo'))
744
745    def test_commit_preview_tree(self):
746        tree = self.make_branch_and_tree('tree')
747        rev_id = tree.commit('rev1')
748        tree.branch.lock_write()
749        self.addCleanup(tree.branch.unlock)
750        tt = tree.preview_transform()
751        tt.new_file('file', tt.root, [b'contents'], b'file_id')
752        self.addCleanup(tt.finalize)
753        preview = tt.get_preview_tree()
754        preview.set_parent_ids([rev_id])
755        builder = tree.branch.get_commit_builder([rev_id])
756        list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
757        builder.finish_inventory()
758        rev2_id = builder.commit('rev2')
759        rev2_tree = tree.branch.repository.revision_tree(rev2_id)
760        self.assertEqual(b'contents', rev2_tree.get_file_text('file'))
761
762    def test_ascii_limbo_paths(self):
763        self.requireFeature(UnicodeFilenameFeature)
764        branch = self.make_branch('any')
765        tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
766        tt = tree.preview_transform()
767        self.addCleanup(tt.finalize)
768        foo_id = tt.new_directory('', ROOT_PARENT)
769        bar_id = tt.new_file(u'\u1234bar', foo_id, [b'contents'])
770        limbo_path = tt._limbo_name(bar_id)
771        self.assertEqual(limbo_path, limbo_path)
772