Statistics
| Revision:

gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / dulwich / tests / test_walk.py @ 959

History | View | Annotate | Download (21.7 KB)

1
# test_walk.py -- Tests for commit walking functionality.
2
# Copyright (C) 2010 Google, Inc.
3
#
4
# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
5
# General Public License as public by the Free Software Foundation; version 2.0
6
# or (at your option) any later version. You can redistribute it and/or
7
# modify it under the terms of either of these two licenses.
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14
#
15
# You should have received a copy of the licenses; if not, see
16
# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
17
# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
18
# License, Version 2.0.
19
#
20

    
21
"""Tests for commit walking functionality."""
22

    
23
from itertools import (
24
    permutations,
25
    )
26

    
27
from dulwich.diff_tree import (
28
    CHANGE_ADD,
29
    CHANGE_MODIFY,
30
    CHANGE_RENAME,
31
    TreeChange,
32
    RenameDetector,
33
    )
34
from dulwich.errors import (
35
    MissingCommitError,
36
    )
37
from dulwich.object_store import (
38
    MemoryObjectStore,
39
    )
40
from dulwich.objects import (
41
    Commit,
42
    Blob,
43
    )
44
from dulwich.walk import (
45
    ORDER_TOPO,
46
    WalkEntry,
47
    Walker,
48
    _topo_reorder
49
    )
50
from dulwich.tests import TestCase
51
from dulwich.tests.utils import (
52
    F,
53
    make_object,
54
    build_commit_graph,
55
    )
56

    
57

    
58
class TestWalkEntry(object):
59

    
60
    def __init__(self, commit, changes):
61
        self.commit = commit
62
        self.changes = changes
63

    
64
    def __repr__(self):
65
        return '<TestWalkEntry commit=%s, changes=%r>' % (
66
          self.commit.id, self.changes)
67

    
68
    def __eq__(self, other):
69
        if not isinstance(other, WalkEntry) or self.commit != other.commit:
70
            return False
71
        if self.changes is None:
72
            return True
73
        return self.changes == other.changes()
74

    
75

    
76
class WalkerTest(TestCase):
77

    
78
    def setUp(self):
79
        super(WalkerTest, self).setUp()
80
        self.store = MemoryObjectStore()
81

    
82
    def make_commits(self, commit_spec, **kwargs):
83
        times = kwargs.pop('times', [])
84
        attrs = kwargs.pop('attrs', {})
85
        for i, t in enumerate(times):
86
            attrs.setdefault(i + 1, {})['commit_time'] = t
87
        return build_commit_graph(self.store, commit_spec, attrs=attrs,
88
                                  **kwargs)
89

    
90
    def make_linear_commits(self, num_commits, **kwargs):
91
        commit_spec = []
92
        for i in range(1, num_commits + 1):
93
            c = [i]
94
            if i > 1:
95
                c.append(i - 1)
96
            commit_spec.append(c)
97
        return self.make_commits(commit_spec, **kwargs)
98

    
99
    def assertWalkYields(self, expected, *args, **kwargs):
100
        walker = Walker(self.store, *args, **kwargs)
101
        expected = list(expected)
102
        for i, entry in enumerate(expected):
103
            if isinstance(entry, Commit):
104
                expected[i] = TestWalkEntry(entry, None)
105
        actual = list(walker)
106
        self.assertEqual(expected, actual)
107

    
108
    def test_linear(self):
109
        c1, c2, c3 = self.make_linear_commits(3)
110
        self.assertWalkYields([c1], [c1.id])
111
        self.assertWalkYields([c2, c1], [c2.id])
112
        self.assertWalkYields([c3, c2, c1], [c3.id])
113
        self.assertWalkYields([c3, c2, c1], [c3.id, c1.id])
114
        self.assertWalkYields([c3, c2], [c3.id], exclude=[c1.id])
115
        self.assertWalkYields([c3, c2], [c3.id, c1.id], exclude=[c1.id])
116
        self.assertWalkYields([c3], [c3.id, c1.id], exclude=[c2.id])
