Skip to content

Commit 47cc5a0

Browse files
committed
Add FilterList.apply_to_buffer, apply_to_file, apply_to_blob
1 parent fe86852 commit 47cc5a0

File tree

4 files changed

+125
-8
lines changed

4 files changed

+125
-8
lines changed

pygit2/_libgit2/ffi.pyi

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ class GitBlameC:
123123
# incomplete
124124
pass
125125

126+
class GitBlobC:
127+
# incomplete
128+
pass
129+
126130
class GitMergeOptionsC:
127131
file_favor: int
128132
flags: int
@@ -268,6 +272,8 @@ def new(a: Literal['git_oid *']) -> GitOidC: ...
268272
@overload
269273
def new(a: Literal['git_blame **']) -> _Pointer[GitBlameC]: ...
270274
@overload
275+
def new(a: Literal['git_blob **']) -> _Pointer[GitBlobC]: ...
276+
@overload
271277
def new(a: Literal['git_clone_options *']) -> GitCloneOptionsC: ...
272278
@overload
273279
def new(a: Literal['git_merge_options *']) -> GitMergeOptionsC: ...

pygit2/decl/filter.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,23 @@ int git_filter_list_contains(
2626
git_filter_list *filters,
2727
const char *name);
2828

29+
int git_filter_list_apply_to_buffer(
30+
git_buf *out,
31+
git_filter_list *filters,
32+
const char* in,
33+
size_t in_len);
34+
35+
int git_filter_list_apply_to_file(
36+
git_buf *out,
37+
git_filter_list *filters,
38+
git_repository *repo,
39+
const char *path);
40+
41+
int git_filter_list_apply_to_blob(
42+
git_buf *out,
43+
git_filter_list *filters,
44+
git_blob *blob);
45+
2946
size_t git_filter_list_length(
3047
const git_filter_list *fl);
3148

pygit2/filter.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@
2929
from collections.abc import Callable
3030
from typing import TYPE_CHECKING
3131

32-
from ._pygit2 import FilterSource
32+
from ._pygit2 import Blob, FilterSource
33+
from .errors import check_error
3334
from .ffi import C, ffi
3435
from .utils import to_bytes
3536

3637
if TYPE_CHECKING:
3738
from ._libgit2.ffi import GitFilterListC
39+
from .repository import BaseRepository
3840

3941

4042
class Filter:
@@ -157,5 +159,49 @@ def __contains__(self, name: str) -> bool:
157159
def __len__(self) -> int:
158160
return C.git_filter_list_length(self._pointer)
159161

162+
def apply_to_buffer(self, data: bytes) -> bytes:
163+
"""
164+
Apply a filter list to a data buffer.
165+
Return the filtered contents.
166+
"""
167+
buf = ffi.new('git_buf *')
168+
err = C.git_filter_list_apply_to_buffer(buf, self._pointer, data, len(data))
169+
check_error(err)
170+
try:
171+
return ffi.string(buf.ptr)
172+
finally:
173+
C.git_buf_dispose(buf)
174+
175+
def apply_to_file(self, repo: BaseRepository, path: str) -> bytes:
176+
"""
177+
Apply a filter list to the contents of a file on disk.
178+
Return the filtered contents.
179+
"""
180+
buf = ffi.new('git_buf *')
181+
c_path = to_bytes(path)
182+
err = C.git_filter_list_apply_to_file(buf, self._pointer, repo._repo, c_path)
183+
check_error(err)
184+
try:
185+
return ffi.string(buf.ptr)
186+
finally:
187+
C.git_buf_dispose(buf)
188+
189+
def apply_to_blob(self, blob: Blob) -> bytes:
190+
"""
191+
Apply a filter list to a data buffer.
192+
Return the filtered contents.
193+
"""
194+
buf = ffi.new('git_buf *')
195+
196+
c_blob = ffi.new('git_blob **')
197+
ffi.buffer(c_blob)[:] = blob._pointer[:]
198+
199+
err = C.git_filter_list_apply_to_blob(buf, self._pointer, c_blob[0])
200+
check_error(err)
201+
try:
202+
return ffi.string(buf.ptr)
203+
finally:
204+
C.git_buf_dispose(buf)
205+
160206
def __del__(self):
161207
C.git_filter_list_free(self._pointer)

