/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
2052.3.2 by John Arbash Meinel
Change Copyright .. by Canonical to Copyright ... Canonical
1
# Copyright (C) 2006 Canonical Ltd
1740.3.1 by Jelmer Vernooij
Introduce and use CommitBuilder objects.
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
"""Tests for repository commit builder."""
18
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
19
from errno import EISDIR
20
import os
21
1910.2.6 by Aaron Bentley
Update for merge review, handle deprecations
22
from bzrlib import inventory
2150.2.2 by Robert Collins
Change the commit builder selected-revision-id test to use a unicode revision id where possible, leading to stricter testing of the hypothetical unicode revision id support in bzr.
23
from bzrlib.errors import NonAsciiRevisionId, CannotSetRevisionId
1740.3.1 by Jelmer Vernooij
Introduce and use CommitBuilder objects.
24
from bzrlib.repository import CommitBuilder
1910.2.22 by Aaron Bentley
Make commits preserve root entry data
25
from bzrlib import tests
1740.3.1 by Jelmer Vernooij
Introduce and use CommitBuilder objects.
26
from bzrlib.tests.repository_implementations.test_repository import TestCaseWithRepository
27
28
29
class TestCommitBuilder(TestCaseWithRepository):
1740.3.3 by Jelmer Vernooij
Move storing directories and links to commit builder.
30
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
31
    def test_get_commit_builder(self):
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
32
        branch = self.make_branch('.')
33
        branch.repository.lock_write()
34
        builder = branch.repository.get_commit_builder(
35
            branch, [], branch.get_config())
1740.3.1 by Jelmer Vernooij
Introduce and use CommitBuilder objects.
36
        self.assertIsInstance(builder, CommitBuilder)
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
37
        branch.repository.commit_write_group()
38
        branch.repository.unlock()
1740.3.3 by Jelmer Vernooij
Move storing directories and links to commit builder.
39
1910.2.6 by Aaron Bentley
Update for merge review, handle deprecations
40
    def record_root(self, builder, tree):
41
        if builder.record_root_entry is True:
2255.7.8 by John Arbash Meinel
Lock the tree when using a commit builder.
42
            tree.lock_read()
43
            try:
44
                ie = tree.inventory.root
45
            finally:
46
                tree.unlock()
1910.2.6 by Aaron Bentley
Update for merge review, handle deprecations
47
            parent_tree = tree.branch.repository.revision_tree(None)
1910.2.22 by Aaron Bentley
Make commits preserve root entry data
48
            parent_invs = []
1910.2.6 by Aaron Bentley
Update for merge review, handle deprecations
49
            builder.record_entry_contents(ie, parent_invs, '', tree)
1731.1.33 by Aaron Bentley
Revert no-special-root changes
50
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
51
    def test_finish_inventory(self):
1740.3.7 by Jelmer Vernooij
Move committer, log, revprops, timestamp and timezone to CommitBuilder.
52
        tree = self.make_branch_and_tree(".")
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
53
        tree.lock_write()
2617.6.8 by Robert Collins
Review feedback and documentation.
54
        try:
55
            builder = tree.branch.get_commit_builder([])
56
            self.record_root(builder, tree)
57
            builder.finish_inventory()
58
            tree.branch.repository.commit_write_group()
59
        finally:
60
            tree.unlock()
1740.3.3 by Jelmer Vernooij
Move storing directories and links to commit builder.
61
2749.3.1 by Jelmer Vernooij
Add CommitBuilder.abort().
62
    def test_abort(self):
63
        tree = self.make_branch_and_tree(".")
64
        tree.lock_write()
65
        try:
66
            builder = tree.branch.get_commit_builder([])
67
            self.record_root(builder, tree)
68
            builder.finish_inventory()
69
            builder.abort()
70
        finally:
71
            tree.unlock()
72
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
73
    def test_commit_message(self):