117

    
118
    def test_missing(self):
119
        cs = list(reversed(self.make_linear_commits(20)))
120
        self.assertWalkYields(cs, [cs[0].id])
121

    
122
        # Exactly how close we can get to a missing commit depends on our
123
        # implementation (in particular the choice of _MAX_EXTRA_COMMITS), but
124
        # we should at least be able to walk some history in a broken repo.
125
        del self.store[cs[-1].id]
126
        for i in range(1, 11):
127
            self.assertWalkYields(cs[:i], [cs[0].id], max_entries=i)
128
        self.assertRaises(MissingCommitError, Walker, self.store, [cs[-1].id])
129

    
130
    def test_branch(self):
131
        c1, x2, x3, y4 = self.make_commits([[1], [2, 1], [3, 2], [4, 1]])
132
        self.assertWalkYields([x3, x2, c1], [x3.id])
133
        self.assertWalkYields([y4, c1], [y4.id])
134
        self.assertWalkYields([y4, x2, c1], [y4.id, x2.id])
135
        self.assertWalkYields([y4, x2], [y4.id, x2.id], exclude=[c1.id])
136
        self.assertWalkYields([y4, x3], [y4.id, x3.id], exclude=[x2.id])
137
        self.assertWalkYields([y4], [y4.id], exclude=[x3.id])
138
        self.assertWalkYields([x3, x2], [x3.id], exclude=[y4.id])
139

    
140
    def test_merge(self):
141
        c1, c2, c3, c4 = self.make_commits([[1], [2, 1], [3, 1], [4, 2, 3]])
142
        self.assertWalkYields([c4, c3, c2, c1], [c4.id])
143
        self.assertWalkYields([c3, c1], [c3.id])
144
        self.assertWalkYields([c2, c1], [c2.id])
145
        self.assertWalkYields([c4, c3], [c4.id], exclude=[c2.id])
146
        self.assertWalkYields([c4, c2], [c4.id], exclude=[c3.id])
147

    
148
    def test_reverse(self):
149
        c1, c2, c3 = self.make_linear_commits(3)
150
        self.assertWalkYields([c1, c2, c3], [c3.id], reverse=True)
151

    
152
    def test_max_entries(self):
153
        c1, c2, c3 = self.make_linear_commits(3)
154
        self.assertWalkYields([c3, c2, c1], [c3.id], max_entries=3)
155
        self.assertWalkYields([c3, c2], [c3.id], max_entries=2)
156
        self.assertWalkYields([c3], [c3.id], max_entries=1)
157

    
158
    def test_reverse_after_max_entries(self):
159
        c1, c2, c3 = self.make_linear_commits(3)
160
        self.assertWalkYields([c1, c2, c3], [c3.id], max_entries=3,
161
                              reverse=True)
162
        self.assertWalkYields([c2, c3], [c3.id], max_entries=2, reverse=True)
163
        self.assertWalkYields([c3], [c3.id], max_entries=1, reverse=True)
164

    
165
    def test_changes_one_parent(self):
166
        blob_a1 = make_object(Blob, data=b'a1')
167
        blob_a2 = make_object(Blob, data=b'a2')
168
        blob_b2 = make_object(Blob, data=b'b2')
169
        c1, c2 = self.make_linear_commits(
170
            2, trees={1: [(b'a', blob_a1)],
171
                      2: [(b'a', blob_a2), (b'b', blob_b2)]})
172
        e1 = TestWalkEntry(c1, [TreeChange.add((b'a', F, blob_a1.id))])
173
        e2 = TestWalkEntry(c2, [TreeChange(CHANGE_MODIFY, (b'a', F, blob_a1.id),
174
                                           (b'a', F, blob_a2.id)),
175
                                TreeChange.add((b'b', F, blob_b2.id))])
176
        self.assertWalkYields([e2, e1], [c2.id])
177

    
178
    def test_changes_multiple_parents(self):
179
        blob_a1 = make_object(Blob, data=b'a1')
