dulwich-users team mailing list archive
-
dulwich-users team
-
Mailing list archive
-
Message #00615
[PATCH 18/34] walk: Add option to follow paths across rename/copy.
From: Dave Borowitz <dborowitz@xxxxxxxxxx>
Change-Id: I472ede8e2854e9c7b57c372f3ed67b227c297392
---
dulwich/diff_tree.py | 2 ++
dulwich/tests/test_walk.py | 34 ++++++++++++++++++++++++++++++++++
dulwich/walk.py | 25 ++++++++++++++++++++-----
3 files changed, 56 insertions(+), 5 deletions(-)
diff --git a/dulwich/diff_tree.py b/dulwich/diff_tree.py
index 9a53ff8..eb0b152 100644
--- a/dulwich/diff_tree.py
+++ b/dulwich/diff_tree.py
@@ -39,6 +39,8 @@ CHANGE_RENAME = 'rename'
CHANGE_COPY = 'copy'
CHANGE_UNCHANGED = 'unchanged'
+RENAME_CHANGE_TYPES = (CHANGE_RENAME, CHANGE_COPY)
+
_NULL_ENTRY = TreeEntry(None, None, None)
_MAX_SCORE = 100
diff --git a/dulwich/tests/test_walk.py b/dulwich/tests/test_walk.py
index 36f0dbc..2db99b3 100644
--- a/dulwich/tests/test_walk.py
+++ b/dulwich/tests/test_walk.py
@@ -22,6 +22,7 @@ from dulwich.diff_tree import (
CHANGE_ADD,
CHANGE_MODIFY,
CHANGE_RENAME,
+ CHANGE_COPY,
TreeChange,
RenameDetector,
)
@@ -239,3 +240,36 @@ class WalkerTest(TestCase):
self.assertWalkYields(
[TestWalkEntry(c2, changes_with_renames)], [c2.id], max_entries=1,
rename_detector=detector)
+
+ def test_follow_rename(self):
+ blob = make_object(Blob, data='blob')
+ names = ['a', 'a', 'b', 'b', 'c', 'c']
+
+ trees = dict((i + 1, [(n, blob, F)]) for i, n in enumerate(names))
+ c1, c2, c3, c4, c5, c6 = self.make_linear_commits(6, trees=trees)
+ self.assertWalkYields([c5], [c6.id], paths=['c'])
+
+ e = lambda n: (n, F, blob.id)
+ self.assertWalkYields(
+ [TestWalkEntry(c5, [TreeChange(CHANGE_RENAME, e('b'), e('c'))]),
+ TestWalkEntry(c3, [TreeChange(CHANGE_RENAME, e('a'), e('b'))]),
+ TestWalkEntry(c1, [TreeChange.add(e('a'))])],
+ [c6.id], paths=['c'], follow=True)
+
+ def test_follow_rename_remove_path(self):
+ blob = make_object(Blob, data='blob')
+ _, _, _, c4, c5, c6 = self.make_linear_commits(
+ 6, trees={1: [('a', blob), ('c', blob)],
+ 2: [],
+ 3: [],
+ 4: [('b', blob)],
+ 5: [('a', blob)],
+ 6: [('c', blob)]})
+
+ e = lambda n: (n, F, blob.id)
+ # Once the path changes to b, we aren't interested in a or c anymore.
+ self.assertWalkYields(
+ [TestWalkEntry(c6, [TreeChange(CHANGE_RENAME, e('a'), e('c'))]),
+ TestWalkEntry(c5, [TreeChange(CHANGE_RENAME, e('b'), e('a'))]),
+ TestWalkEntry(c4, [TreeChange.add(e('b'))])],
+ [c6.id], paths=['c'], follow=True)
diff --git a/dulwich/walk.py b/dulwich/walk.py
index 5d2a573..9bc16a4 100644
--- a/dulwich/walk.py
+++ b/dulwich/walk.py
@@ -23,8 +23,10 @@ import itertools
import os
from dulwich.diff_tree import (
+ RENAME_CHANGE_TYPES,
tree_changes,
tree_changes_for_merge,
+ RenameDetector,
)
ORDER_DATE = 'date'
@@ -77,7 +79,7 @@ class Walker(object):
def __init__(self, store, include, exclude=None, order=ORDER_DATE,
reverse=False, max_entries=None, paths=None,
- rename_detector=None):
+ rename_detector=None, follow=False):
"""Constructor.
:param store: ObjectStore instance for looking up objects.
@@ -94,6 +96,8 @@ class Walker(object):
:param paths: Iterable of file or subtree paths to show entries for.
:param rename_detector: diff.RenameDetector object for detecting
renames.
+ :param follow: If True, follow path across renames/copies. Forces a
+ default rename_detector.
"""
self._store = store
@@ -103,6 +107,8 @@ class Walker(object):
self._reverse = reverse
self._max_entries = max_entries
self._num_entries = 0
+ if follow and not rename_detector:
+ rename_detector = RenameDetector(store)
self._rename_detector = rename_detector
exclude = exclude or []
@@ -110,7 +116,8 @@ class Walker(object):
self._pq = []
self._pq_set = set()
self._done = set()
- self._paths = paths and list(paths) or None
+ self._paths = paths and set(paths) or None
+ self._follow = follow
for commit_id in itertools.chain(include, exclude):
self._push(store[commit_id])
@@ -153,15 +160,23 @@ class Walker(object):
return False
def _change_matches(self, change):
- return (self._path_matches(change.old.path) or
- self._path_matches(change.new.path))
+ old_path = change.old.path
+ new_path = change.new.path
+ if self._path_matches(new_path):
+ if self._follow and change.type in RENAME_CHANGE_TYPES:
+ self._paths.add(old_path)
+ self._paths.remove(new_path)
+ return True
+ elif self._path_matches(old_path):
+ return True
+ return False
def _make_entry(self, commit):
"""Make a WalkEntry from a commit.
:param commit: The commit for the WalkEntry.
:return: A WalkEntry object, or None if no entry should be returned for
- this commit (e.g. if it doesn't match any requested paths).
+ this commit (e.g. if it doesn't match any requested paths).
"""
entry = WalkEntry(self._store, commit, self._rename_detector)
if self._paths is None:
--
1.7.3.1
References