1740.3.7 by Jelmer Vernooij
Move committer, log, revprops, timestamp and timezone to CommitBuilder.
74
        tree = self.make_branch_and_tree(".")
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
75
        tree.lock_write()
2617.6.8 by Robert Collins
Review feedback and documentation.
76
        try:
77
            builder = tree.branch.get_commit_builder([])
78
            self.record_root(builder, tree)
79
            builder.finish_inventory()
80
            rev_id = builder.commit('foo bar blah')
81
        finally:
82
            tree.unlock()
1740.3.9 by Jelmer Vernooij
Make the commit message the first argument of CommitBuilder.commit().
83
        rev = tree.branch.repository.get_revision(rev_id)
84
        self.assertEqual('foo bar blah', rev.message)
85
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
86
    def test_commit_with_revision_id(self):
1740.3.9 by Jelmer Vernooij
Make the commit message the first argument of CommitBuilder.commit().
87
        tree = self.make_branch_and_tree(".")
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
88
        tree.lock_write()
1740.3.9 by Jelmer Vernooij
Make the commit message the first argument of CommitBuilder.commit().
89
        try:
2617.6.8 by Robert Collins
Review feedback and documentation.
90
            # use a unicode revision id to test more corner cases.
91
            # The repository layer is meant to handle this.
92
            revision_id = u'\xc8abc'.encode('utf8')
2150.2.2 by Robert Collins
Change the commit builder selected-revision-id test to use a unicode revision id where possible, leading to stricter testing of the hypothetical unicode revision id support in bzr.
93
            try:
2617.6.8 by Robert Collins
Review feedback and documentation.
94
                try:
95
                    builder = tree.branch.get_commit_builder([],
96
                        revision_id=revision_id)
97
                except NonAsciiRevisionId:
98
                    revision_id = 'abc'
99
                    builder = tree.branch.get_commit_builder([],
100
                        revision_id=revision_id)
101
            except CannotSetRevisionId:
102
                # This format doesn't support supplied revision ids
103
                return
104
            self.record_root(builder, tree)
105
            builder.finish_inventory()
106
            self.assertEqual(revision_id, builder.commit('foo bar'))
107
        finally:
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
108
            tree.unlock()
2150.2.2 by Robert Collins
Change the commit builder selected-revision-id test to use a unicode revision id where possible, leading to stricter testing of the hypothetical unicode revision id support in bzr.
109
        self.assertTrue(tree.branch.repository.has_revision(revision_id))
110
        # the revision id must be set on the inventory when saving it. This
111
        # does not precisely test that - a repository that wants to can add it
112
        # on deserialisation, but thats all the current contract guarantees
113
        # anyway.
114
        self.assertEqual(revision_id,
115
            tree.branch.repository.get_inventory(revision_id).revision_id)
1740.3.8 by Jelmer Vernooij
Move make_revision() to commit builder.
116
1910.2.8 by Aaron Bentley
Fix commit_builder when root not passed to record_entry_contents
117
    def test_commit_without_root(self):
118
        """This should cause a deprecation warning, not an assertion failure"""
119
        tree = self.make_branch_and_tree(".")
2255.7.8 by John Arbash Meinel
Lock the tree when using a commit builder.
120
        tree.lock_write()
121
        try:
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
122
            if tree.branch.repository.supports_rich_root():
123
                raise tests.TestSkipped('Format requires root')
124
            self.build_tree(['foo'])
125
            tree.add('foo', 'foo-id')
2255.7.8 by John Arbash Meinel
Lock the tree when using a commit builder.
126
            entry = tree.inventory['foo-id']
127
            builder = tree.branch.get_commit_builder([])
128
            self.callDeprecated(['Root entry should be supplied to'
129
                ' record_entry_contents, as of bzr 0.10.'],
130
                builder.record_entry_contents, entry, [], 'foo', tree)
131
            builder.finish_inventory()
132
            rev_id = builder.commit('foo bar')