180
        blob_b2 = make_object(Blob, data=b'b2')
181
        blob_a3 = make_object(Blob, data=b'a3')
182
        c1, c2, c3 = self.make_commits(
183
            [[1], [2], [3, 1, 2]],
184
            trees={1: [(b'a', blob_a1)], 2: [(b'b', blob_b2)],
185
                   3: [(b'a', blob_a3), (b'b', blob_b2)]})
186
        # a is a modify/add conflict and b is not conflicted.
187
        changes = [[
188
            TreeChange(CHANGE_MODIFY, (b'a', F, blob_a1.id), (b'a', F, blob_a3.id)),
189
            TreeChange.add((b'a', F, blob_a3.id)),
190
        ]]
191
        self.assertWalkYields([TestWalkEntry(c3, changes)], [c3.id],
192
                              exclude=[c1.id, c2.id])
193

    
194
    def test_path_matches(self):
195
        walker = Walker(None, [], paths=[b'foo', b'bar', b'baz/quux'])
196
        self.assertTrue(walker._path_matches(b'foo'))
197
        self.assertTrue(walker._path_matches(b'foo/a'))
198
        self.assertTrue(walker._path_matches(b'foo/a/b'))
199
        self.assertTrue(walker._path_matches(b'bar'))
200
        self.assertTrue(walker._path_matches(b'baz/quux'))
201
        self.assertTrue(walker._path_matches(b'baz/quux/a'))
202

    
203
        self.assertFalse(walker._path_matches(None))
204
        self.assertFalse(walker._path_matches(b'oops'))
205
        self.assertFalse(walker._path_matches(b'fool'))
206
        self.assertFalse(walker._path_matches(b'baz'))
207
        self.assertFalse(walker._path_matches(b'baz/quu'))
208

    
209
    def test_paths(self):
210
        blob_a1 = make_object(Blob, data=b'a1')
211
        blob_b2 = make_object(Blob, data=b'b2')
212
        blob_a3 = make_object(Blob, data=b'a3')
213
        blob_b3 = make_object(Blob, data=b'b3')
214
        c1, c2, c3 = self.make_linear_commits(
215
            3, trees={1: [(b'a', blob_a1)],
216
                      2: [(b'a', blob_a1), (b'x/b', blob_b2)],
217
                      3: [(b'a', blob_a3), (b'x/b', blob_b3)]})
218

    
219
        self.assertWalkYields([c3, c2, c1], [c3.id])
220
        self.assertWalkYields([c3, c1], [c3.id], paths=[b'a'])
221
        self.assertWalkYields([c3, c2], [c3.id], paths=[b'x/b'])
222

    
223
        # All changes are included, not just for requested paths.
224
        changes = [
225
            TreeChange(CHANGE_MODIFY, (b'a', F, blob_a1.id),
226
                       (b'a', F, blob_a3.id)),
227
            TreeChange(CHANGE_MODIFY, (b'x/b', F, blob_b2.id),
228
                       (b'x/b', F, blob_b3.id)),
229
        ]
230
        self.assertWalkYields([TestWalkEntry(c3, changes)], [c3.id],
231
                              max_entries=1, paths=[b'a'])
232

    
233
    def test_paths_subtree(self):
234
        blob_a = make_object(Blob, data=b'a')
235
        blob_b = make_object(Blob, data=b'b')
236
        c1, c2, c3 = self.make_linear_commits(
237
            3, trees={1: [(b'x/a', blob_a)],
238
                      2: [(b'b', blob_b), (b'x/a', blob_a)],
239
                      3: [(b'b', blob_b), (b'x/a', blob_a), (b'x/b', blob_b)]})
240
        self.assertWalkYields([c2], [c3.id], paths=[b'b'])
241
        self.assertWalkYields([c3, c1], [c3.id], paths=[b'x'])
242

    
243
    def test_paths_max_entries(self):
244
        blob_a = make_object(Blob, data=b'a')
245
        blob_b = make_object(Blob, data=b'b')