test/test_filter.py

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import pygit2
99
from pygit2 import Blob, Filter, FilterSource, Repository
10-
from pygit2.enums import BlobFilter
10+
from pygit2.enums import BlobFilter, FilterMode
1111
from pygit2.errors import Passthrough
1212

1313

@@ -146,22 +146,31 @@ def test_filterlist_none(testrepo: Repository) -> None:
146146
assert fl is None
147147

148148

149-
def test_filterlist_crlf(testrepo: Repository) -> None:
149+
def test_filterlist_apply_to_buffer_crlf_clean(testrepo: Repository) -> None:
150150
testrepo.config['core.autocrlf'] = True
151151

152-
fl = testrepo.load_filter_list('hello.txt')
152+
fl = testrepo.load_filter_list('whatever.txt', mode=FilterMode.CLEAN)
153153
assert fl is not None
154154
assert len(fl) == 1
155155
assert 'crlf' in fl
156-
156+
assert 'bogus_filter_name' not in fl
157157
with pytest.raises(TypeError):
158158
1234 in fl # type: ignore
159159

160+
filtered = fl.apply_to_buffer(b'hello\r\nworld\r\n')
161+
assert filtered == b'hello\nworld\n'
160162

161-
def test_filterlist_rot13(testrepo: Repository, rot13_filter: Filter) -> None:
162-
fl = testrepo.load_filter_list('hello.txt')
163+
164+
def test_filterlist_apply_to_buffer_crlf_smudge(testrepo: Repository) -> None:
165+
testrepo.config['core.autocrlf'] = True
166+
167+
fl = testrepo.load_filter_list('whatever.txt', mode=FilterMode.SMUDGE)
163168
assert fl is not None
164-
assert 'rot13' in fl
169+
assert len(fl) == 1
170+
assert 'crlf' in fl
171+
172+
filtered = fl.apply_to_buffer(b'hello\nworld\n')
173+
assert filtered == b'hello\r\nworld\r\n'
165174

166175

167176
def test_filterlist_dangerous_unregister(testrepo: Repository) -> None:
@@ -182,3 +191,42 @@ def test_filterlist_dangerous_unregister(testrepo: Repository) -> None:
182191
del fl
183192
gc.collect()
184193
pygit2.filter_unregister('rot13')
194+
195+
196+
def test_filterlist_apply_to_file(testrepo: Repository, rot13_filter: Filter) -> None:
197+
fl = testrepo.load_filter_list('bye.txt')
198+
assert fl is not None
199+
assert len(fl) == 1
200+
assert 'rot13' in fl
201+
202+
filtered = fl.apply_to_file(testrepo, 'bye.txt')
203+
assert filtered == b'olr jbeyq\n'
204+
205+
206+
def test_filterlist_apply_to_blob(testrepo: Repository, rot13_filter: Filter) -> None:
207+
fl = testrepo.load_filter_list('whatever.txt')
208+
assert fl is not None
209+
assert len(fl) == 1
210+
assert 'rot13' in fl
211+
212+
blob_oid = testrepo.create_blob(b'bye world\n')
213+
blob = testrepo[blob_oid]
214+
assert isinstance(blob, Blob)
215+
216+
filtered = fl.apply_to_blob(blob)
217+
assert filtered == b'olr jbeyq\n'
218+
219+
220+
def test_filterlist_apply_to_buffer_multiple(
221+
testrepo: Repository, rot13_filter: Filter
222+
) -> None:
223+
testrepo.config['core.autocrlf'] = True
224+
225+
fl = testrepo.load_filter_list('whatever.txt')
226+
assert fl is not None
227+
assert len(fl) == 2
228+
assert 'crlf' in fl
229+
assert 'rot13' in fl
230+
231+
filtered = fl.apply_to_buffer(b'bye\r\nworld\r\n')
232+
assert filtered == b'olr\njbeyq\n'

0 commit comments

Comments
 (0)