133
        finally:
134
            tree.unlock()
1910.2.8 by Aaron Bentley
Fix commit_builder when root not passed to record_entry_contents
135
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
136
    def test_commit(self):
1740.3.8 by Jelmer Vernooij
Move make_revision() to commit builder.
137
        tree = self.make_branch_and_tree(".")
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
138
        tree.lock_write()
2617.6.8 by Robert Collins
Review feedback and documentation.
139
        try:
140
            builder = tree.branch.get_commit_builder([])
141
            self.record_root(builder, tree)
142
            builder.finish_inventory()
143
            rev_id = builder.commit('foo bar')
144
        finally:
145
            tree.unlock()
1740.3.9 by Jelmer Vernooij
Make the commit message the first argument of CommitBuilder.commit().
146
        self.assertNotEqual(None, rev_id)
147
        self.assertTrue(tree.branch.repository.has_revision(rev_id))
1757.1.2 by Robert Collins
Bugfix CommitBuilders recording of the inventory revision id.
148
        # the revision id must be set on the inventory when saving it. This does not
149
        # precisely test that - a repository that wants to can add it on deserialisation,
150
        # but thats all the current contract guarantees anyway.
151
        self.assertEqual(rev_id, tree.branch.repository.get_inventory(rev_id).revision_id)
2041.1.1 by John Arbash Meinel
Add a 'get_tree()' call that returns a RevisionTree for the newly committed tree
152
2041.1.5 by John Arbash Meinel
CommitBuilder.get_tree => CommitBuilder.revision_tree
153
    def test_revision_tree(self):
2041.1.1 by John Arbash Meinel
Add a 'get_tree()' call that returns a RevisionTree for the newly committed tree
154
        tree = self.make_branch_and_tree(".")
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
155
        tree.lock_write()
2617.6.8 by Robert Collins
Review feedback and documentation.
156
        try:
157
            builder = tree.branch.get_commit_builder([])
158
            self.record_root(builder, tree)
159
            builder.finish_inventory()
160
            rev_id = builder.commit('foo bar')
161
        finally:
162
            tree.unlock()
2041.1.5 by John Arbash Meinel
CommitBuilder.get_tree => CommitBuilder.revision_tree
163
        rev_tree = builder.revision_tree()
2041.1.1 by John Arbash Meinel
Add a 'get_tree()' call that returns a RevisionTree for the newly committed tree
164
        # Just a couple simple tests to ensure that it actually follows
165
        # the RevisionTree api.
166
        self.assertEqual(rev_id, rev_tree.get_revision_id())
167
        self.assertEqual([], rev_tree.get_parent_ids())
2255.7.65 by Robert Collins
Split test_root_revision_entry into tree and repository portions.
168
169
    def test_root_entry_has_revision(self):
170
        # test the root revision created and put in the basis
171
        # has the right rev id.
172
        tree = self.make_branch_and_tree('.')
173
        rev_id = tree.commit('message')
174
        basis_tree = tree.basis_tree()
175
        basis_tree.lock_read()
176
        self.addCleanup(basis_tree.unlock)
177
        self.assertEqual(rev_id, basis_tree.inventory.root.revision)
178
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
179
    def _get_revtrees(self, tree, revision_ids):
180
        trees = list(tree.branch.repository.revision_trees(revision_ids))
181
        for tree in trees:
182
            tree.lock_read()
183
            self.addCleanup(tree.unlock)
184
        return trees
185
186
    def test_last_modified_revision_after_commit_root_unchanged(self):
187
        # commiting without changing the root does not change the 
188
        # last modified except on non-rich-root-repositories.
189
        tree = self.make_branch_and_tree('.')
190
        rev1 = tree.commit('')
191
        rev2 = tree.commit('')
192
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
193
        self.assertEqual(rev1, tree1.inventory.root.revision)
194
        if tree.branch.repository.supports_rich_root():