246
        c1, c2 = self.make_linear_commits(
247
            2, trees={1: [(b'a', blob_a)],
248
                      2: [(b'a', blob_a), (b'b', blob_b)]})
249
        self.assertWalkYields([c2], [c2.id], paths=[b'b'], max_entries=1)
250
        self.assertWalkYields([c1], [c1.id], paths=[b'a'], max_entries=1)
251

    
252
    def test_paths_merge(self):
253
        blob_a1 = make_object(Blob, data=b'a1')
254
        blob_a2 = make_object(Blob, data=b'a2')
255
        blob_a3 = make_object(Blob, data=b'a3')
256
        x1, y2, m3, m4 = self.make_commits(
257
            [[1], [2], [3, 1, 2], [4, 1, 2]],
258
            trees={1: [(b'a', blob_a1)],
259
                   2: [(b'a', blob_a2)],
260
                   3: [(b'a', blob_a3)],
261
                   4: [(b'a', blob_a1)]})  # Non-conflicting
262
        self.assertWalkYields([m3, y2, x1], [m3.id], paths=[b'a'])
263
        self.assertWalkYields([y2, x1], [m4.id], paths=[b'a'])
264

    
265
    def test_changes_with_renames(self):
266
        blob = make_object(Blob, data=b'blob')
267
        c1, c2 = self.make_linear_commits(
268
            2, trees={1: [(b'a', blob)], 2: [(b'b', blob)]})
269
        entry_a = (b'a', F, blob.id)
270
        entry_b = (b'b', F, blob.id)
271
        changes_without_renames = [TreeChange.delete(entry_a),
272
                                   TreeChange.add(entry_b)]
273
        changes_with_renames = [TreeChange(CHANGE_RENAME, entry_a, entry_b)]
274
        self.assertWalkYields(
275
          [TestWalkEntry(c2, changes_without_renames)], [c2.id], max_entries=1)
276
        detector = RenameDetector(self.store)
277
        self.assertWalkYields(
278
          [TestWalkEntry(c2, changes_with_renames)], [c2.id], max_entries=1,
279
          rename_detector=detector)
280

    
281
    def test_follow_rename(self):
282
        blob = make_object(Blob, data=b'blob')
283
        names = [b'a', b'a', b'b', b'b', b'c', b'c']
284

    
285
        trees = dict((i + 1, [(n, blob, F)]) for i, n in enumerate(names))
286
        c1, c2, c3, c4, c5, c6 = self.make_linear_commits(6, trees=trees)
287
        self.assertWalkYields([c5], [c6.id], paths=[b'c'])
288

    
289
        e = lambda n: (n, F, blob.id)
290
        self.assertWalkYields(
291
            [TestWalkEntry(c5, [TreeChange(CHANGE_RENAME, e(b'b'), e(b'c'))]),
292
             TestWalkEntry(c3, [TreeChange(CHANGE_RENAME, e(b'a'), e(b'b'))]),
293
             TestWalkEntry(c1, [TreeChange.add(e(b'a'))])],
294
            [c6.id], paths=[b'c'], follow=True)
295

    
296
    def test_follow_rename_remove_path(self):
297
        blob = make_object(Blob, data=b'blob')
298
        _, _, _, c4, c5, c6 = self.make_linear_commits(
299
            6, trees={1: [(b'a', blob), (b'c', blob)],
300
                      2: [],
301
                      3: [],
302
                      4: [(b'b', blob)],
303
                      5: [(b'a', blob)],
304
                      6: [(b'c', blob)]})
305

    
306
        e = lambda n: (n, F, blob.id)
307
        # Once the path changes to b, we aren't interested in a or c anymore.
308
        self.assertWalkYields(
309
            [TestWalkEntry(c6, [TreeChange(CHANGE_RENAME, e(b'a'), e(b'c'))]),
310
             TestWalkEntry(c5, [TreeChange(CHANGE_RENAME, e(b'b'), e(b'a'))]),
311
             TestWalkEntry(c4, [TreeChange.add(e(b'b'))])],
312
            [c6.id], paths=[b'c'], follow=True)
