Merge lp:~jelmer/brz/renames into lp:brz/3.1

Proposed by Jelmer Vernooij
Status: Merged
Approved by: Jelmer Vernooij
Approved revision: no longer in the source branch.
Merge reported by: The Breezy Bot
Merged at revision: not available
Proposed branch: lp:~jelmer/brz/renames
Merge into: lp:brz/3.1
Diff against target: 554 lines (+191/-66)
7 files modified
breezy/builtins.py (+1/-1)
breezy/commit.py (+2/-2)
breezy/git/commit.py (+8/-5)
breezy/git/tests/test_workingtree.py (+100/-27)
breezy/git/tree.py (+56/-19)
breezy/tests/per_repository/test_commit_builder.py (+21/-8)
doc/en/release-notes/brz-3.1.txt (+3/-4)
To merge this branch: bzr merge lp:~jelmer/brz/renames
Reviewer Review Type Date Requested Status
Jelmer Vernooij Approve
Review via email: mp+381006@code.launchpad.net

This proposal supersedes a proposal from 2020-01-26.

Commit message

Support Git rename tracking.

Description of the change

Support Git rename tracking.

To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) : Posted in a previous version of this proposal
review: Approve
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : Posted in a previous version of this proposal
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : Posted in a previous version of this proposal
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote :
Revision history for this message
Jelmer Vernooij (jelmer) :
review: Approve
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote :
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'breezy/builtins.py'
2--- breezy/builtins.py 2020-04-02 01:37:43 +0000
3+++ breezy/builtins.py 2020-06-16 23:49:21 +0000
4@@ -4233,7 +4233,7 @@
5
6 try:
7 from . import tests
8- except ImportError:
9+ except ImportError as e:
10 raise errors.BzrCommandError("tests not available. Install the "
11 "breezy tests to run the breezy testsuite.")
12
13
14=== modified file 'breezy/commit.py'
15--- breezy/commit.py 2019-09-21 17:08:09 +0000
16+++ breezy/commit.py 2020-06-16 23:49:21 +0000
17@@ -440,8 +440,8 @@
18 # as updating its basis and unversioning paths that were missing.
19 self.work_tree.unversion(self.deleted_paths)
20 self._set_progress_stage("Updating the working tree")
21- self.work_tree.update_basis_by_delta(self.rev_id,
22- self.builder.get_basis_delta())
23+ self.work_tree.update_basis_by_delta(
24+ self.rev_id, self.builder.get_basis_delta())
25 self.reporter.completed(new_revno, self.rev_id)
26 self._process_post_hooks(old_revno, new_revno)
27 return self.rev_id
28
29=== modified file 'breezy/git/commit.py'
30--- breezy/git/commit.py 2020-01-30 15:48:47 +0000
31+++ breezy/git/commit.py 2020-06-16 23:49:21 +0000
32@@ -69,6 +69,7 @@
33 self.store = self.repository._git.object_store
34 self._blobs = {}
35 self._inv_delta = []
36+ self._deleted_paths = set()
37 self._any_changes = False
38 self._mapping = self.repository.get_mapping()
39
40@@ -92,7 +93,7 @@
41 self._any_changes = True
42 if change.path[1] is None:
43 self._inv_delta.append((change.path[0], change.path[1], change.file_id, None))
44- self._blobs[change.path[0].encode("utf-8")] = None
45+ self._deleted_paths.add(change.path[0].encode("utf-8"))
46 continue
47 try:
48 entry_kls = entry_factory[change.kind[1]]
49@@ -128,8 +129,9 @@
50 raise AssertionError("Unknown kind %r" % change.kind[1])
51 mode = object_mode(change.kind[1], change.executable[1])
52 self._inv_delta.append((change.path[0], change.path[1], change.file_id, entry))
53- encoded_new_path = change.path[1].encode("utf-8")
54- self._blobs[encoded_new_path] = (mode, sha)
55+ if change.path[0] is not None:
56+ self._deleted_paths.add(change.path[0].encode("utf-8"))
57+ self._blobs[change.path[1].encode("utf-8")] = (mode, sha)
58 if st is not None:
59 yield change.path[1], (entry.text_sha1, st)
60 if not seen_root and len(self.parents) == 0:
61@@ -146,6 +148,8 @@
62 for entry in basis_tree._iter_tree_contents(include_trees=False):
63 if entry.path in self._blobs:
64 continue
65+ if entry.path in self._deleted_paths:
66+ continue
67 self._blobs[entry.path] = (entry.mode, entry.sha)
68 self.new_inventory = None
69
70@@ -155,8 +159,7 @@
71
72 def finish_inventory(self):
73 # eliminate blobs that were removed
74- self._blobs = {k: v for (k, v) in viewitems(
75- self._blobs) if v is not None}
76+ self._blobs = {k: v for (k, v) in viewitems(self._blobs)}
77
78 def _iterblobs(self):
79 return ((path, sha, mode) for (path, (mode, sha))
80
81=== modified file 'breezy/git/tests/test_workingtree.py'
82--- breezy/git/tests/test_workingtree.py 2020-01-19 15:14:16 +0000
83+++ breezy/git/tests/test_workingtree.py 2020-06-16 23:49:21 +0000
84@@ -22,6 +22,8 @@
85 import os
86 import stat
87
88+from dulwich import __version__ as dulwich_version
89+from dulwich.diff_tree import RenameDetector
90 from dulwich.index import IndexEntry
91 from dulwich.objects import (
92 S_IFGITLINK,
93@@ -141,9 +143,15 @@
94
95 def test_missing(self):
96 delta = TreeDelta()
97- delta.removed.append(TreeChange(b'git:a', ('a', 'a'), False, (True, True), (b'TREE_ROOT', b'TREE_ROOT'), ('a', 'a'), ('file', None), (True, False)))
98- changes = [((b'a', b'a'), (stat.S_IFREG | 0o755, 0),
99- (b'a' * 40, b'a' * 40))]
100+ delta.removed.append(
101+ TreeChange(
102+ b'git:a', ('a', 'a'), False, (True, True),
103+ (b'TREE_ROOT', b'TREE_ROOT'), ('a', 'a'), ('file', None),
104+ (True, False)))
105+ changes = [
106+ ('remove',
107+ (b'a', stat.S_IFREG | 0o755, b'a' * 40),
108+ (b'a', 0, b'a' * 40))]
109 self.assertEqual(
110 delta,
111 tree_delta_from_git_changes(changes, (default_mapping, default_mapping)))
112@@ -158,7 +166,7 @@
113
114 def expectDelta(self, expected_changes,
115 expected_extras=None, want_unversioned=False,
116- tree_id=None):
117+ tree_id=None, rename_detector=None):
118 if tree_id is None:
119 try:
120 tree_id = self.store[self.wt.branch.repository._git.head()].tree
121@@ -166,7 +174,8 @@
122 tree_id = None
123 with self.wt.lock_read():
124 changes, extras = changes_between_git_tree_and_working_copy(
125- self.store, tree_id, self.wt, want_unversioned=want_unversioned)
126+ self.store, tree_id, self.wt, want_unversioned=want_unversioned,
127+ rename_detector=rename_detector)
128 self.assertEqual(expected_changes, list(changes))
129 if expected_extras is None:
130 expected_extras = set()
131@@ -174,7 +183,7 @@
132
133 def test_empty(self):
134 self.expectDelta(
135- [((None, b''), (None, stat.S_IFDIR), (None, Tree().id))])
136+ [('add', (None, None, None), (b'', stat.S_IFDIR, Tree().id))])
137
138 def test_added_file(self):
139 self.build_tree(['a'])
140@@ -183,20 +192,74 @@
141 t = Tree()
142 t.add(b"a", stat.S_IFREG | 0o644, a.id)
143 self.expectDelta(
144- [((None, b''), (None, stat.S_IFDIR), (None, t.id)),
145- ((None, b'a'), (None, stat.S_IFREG | 0o644), (None, a.id))])
146+ [('add', (None, None, None), (b'', stat.S_IFDIR, t.id)),
147+ ('add', (None, None, None), (b'a', stat.S_IFREG | 0o644, a.id))])
148+
149+ def test_renamed_file(self):
150+ self.build_tree(['a'])
151+ self.wt.add(['a'])
152+ self.wt.rename_one('a', 'b')
153+ a = Blob.from_string(b'contents of a\n')
154+ self.store.add_object(a)
155+ oldt = Tree()
156+ oldt.add(b"a", stat.S_IFREG | 0o644, a.id)
157+ self.store.add_object(oldt)
158+ newt = Tree()
159+ newt.add(b"b", stat.S_IFREG | 0o644, a.id)
160+ self.store.add_object(newt)
161+ self.expectDelta(
162+ [('modify', (b'', stat.S_IFDIR, oldt.id), (b'', stat.S_IFDIR, newt.id)),
163+ ('delete', (b'a', stat.S_IFREG | 0o644, a.id), (None, None, None)),
164+ ('add', (None, None, None), (b'b', stat.S_IFREG | 0o644, a.id)),
165+ ],
166+ tree_id=oldt.id)
167+ if dulwich_version >= (0, 19, 15):
168+ self.expectDelta(
169+ [('modify', (b'', stat.S_IFDIR, oldt.id), (b'', stat.S_IFDIR, newt.id)),
170+ ('rename', (b'a', stat.S_IFREG | 0o644, a.id), (b'b', stat.S_IFREG | 0o644, a.id))],
171+ tree_id=oldt.id, rename_detector=RenameDetector(self.store))
172+
173+ def test_copied_file(self):
174+ self.build_tree(['a'])
175+ self.wt.add(['a'])
176+ self.wt.copy_one('a', 'b')
177+ a = Blob.from_string(b'contents of a\n')
178+ self.store.add_object(a)
179+ oldt = Tree()
180+ oldt.add(b"a", stat.S_IFREG | 0o644, a.id)
181+ self.store.add_object(oldt)
182+ newt = Tree()
183+ newt.add(b"a", stat.S_IFREG | 0o644, a.id)
184+ newt.add(b"b", stat.S_IFREG | 0o644, a.id)
185+ self.store.add_object(newt)
186+ self.expectDelta(
187+ [('modify', (b'', stat.S_IFDIR, oldt.id), (b'', stat.S_IFDIR, newt.id)),
188+ ('add', (None, None, None), (b'b', stat.S_IFREG | 0o644, a.id)),
189+ ],
190+ tree_id=oldt.id)
191+
192+ if dulwich_version >= (0, 19, 15):
193+ self.expectDelta(
194+ [('modify', (b'', stat.S_IFDIR, oldt.id), (b'', stat.S_IFDIR, newt.id)),
195+ ('copy', (b'a', stat.S_IFREG | 0o644, a.id), (b'b', stat.S_IFREG | 0o644, a.id))],
196+ tree_id=oldt.id, rename_detector=RenameDetector(self.store, find_copies_harder=True))
197+ self.expectDelta(
198+ [('modify', (b'', stat.S_IFDIR, oldt.id), (b'', stat.S_IFDIR, newt.id)),
199+ ('add', (None, None, None), (b'b', stat.S_IFREG | 0o644, a.id)),
200+ ],
201+ tree_id=oldt.id, rename_detector=RenameDetector(self.store, find_copies_harder=False))
202
203 def test_added_unknown_file(self):
204 self.build_tree(['a'])
205 t = Tree()
206 self.expectDelta(
207- [((None, b''), (None, stat.S_IFDIR), (None, t.id))])
208+ [('add', (None, None, None), (b'', stat.S_IFDIR, t.id))])
209 a = Blob.from_string(b'contents of a\n')
210 t = Tree()
211 t.add(b"a", stat.S_IFREG | 0o644, a.id)
212 self.expectDelta(
213- [((None, b''), (None, stat.S_IFDIR), (None, t.id)),
214- ((None, b'a'), (None, stat.S_IFREG | 0o644), (None, a.id))],
215+ [('add', (None, None, None), (b'', stat.S_IFDIR, t.id)),
216+ ('add', (None, None, None), (b'a', stat.S_IFREG | 0o644, a.id))],
217 [b'a'],
218 want_unversioned=True)
219
220@@ -208,8 +271,8 @@
221 t = Tree()
222 t.add(b"a", 0, ZERO_SHA)
223 self.expectDelta(
224- [((None, b''), (None, stat.S_IFDIR), (None, t.id)),
225- ((None, b'a'), (None, 0), (None, ZERO_SHA))],
226+ [('add', (None, None, None), (b'', stat.S_IFDIR, t.id)),
227+ ('add', (None, None, None), (b'a', 0, ZERO_SHA))],
228 [])
229
230 def test_missing_versioned_file(self):
231@@ -223,8 +286,12 @@
232 newt = Tree()
233 newt.add(b"a", 0, ZERO_SHA)
234 self.expectDelta(
235- [((b'', b''), (stat.S_IFDIR, stat.S_IFDIR), (oldt.id, newt.id)),
236- ((b'a', b'a'), (stat.S_IFREG | 0o644, 0), (a.id, ZERO_SHA))])
237+ [('modify',
238+ (b'', stat.S_IFDIR, oldt.id),
239+ (b'', stat.S_IFDIR, newt.id)),
240+ ('modify',
241+ (b'a', stat.S_IFREG | 0o644, a.id),
242+ (b'a', 0, ZERO_SHA))])
243
244 def test_versioned_replace_by_dir(self):
245 self.build_tree(['a'])
246@@ -239,16 +306,20 @@
247 newa = Tree()
248 newt.add(b"a", stat.S_IFDIR, newa.id)
249 self.expectDelta([
250- ((b'', b''),
251- (stat.S_IFDIR, stat.S_IFDIR),
252- (oldt.id, newt.id)),
253- ((b'a', b'a'), (stat.S_IFREG | 0o644, stat.S_IFDIR), (olda.id, newa.id))
254+ ('modify',
255+ (b'', stat.S_IFDIR, oldt.id),
256+ (b'', stat.S_IFDIR, newt.id)),
257+ ('modify',
258+ (b'a', stat.S_IFREG | 0o644, olda.id),
259+ (b'a', stat.S_IFDIR, newa.id))
260 ], want_unversioned=False)
261 self.expectDelta([
262- ((b'', b''),
263- (stat.S_IFDIR, stat.S_IFDIR),
264- (oldt.id, newt.id)),
265- ((b'a', b'a'), (stat.S_IFREG | 0o644, stat.S_IFDIR), (olda.id, newa.id))
266+ ('modify',
267+ (b'', stat.S_IFDIR, oldt.id),
268+ (b'', stat.S_IFDIR, newt.id)),
269+ ('modify',
270+ (b'a', stat.S_IFREG | 0o644, olda.id),
271+ (b'a', stat.S_IFDIR, newa.id)),
272 ], want_unversioned=True)
273
274 def test_extra(self):
275@@ -257,10 +328,12 @@
276 newt = Tree()
277 newt.add(b"a", stat.S_IFREG | 0o644, newa.id)
278 self.expectDelta([
279- ((None, b''),
280- (None, stat.S_IFDIR),
281- (None, newt.id)),
282- ((None, b'a'), (None, stat.S_IFREG | 0o644), (None, newa.id))
283+ ('add',
284+ (None, None, None),
285+ (b'', stat.S_IFDIR, newt.id)),
286+ ('add',
287+ (None, None, None),
288+ (b'a', stat.S_IFREG | 0o644, newa.id)),
289 ], [b'a'], want_unversioned=True)
290
291 def test_submodule(self):
292
293=== modified file 'breezy/git/tree.py'
294--- breezy/git/tree.py 2020-04-27 02:29:59 +0000
295+++ breezy/git/tree.py 2020-06-16 23:49:21 +0000
296@@ -28,7 +28,7 @@
297 parse_submodules,
298 ConfigFile as GitConfigFile,
299 )
300-from dulwich.diff_tree import tree_changes
301+from dulwich.diff_tree import tree_changes, RenameDetector
302 from dulwich.errors import NotTreeError
303 from dulwich.index import (
304 blob_from_path_and_stat,
305@@ -737,7 +737,9 @@
306 target_extras = set()
307 ret = delta.TreeDelta()
308 added = []
309- for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
310+ for (change_type, old, new) in changes:
311+ (oldpath, oldmode, oldsha) = old
312+ (newpath, newmode, newsha) = new
313 if newpath == b'' and not include_root:
314 continue
315 if oldpath is not None:
316@@ -809,13 +811,17 @@
317 fileid, (oldpath_decoded, newpath_decoded), (oldsha != newsha),
318 (oldversioned, newversioned),
319 (oldparent, newparent), (oldname, newname),
320- (oldkind, newkind), (oldexe, newexe))
321+ (oldkind, newkind), (oldexe, newexe),
322+ copied=(change_type == 'copy'))
323 if oldpath is None:
324 added.append((newpath, newkind))
325 elif newpath is None or newmode == 0:
326 ret.removed.append(change)
327 elif oldpath != newpath:
328- ret.renamed.append(change)
329+ if change_type == 'copy':
330+ ret.copied.append(change)
331+ else:
332+ ret.renamed.append(change)
333 elif mode_kind(oldmode) != mode_kind(newmode):
334 ret.kind_changed.append(change)
335 elif oldsha != newsha or oldmode != newmode:
336@@ -863,7 +869,9 @@
337 """
338 if target_extras is None:
339 target_extras = set()
340- for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
341+ for (change_type, old, new) in changes:
342+ (oldpath, oldmode, oldsha) = old
343+ (newpath, newmode, newsha) = new
344 if oldpath is not None:
345 oldpath_decoded = oldpath.decode('utf-8')
346 else:
347@@ -934,7 +942,8 @@
348 fileid, (oldpath_decoded, newpath_decoded), (oldsha != newsha),
349 (oldversioned, newversioned),
350 (oldparent, newparent), (oldname, newname),
351- (oldkind, newkind), (oldexe, newexe))
352+ (oldkind, newkind), (oldexe, newexe),
353+ copied=(change_type == 'copy'))
354
355
356 class InterGitTrees(_mod_tree.InterTree):
357@@ -997,7 +1006,9 @@
358 paths = set(paths)
359 ret = {}
360 changes = self._iter_git_changes(specific_files=paths)[0]
361- for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
362+ for (change_type, old, new) in changes:
363+ oldpath = old[0]
364+ newpath = new[0]
365 if oldpath in paths:
366 ret[oldpath] = newpath
367 for path in paths:
368@@ -1015,7 +1026,9 @@
369 paths = set(paths)
370 ret = {}
371 changes = self._iter_git_changes(specific_files=paths)[0]
372- for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
373+ for (change_type, old, new) in changes:
374+ oldpath = old[0]
375+ newpath = new[0]
376 if newpath in paths:
377 ret[newpath] = oldpath
378 for path in paths:
379@@ -1060,9 +1073,10 @@
380 self.target._repository._git.object_store])
381 else:
382 store = self.source._repository._git.object_store
383- return store.tree_changes(
384- self.source.tree, self.target.tree, want_unchanged=want_unchanged,
385- include_trees=True, change_type_same=True), set()
386+ rename_detector = RenameDetector(store)
387+ return tree_changes(
388+ store, self.source.tree, self.target.tree, want_unchanged=want_unchanged,
389+ include_trees=True, change_type_same=True, rename_detector=rename_detector), set()
390
391
392 _mod_tree.InterTree.register_optimiser(InterGitRevisionTrees)
393@@ -1601,6 +1615,12 @@
394 def __init__(self, source, target):
395 super(InterIndexGitTree, self).__init__(source, target)
396 self._index = target.index
397+ if self.source.store == self.target.store:
398+ self.store = self.source.store
399+ else:
400+ self.store = OverlayObjectStore(
401+ [self.source.store, self.target.store])
402+ self.rename_detector = RenameDetector(self.store)
403
404 @classmethod
405 def is_compatible(cls, source, target):
406@@ -1622,15 +1642,17 @@
407 return changes_between_git_tree_and_working_copy(
408 self.source.store, self.source.tree,
409 self.target, want_unchanged=want_unchanged,
410- want_unversioned=want_unversioned)
411+ want_unversioned=want_unversioned,
412+ rename_detector=self.rename_detector)
413
414
415 _mod_tree.InterTree.register_optimiser(InterIndexGitTree)
416
417
418-def changes_between_git_tree_and_working_copy(store, from_tree_sha, target,
419+def changes_between_git_tree_and_working_copy(source_store, from_tree_sha, target,
420 want_unchanged=False,
421- want_unversioned=False):
422+ want_unversioned=False,
423+ rename_detector=None):
424 """Determine the changes between a git tree and a working tree with index.
425
426 """
427@@ -1657,7 +1679,7 @@
428 blobs[path] = (index_entry.sha, index_entry.mode)
429 else:
430 dirified.append((path, Tree().id, stat.S_IFDIR))
431- store.add_object(Tree())
432+ target.store.add_object(Tree())
433 else:
434 mode = live_entry.mode
435 if not trust_executable:
436@@ -1665,6 +1687,19 @@
437 mode |= 0o111
438 else:
439 mode &= ~0o111
440+ if live_entry.sha != index_entry.sha:
441+ rp = path.decode('utf-8')
442+ if stat.S_ISREG(live_entry.mode):
443+ blob = Blob()
444+ with target.get_file(rp) as f:
445+ blob.data = f.read()
446+ elif stat.S_ISLNK(live_entry.mode):
447+ blob = Blob()
448+ blob.data = target.get_symlink_target(rp).encode(osutils._fs_enc)
449+ else:
450+ blob = None
451+ if blob is not None:
452+ target.store.add_object(blob)
453 blobs[path] = (live_entry.sha, cleanup_mode(live_entry.mode))
454 if want_unversioned:
455 for e in target.extras():
456@@ -1681,12 +1716,14 @@
457 target.abspath(e).encode(osutils._fs_enc), st)
458 else:
459 continue
460- store.add_object(blob)
461+ target.store.add_object(blob)
462 np = np.encode('utf-8')
463 blobs[np] = (blob.id, cleanup_mode(st.st_mode))
464 extras.add(np)
465 to_tree_sha = commit_tree(
466- store, dirified + [(p, s, m) for (p, (s, m)) in blobs.items()])
467- return store.tree_changes(
468- from_tree_sha, to_tree_sha, include_trees=True,
469+ target.store, dirified + [(p, s, m) for (p, (s, m)) in blobs.items()])
470+ store = OverlayObjectStore([source_store, target.store])
471+ return tree_changes(
472+ store, from_tree_sha, to_tree_sha, include_trees=True,
473+ rename_detector=rename_detector,
474 want_unchanged=want_unchanged, change_type_same=True), extras
475
476=== modified file 'breezy/tests/per_repository/test_commit_builder.py'
477--- breezy/tests/per_repository/test_commit_builder.py 2019-06-29 13:16:26 +0000
478+++ breezy/tests/per_repository/test_commit_builder.py 2020-06-16 23:49:21 +0000
479@@ -498,11 +498,11 @@
480 builder.abort()
481 raise
482 delta = builder.get_basis_delta()
483- delta_dict = dict((change[2], change) for change in delta)
484+ delta_dict = dict((change[1], change) for change in delta)
485 if tree.branch.repository._format.records_per_file_revision:
486- version_recorded = (file_id in delta_dict
487- and delta_dict[file_id][3] is not None
488- and delta_dict[file_id][3].revision == rev2)
489+ version_recorded = (new_name in delta_dict
490+ and delta_dict[new_name][3] is not None
491+ and delta_dict[new_name][3].revision == rev2)
492 if records_version:
493 self.assertTrue(version_recorded)
494 else:
495@@ -513,11 +513,24 @@
496 specific_files=[new_name]))[1]
497
498 if delta_against_basis:
499- if tree.supports_rename_tracking() or name == new_name:
500- expected_delta = (name, new_name, file_id, new_entry)
501+ (delta_old_name, delta_new_name,
502+ delta_file_id, delta_entry) = delta_dict[new_name]
503+ self.assertEqual(delta_new_name, new_name)
504+ if tree.supports_rename_tracking():
505+ self.assertEqual(name, delta_old_name)
506 else:
507- expected_delta = (None, new_name, file_id, new_entry)
508- self.assertEqual(expected_delta, delta_dict[file_id])
509+ self.assertIn(delta_old_name, (name, None))
510+ if tree.supports_setting_file_ids():
511+ self.assertEqual(delta_file_id, file_id)
512+ self.assertEqual(delta_entry.file_id, file_id)
513+ self.assertEqual(delta_entry.kind, new_entry.kind)
514+ self.assertEqual(delta_entry.name, new_entry.name)
515+ self.assertEqual(delta_entry.parent_id, new_entry.parent_id)
516+ if delta_entry.kind == 'file':
517+ self.assertEqual(delta_entry.text_size, new_entry.text_size)
518+ self.assertEqual(delta_entry.text_sha1, new_entry.text_sha1)
519+ elif delta_entry.kind == 'symlink':
520+ self.assertEqual(delta_entry.symlink_target, new_entry.symlink_target)
521 else:
522 expected_delta = None
523 if tree.branch.repository._format.records_per_file_revision:
524
525=== modified file 'doc/en/release-notes/brz-3.1.txt'
526--- doc/en/release-notes/brz-3.1.txt 2020-06-16 01:38:06 +0000
527+++ doc/en/release-notes/brz-3.1.txt 2020-06-16 23:49:21 +0000
528@@ -32,6 +32,9 @@
529 * Permission denied errors from GitLab during push are now properly
530 recognized. (Jelmer Vernooij)
531
532+ * Support rename and copy tracking when accessing Git
533+ repositories. (Jelmer Vernooij, #1760740)
534+
535 Bug Fixes
536 *********
537
538@@ -41,7 +44,6 @@
539 * Don't require ``ctypes.pythonapi`` to exist, as it's missing on newer
540 versions of Pypy3. (Jelmer Vernooij)
541
542-
543 Documentation
544 *************
545
546@@ -73,9 +75,6 @@
547 * Tests for most bzr-specific functionality has been moved to the
548 ``breezy.bzr.tests`` module. (Jelmer Vernooij)
549
550-..
551- vim: tw=74 ft=rst ff=unix
552-
553 brz 3.1.0
554 #########
555

Subscribers

People subscribed via source and target branches