195
            self.assertEqual(rev1, tree2.inventory.root.revision)
196
        else:
197
            self.assertEqual(rev2, tree2.inventory.root.revision)
198
199
    def _add_commit_check_unchanged(self, tree, name):
200
        tree.add([name], [name + 'id'])
201
        rev1 = tree.commit('')
202
        rev2 = tree.commit('')
203
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
204
        self.assertEqual(rev1, tree1.inventory[name + 'id'].revision)
205
        self.assertEqual(rev1, tree2.inventory[name + 'id'].revision)
206
        self.assertFileAncestry([rev1], tree, name)
207
208
    def test_last_modified_revision_after_commit_dir_unchanged(self):
209
        # committing without changing a dir does not change the last modified.
210
        tree = self.make_branch_and_tree('.')
211
        self.build_tree(['dir/'])
212
        self._add_commit_check_unchanged(tree, 'dir')
213
214
    def test_last_modified_revision_after_commit_dir_contents_unchanged(self):
215
        # committing without changing a dir does not change the last modified
216
        # of the dir even the dirs contents are changed.
217
        tree = self.make_branch_and_tree('.')
218
        self.build_tree(['dir/'])
219
        tree.add(['dir'], ['dirid'])
220
        rev1 = tree.commit('')
221
        self.build_tree(['dir/content'])
222
        tree.add(['dir/content'], ['contentid'])
223
        rev2 = tree.commit('')
224
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
225
        self.assertEqual(rev1, tree1.inventory['dirid'].revision)
226
        self.assertEqual(rev1, tree2.inventory['dirid'].revision)
227
        self.assertFileAncestry([rev1], tree, 'dir')
228
229
    def test_last_modified_revision_after_commit_file_unchanged(self):
230
        # committing without changing a file does not change the last modified.
231
        tree = self.make_branch_and_tree('.')
232
        self.build_tree(['file'])
233
        self._add_commit_check_unchanged(tree, 'file')
234
235
    def test_last_modified_revision_after_commit_link_unchanged(self):
236
        # committing without changing a link does not change the last modified.
237
        self.requireFeature(tests.SymlinkFeature)
238
        tree = self.make_branch_and_tree('.')
239
        os.symlink('target', 'link')
240
        self._add_commit_check_unchanged(tree, 'link')
241
242
    def _add_commit_renamed_check_changed(self, tree, name):
243
        def rename():
244
            tree.rename_one(name, 'new_' + name)
245
        self._add_commit_change_check_changed(tree, name, rename)
246
247
    def test_last_modified_revision_after_rename_dir_changes(self):
248
        # renaming a dir changes the last modified.
249
        tree = self.make_branch_and_tree('.')
250
        self.build_tree(['dir/'])
251
        self._add_commit_renamed_check_changed(tree, 'dir')
252
253
    def test_last_modified_revision_after_rename_file_changes(self):
254
        # renaming a file changes the last modified.
255
        tree = self.make_branch_and_tree('.')
256
        self.build_tree(['file'])
257
        self._add_commit_renamed_check_changed(tree, 'file')
258
259
    def test_last_modified_revision_after_rename_link_changes(self):
260
        # renaming a link changes the last modified.
261
        self.requireFeature(tests.SymlinkFeature)
262
        tree = self.make_branch_and_tree('.')
263
        os.symlink('target', 'link')
264
        self._add_commit_renamed_check_changed(tree, 'link')
265
266
    def _add_commit_reparent_check_changed(self, tree, name):
267
        self.build_tree(['newparent/'])
268
        tree.add(['newparent'])
269
        def reparent():
270
            tree.rename_one(name, 'newparent/new_' + name)
271
        self._add_commit_change_check_changed(tree, name, reparent)
272
273
    def test_last_modified_revision_after_reparent_dir_changes(self):
274
        # reparenting a dir changes the last modified.