313

    
314
    def test_since(self):
315
        c1, c2, c3 = self.make_linear_commits(3)
316
        self.assertWalkYields([c3, c2, c1], [c3.id], since=-1)
317
        self.assertWalkYields([c3, c2, c1], [c3.id], since=0)
318
        self.assertWalkYields([c3, c2], [c3.id], since=1)
319
        self.assertWalkYields([c3, c2], [c3.id], since=99)
320
        self.assertWalkYields([c3, c2], [c3.id], since=100)
321
        self.assertWalkYields([c3], [c3.id], since=101)
322
        self.assertWalkYields([c3], [c3.id], since=199)
323
        self.assertWalkYields([c3], [c3.id], since=200)
324
        self.assertWalkYields([], [c3.id], since=201)
325
        self.assertWalkYields([], [c3.id], since=300)
326

    
327
    def test_until(self):
328
        c1, c2, c3 = self.make_linear_commits(3)
329
        self.assertWalkYields([], [c3.id], until=-1)
330
        self.assertWalkYields([c1], [c3.id], until=0)
331
        self.assertWalkYields([c1], [c3.id], until=1)
332
        self.assertWalkYields([c1], [c3.id], until=99)
333
        self.assertWalkYields([c2, c1], [c3.id], until=100)
334
        self.assertWalkYields([c2, c1], [c3.id], until=101)
335
        self.assertWalkYields([c2, c1], [c3.id], until=199)
336
        self.assertWalkYields([c3, c2, c1], [c3.id], until=200)
337
        self.assertWalkYields([c3, c2, c1], [c3.id], until=201)
338
        self.assertWalkYields([c3, c2, c1], [c3.id], until=300)
339

    
340
    def test_since_until(self):
341
        c1, c2, c3 = self.make_linear_commits(3)
342
        self.assertWalkYields([], [c3.id], since=100, until=99)
343
        self.assertWalkYields([c3, c2, c1], [c3.id], since=-1, until=201)
344
        self.assertWalkYields([c2], [c3.id], since=100, until=100)
345
        self.assertWalkYields([c2], [c3.id], since=50, until=150)
346

    
347
    def test_since_over_scan(self):
348
        commits = self.make_linear_commits(
349
          11, times=[9, 0, 1, 2, 3, 4, 5, 8, 6, 7, 9])
350
        c8, _, c10, c11 = commits[-4:]
351
        del self.store[commits[0].id]
352
        # c9 is older than we want to walk, but is out of order with its parent,
353
        # so we need to walk past it to get to c8.
354
        # c1 would also match, but we've deleted it, and it should get pruned
355
        # even with over-scanning.
356
        self.assertWalkYields([c11, c10, c8], [c11.id], since=7)
357

    
358
    def assertTopoOrderEqual(self, expected_commits, commits):
359
        entries = [TestWalkEntry(c, None) for c in commits]
360
        actual_ids = [e.commit.id for e in list(_topo_reorder(entries))]
361
        self.assertEqual([c.id for c in expected_commits], actual_ids)
362

    
363
    def test_topo_reorder_linear(self):
364
        commits = self.make_linear_commits(5)
365
        commits.reverse()
366
        for perm in permutations(commits):
367
            self.assertTopoOrderEqual(commits, perm)
368

    
369
    def test_topo_reorder_multiple_parents(self):
370
        c1, c2, c3 = self.make_commits([[1], [2], [3, 1, 2]])
371
        # Already sorted, so totally FIFO.
372
        self.assertTopoOrderEqual([c3, c2, c1], [c3, c2, c1])
373
        self.assertTopoOrderEqual([c3, c1, c2], [c3, c1, c2])
374

    
375
        # c3 causes one parent to be yielded.
376
        self.assertTopoOrderEqual([c3, c2, c1], [c2, c3, c1])
377
        self.assertTopoOrderEqual([c3, c1, c2], [c1, c3, c2])
