← Back to team overview

dulwich-users team mailing list archive

[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