275
        tree = self.make_branch_and_tree('.')
276
        self.build_tree(['dir/'])
277
        self._add_commit_reparent_check_changed(tree, 'dir')
278
279
    def test_last_modified_revision_after_reparent_file_changes(self):
280
        # reparenting a file changes the last modified.
281
        tree = self.make_branch_and_tree('.')
282
        self.build_tree(['file'])
283
        self._add_commit_reparent_check_changed(tree, 'file')
284
285
    def test_last_modified_revision_after_reparent_link_changes(self):
286
        # reparenting a link changes the last modified.
287
        self.requireFeature(tests.SymlinkFeature)
288
        tree = self.make_branch_and_tree('.')
289
        os.symlink('target', 'link')
290
        self._add_commit_reparent_check_changed(tree, 'link')
291
292
    def _add_commit_change_check_changed(self, tree, name, changer):
293
        tree.add([name], [name + 'id'])
294
        rev1 = tree.commit('')
295
        changer()
296
        rev2 = tree.commit('')
297
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
298
        self.assertEqual(rev1, tree1.inventory[name + 'id'].revision)
299
        self.assertEqual(rev2, tree2.inventory[name + 'id'].revision)
300
        self.assertFileAncestry([rev1, rev2], tree, name)
301
302
    def assertFileAncestry(self, ancestry, tree, name, alt_ancestry=None):
303
        # all the changes that have occured should be in the ancestry
304
        # (closest to a public per-file graph API we have today)
305
        tree.lock_read()
306
        self.addCleanup(tree.unlock)
307
        vw = tree.branch.repository.weave_store.get_weave(name + 'id',
308
            tree.branch.repository.get_transaction())
309
        result = vw.get_ancestry([ancestry[-1]])
310
        if alt_ancestry is None:
311
            self.assertEqual(ancestry, result)
312
        else:
313
            self.assertSubset([tuple(result)],
314
                [tuple(ancestry), tuple(alt_ancestry)])
315
316
    def test_last_modified_revision_after_content_file_changes(self):
317
        # altering a file changes the last modified.
318
        tree = self.make_branch_and_tree('.')
319
        self.build_tree(['file'])
320
        def change_file():
321
            tree.put_file_bytes_non_atomic('fileid', 'new content')
322
        self._add_commit_change_check_changed(tree, 'file', change_file)
323
324
    def test_last_modified_revision_after_content_link_changes(self):
325
        # changing a link changes the last modified.
326
        self.requireFeature(tests.SymlinkFeature)
327
        tree = self.make_branch_and_tree('.')
328
        os.symlink('target', 'link')
329
        def change_link():
330
            os.unlink('link')
331
            os.symlink('newtarget', 'link')
332
        self._add_commit_change_check_changed(tree, 'link', change_link)
333
334
    def _commit_sprout(self, tree, name):
335
        tree.add([name], [name + 'id'])
336
        rev_id = tree.commit('')
337
        return rev_id, tree.bzrdir.sprout('t2').open_workingtree()
338
339
    def _rename_in_tree(self, tree, name):
340
        tree.rename_one(name, 'new_' + name)
341
        return tree.commit('')
342
343
    def _commit_sprout_rename_merge(self, tree1, name):
344
        rev1, tree2 = self._commit_sprout(tree1, name)
345
        # change both sides equally
346
        rev2 = self._rename_in_tree(tree1, name)
347
        rev3 = self._rename_in_tree(tree2, name)
348
        tree1.merge_from_branch(tree2.branch)
349
        rev4 = tree1.commit('')
350
        tree3, = self._get_revtrees(tree1, [rev4])
351
        self.assertEqual(rev4, tree3.inventory[name + 'id'].revision)
352
        self.assertFileAncestry([rev1, rev2, rev3, rev4], tree1, name,
353
            [rev1, rev3, rev2, rev4])
354
355
    def test_last_modified_revision_after_merge_dir_changes(self):