378

    
379
        # c3 causes both parents to be yielded.
380
        self.assertTopoOrderEqual([c3, c2, c1], [c1, c2, c3])
381
        self.assertTopoOrderEqual([c3, c2, c1], [c2, c1, c3])
382

    
383
    def test_topo_reorder_multiple_children(self):
384
        c1, c2, c3 = self.make_commits([[1], [2, 1], [3, 1]])
385

    
386
        # c2 and c3 are FIFO but c1 moves to the end.
387
        self.assertTopoOrderEqual([c3, c2, c1], [c3, c2, c1])
388
        self.assertTopoOrderEqual([c3, c2, c1], [c3, c1, c2])
389
        self.assertTopoOrderEqual([c3, c2, c1], [c1, c3, c2])
390

    
391
        self.assertTopoOrderEqual([c2, c3, c1], [c2, c3, c1])
392
        self.assertTopoOrderEqual([c2, c3, c1], [c2, c1, c3])
393
        self.assertTopoOrderEqual([c2, c3, c1], [c1, c2, c3])
394

    
395
    def test_out_of_order_children(self):
396
        c1, c2, c3, c4, c5 = self.make_commits(
397
          [[1], [2, 1], [3, 2], [4, 1], [5, 3, 4]],
398
          times=[2, 1, 3, 4, 5])
399
        self.assertWalkYields([c5, c4, c3, c1, c2], [c5.id])
400
        self.assertWalkYields([c5, c4, c3, c2, c1], [c5.id], order=ORDER_TOPO)
401

    
402
    def test_out_of_order_with_exclude(self):
403
        # Create the following graph:
404
        # c1-------x2---m6
405
        #   \          /
406
        #    \-y3--y4-/--y5
407
        # Due to skew, y5 is the oldest commit.
408
        c1, x2, y3, y4, y5, m6 = self.make_commits(
409
          [[1], [2, 1], [3, 1], [4, 3], [5, 4], [6, 2, 4]],
410
          times=[2, 3, 4, 5, 1, 6])
411
        self.assertWalkYields([m6, y4, y3, x2, c1], [m6.id])
412
        # Ensure that c1..y4 get excluded even though they're popped from the
413
        # priority queue long before y5.
414
        self.assertWalkYields([m6, x2], [m6.id], exclude=[y5.id])
415

    
416
    def test_empty_walk(self):
417
        c1, c2, c3 = self.make_linear_commits(3)
418
        self.assertWalkYields([], [c3.id], exclude=[c3.id])
419

    
420

    
421
class WalkEntryTest(TestCase):
422

    
423
    def setUp(self):
424
        super(WalkEntryTest, self).setUp()
425
        self.store = MemoryObjectStore()
426

    
427
    def make_commits(self, commit_spec, **kwargs):
428
        times = kwargs.pop('times', [])
429
        attrs = kwargs.pop('attrs', {})
430
        for i, t in enumerate(times):
431
            attrs.setdefault(i + 1, {})['commit_time'] = t
432
        return build_commit_graph(self.store, commit_spec, attrs=attrs,
433
                                  **kwargs)
434

    
435
    def make_linear_commits(self, num_commits, **kwargs):
436
        commit_spec = []
437
        for i in range(1, num_commits + 1):
438
            c = [i]
439
            if i > 1:
440
                c.append(i - 1)
441
            commit_spec.append(c)
442
        return self.make_commits(commit_spec, **kwargs)
443

    
444
    def test_all_changes(self):
445
        # Construct a commit with 2 files in different subdirectories.
446
        blob_a = make_object(Blob, data=b'a')
447
        blob_b = make_object(Blob, data=b'b')
448
        c1 = self.make_linear_commits(
449
            1,
450
            trees={1: [(b'x/a', blob_a), (b'y/b', blob_b)]},
451
        )[0]
452

    
453
        # Get the WalkEntry for the commit.
454
        walker = Walker(self.store, c1.id)
455
        walker_entry = list(walker)[0]