356
        # merge a dir changes the last modified.
357
        tree1 = self.make_branch_and_tree('t1')
358
        self.build_tree(['t1/dir/'])
359
        self._commit_sprout_rename_merge(tree1, 'dir')
360
361
    def test_last_modified_revision_after_merge_file_changes(self):
362
        # merge a file changes the last modified.
363
        tree1 = self.make_branch_and_tree('t1')
364
        self.build_tree(['t1/file'])
365
        self._commit_sprout_rename_merge(tree1, 'file')
366
367
    def test_last_modified_revision_after_merge_link_changes(self):
368
        # merge a link changes the last modified.
369
        self.requireFeature(tests.SymlinkFeature)
370
        tree1 = self.make_branch_and_tree('t1')
371
        os.symlink('target', 't1/link')
372
        self._commit_sprout_rename_merge(tree1, 'link')
373
374
    def _commit_sprout_rename_merge_converged(self, tree1, name):
375
        rev1, tree2 = self._commit_sprout(tree1, name)
376
        # change on the other side to merge back
377
        rev2 = self._rename_in_tree(tree2, name)
378
        tree1.merge_from_branch(tree2.branch)
379
        rev3 = tree1.commit('')
380
        tree3, = self._get_revtrees(tree1, [rev2])
381
        self.assertEqual(rev2, tree3.inventory[name + 'id'].revision)
382
        self.assertFileAncestry([rev1, rev2], tree1, name)
383
384
    def test_last_modified_revision_after_converged_merge_dir_changes(self):
385
        # merge a dir changes the last modified.
386
        tree1 = self.make_branch_and_tree('t1')
387
        self.build_tree(['t1/dir/'])
388
        self._commit_sprout_rename_merge_converged(tree1, 'dir')
389
390
    def test_last_modified_revision_after_converged_merge_file_changes(self):
391
        # merge a file changes the last modified.
392
        tree1 = self.make_branch_and_tree('t1')
393
        self.build_tree(['t1/file'])
394
        self._commit_sprout_rename_merge_converged(tree1, 'file')
395
396
    def test_last_modified_revision_after_converged_merge_link_changes(self):
397
        # merge a link changes the last modified.
398
        self.requireFeature(tests.SymlinkFeature)
399
        tree1 = self.make_branch_and_tree('t1')
400
        os.symlink('target', 't1/link')
401
        self._commit_sprout_rename_merge_converged(tree1, 'link')
402
403
    def make_dir(self, name):
404
        self.build_tree([name + '/'])
405
406
    def make_file(self, name):
407
        self.build_tree([name])
408
409
    def make_link(self, name):
410
        self.requireFeature(tests.SymlinkFeature)
411
        os.symlink('target', name)
412
413
    def _check_kind_change(self, make_before, make_after):
414
        tree = self.make_branch_and_tree('.')
415
        path = 'name'
416
        make_before(path)
417
        def change_kind():
418
            try:
419
                os.unlink(path)
420
            except OSError, e:
421
                if e.errno != EISDIR:
422
                    raise
423
                os.rmdir(path)
424
            make_after(path)
425
        self._add_commit_change_check_changed(tree, path, change_kind)
426
427
    def test_last_modified_dir_file(self):
428
        self._check_kind_change(self.make_dir, self.make_file)
429
430
    def test_last_modified_dir_link(self):
431
        self._check_kind_change(self.make_dir, self.make_link)
432
433
    def test_last_modified_link_file(self):
434
        self._check_kind_change(self.make_link, self.make_file)
435
436
    def test_last_modified_link_dir(self):
437
        self._check_kind_change(self.make_link, self.make_dir)
438
439
    def test_last_modified_file_dir(self):
440
        self._check_kind_change(self.make_file, self.make_dir)
441
442
    def test_last_modified_file_link(self):
443
        self._check_kind_change(self.make_file, self.make_link)