456
        changes = walker_entry.changes()
457

    
458
        # Compare the changes with the expected values.
459
        entry_a = (b'x/a', F, blob_a.id)
460
        entry_b = (b'y/b', F, blob_b.id)
461
        self.assertEqual(
462
            [TreeChange.add(entry_a),
463
             TreeChange.add(entry_b)],
464
            changes,
465
        )
466

    
467
    def test_all_with_merge(self):
468
        blob_a = make_object(Blob, data=b'a')
469
        blob_a2 = make_object(Blob, data=b'a2')
470
        blob_b = make_object(Blob, data=b'b')
471
        blob_b2 = make_object(Blob, data=b'b2')
472
        x1, y2, m3 = self.make_commits(
473
            [[1], [2], [3, 1, 2]],
474
            trees={1: [(b'x/a', blob_a)],
475
                   2: [(b'y/b', blob_b)],
476
                   3: [(b'x/a', blob_a2), (b'y/b', blob_b2)]})
477

    
478
        # Get the WalkEntry for the merge commit.
479
        walker = Walker(self.store, m3.id)
480
        entries = list(walker)
481
        walker_entry = entries[0]
482
        self.assertEqual(walker_entry.commit.id, m3.id)
483
        changes = walker_entry.changes()
484
        self.assertEqual(2, len(changes))
485

    
486
        entry_a = (b'x/a', F, blob_a.id)
487
        entry_a2 = (b'x/a', F, blob_a2.id)
488
        entry_b = (b'y/b', F, blob_b.id)
489
        entry_b2 = (b'y/b', F, blob_b2.id)
490
        self.assertEqual(
491
            [[TreeChange(CHANGE_MODIFY, entry_a, entry_a2),
492
             TreeChange.add(entry_a2)],
493
            [TreeChange.add(entry_b2),
494
             TreeChange(CHANGE_MODIFY, entry_b, entry_b2)]],
495
            changes,
496
        )
497

    
498
    def test_filter_changes(self):
499
        # Construct a commit with 2 files in different subdirectories.
500
        blob_a = make_object(Blob, data=b'a')
501
        blob_b = make_object(Blob, data=b'b')
502
        c1 = self.make_linear_commits(
503
            1,
504
            trees={1: [(b'x/a', blob_a), (b'y/b', blob_b)]},
505
        )[0]
506

    
507
        # Get the WalkEntry for the commit.
508
        walker = Walker(self.store, c1.id)
509
        walker_entry = list(walker)[0]
510
        changes = walker_entry.changes(path_prefix=b'x')
511

    
512
        # Compare the changes with the expected values.
513
        entry_a = (b'a', F, blob_a.id)
514
        self.assertEqual(
515
            [TreeChange.add(entry_a)],
516
            changes,
517
        )
518

    
519
    def test_filter_with_merge(self):
520
        blob_a = make_object(Blob, data=b'a')
521
        blob_a2 = make_object(Blob, data=b'a2')
522
        blob_b = make_object(Blob, data=b'b')
523
        blob_b2 = make_object(Blob, data=b'b2')
524
        x1, y2, m3 = self.make_commits(
525
            [[1], [2], [3, 1, 2]],
526
            trees={1: [(b'x/a', blob_a)],
527
                   2: [(b'y/b', blob_b)],
528
                   3: [(b'x/a', blob_a2), (b'y/b', blob_b2)]})
529

    
530
        # Get the WalkEntry for the merge commit.
531
        walker = Walker(self.store, m3.id)
532
        entries = list(walker)
533
        walker_entry = entries[0]
534
        self.assertEqual(walker_entry.commit.id, m3.id)
535
        changes = walker_entry.changes(b'x')
536
        self.assertEqual(1, len(changes))
537

    
538
        entry_a = (b'a', F, blob_a.id)
539
        entry_a2 = (b'a', F, blob_a2.id)
540
        self.assertEqual(
541
            [[TreeChange(CHANGE_MODIFY, entry_a, entry_a2)]],
542
            changes,
543
        )