Skip to content

Commit 4737aff

Browse files
committed
Merge remote-tracking branch 'koordinates/accept-pathlike'
2 parents eabfce1 + 5d6feb1 commit 4737aff

File tree

15 files changed

+230
-29
lines changed

15 files changed

+230
-29
lines changed

pygit2/index.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def __contains__(self, path):
7878

7979
def __getitem__(self, key):
8080
centry = ffi.NULL
81-
if isinstance(key, str):
81+
if isinstance(key, str) or hasattr(key, '__fspath__'):
8282
centry = C.git_index_get_bypath(self._index, to_bytes(key), 0)
8383
elif isinstance(key, int):
8484
if key >= 0:
@@ -200,13 +200,13 @@ def add(self, path_or_entry):
200200
Index without checking for the existence of the path or id.
201201
"""
202202

203-
if isinstance(path_or_entry, str):
204-
path = path_or_entry
205-
err = C.git_index_add_bypath(self._index, to_bytes(path))
206-
elif isinstance(path_or_entry, IndexEntry):
203+
if isinstance(path_or_entry, IndexEntry):
207204
entry = path_or_entry
208205
centry, str_ref = entry._to_c()
209206
err = C.git_index_add(self._index, centry)
207+
elif isinstance(path_or_entry, str) or hasattr(path_or_entry, '__fspath__'):
208+
path = path_or_entry
209+
err = C.git_index_add_bypath(self._index, to_bytes(path))
210210
else:
211211
raise AttributeError('argument must be string or IndexEntry')
212212

pygit2/repository.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1280,6 +1280,9 @@ def compress(self):
12801280

12811281
class Repository(BaseRepository):
12821282
def __init__(self, path, *args, **kwargs):
1283+
if hasattr(path, "__fspath__"):
1284+
path = path.__fspath__()
1285+
12831286
if not isinstance(path, str):
12841287
path = path.decode('utf-8')
12851288

pygit2/utils.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
2424
# Boston, MA 02110-1301, USA.
2525

26+
import os
27+
2628
# Import from pygit2
2729
from .ffi import ffi
2830

@@ -31,13 +33,19 @@ def to_bytes(s, encoding='utf-8', errors='strict'):
3133
if s == ffi.NULL or s is None:
3234
return ffi.NULL
3335

36+
if hasattr(s, '__fspath__'):
37+
s = os.fspath(s)
38+
3439
if isinstance(s, bytes):
3540
return s
3641

3742
return s.encode(encoding, errors)
3843

3944

4045
def to_str(s):
46+
if hasattr(s, '__fspath__'):
47+
s = os.fspath(s)
48+
4149
if type(s) is str:
4250
return s
4351

@@ -76,10 +84,11 @@ def __init__(self, l):
7684

7785
strings = [None] * len(l)
7886
for i in range(len(l)):
79-
if not isinstance(l[i], str):
80-
raise TypeError("Value must be a string")
87+
li = l[i]
88+
if not isinstance(li, str) and not hasattr(li, '__fspath__'):
89+
raise TypeError("Value must be a string or PathLike object")
8190

82-
strings[i] = ffi.new('char []', to_bytes(l[i]))
91+
strings[i] = ffi.new('char []', to_bytes(li))
8392

8493
self._arr = ffi.new('char *[]', strings)
8594
self._strings = strings

src/pygit2.c

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,28 @@ PyObject *
8585
discover_repository(PyObject *self, PyObject *args)
8686
{
8787
git_buf repo_path = {NULL};
88-
const char *path;
89-
PyObject *py_repo_path;
88+
const char *path = NULL;
89+
PyBytesObject *py_path = NULL;
9090
int across_fs = 0;
91+
PyBytesObject *py_ceiling_dirs = NULL;
9192
const char *ceiling_dirs = NULL;
93+
PyObject *py_repo_path = NULL;
9294
int err;
9395

94-
if (!PyArg_ParseTuple(args, "s|Is", &path, &across_fs, &ceiling_dirs))
96+
if (!PyArg_ParseTuple(args, "O&|IO&", PyUnicode_FSConverter, &py_path, &across_fs, PyUnicode_FSConverter, &py_ceiling_dirs))
9597
return NULL;
9698

99+
if (py_path != NULL)
100+
path = PyBytes_AS_STRING(py_path);
101+
if (py_ceiling_dirs != NULL)
102+
ceiling_dirs = PyBytes_AS_STRING(py_ceiling_dirs);
103+
97104
memset(&repo_path, 0, sizeof(git_buf));
98105
err = git_repository_discover(&repo_path, path, across_fs, ceiling_dirs);
106+
107+
Py_XDECREF(py_path);
108+
Py_XDECREF(py_ceiling_dirs);
109+
99110
if (err == GIT_ENOTFOUND)
100111
Py_RETURN_NONE;
101112
if (err < 0)
@@ -116,13 +127,18 @@ PyObject *
116127
hashfile(PyObject *self, PyObject *args)
117128
{
118129
git_oid oid;
119-
const char* path;
130+
PyBytesObject *py_path = NULL;
131+
const char* path = NULL;
120132
int err;
121133

122-
if (!PyArg_ParseTuple(args, "s", &path))
134+
if (!PyArg_ParseTuple(args, "O&", PyUnicode_FSConverter, &py_path))
123135
return NULL;
124136

137+
if (py_path != NULL)
138+
path = PyBytes_AS_STRING(py_path);
139+
125140
err = git_odb_hashfile(&oid, path, GIT_OBJ_BLOB);
141+
Py_XDECREF(py_path);
126142
if (err < 0)
127143
return Error_set(err);
128144

@@ -161,14 +177,18 @@ PyDoc_STRVAR(init_file_backend__doc__,
161177
PyObject *
162178
init_file_backend(PyObject *self, PyObject *args)
163179
{
180+
PyBytesObject *py_path = NULL;
164181
const char* path = NULL;
165182
int err = GIT_OK;
166183
git_repository *repository = NULL;
167-
if (!PyArg_ParseTuple(args, "s", &path)) {
184+
if (!PyArg_ParseTuple(args, "O&", PyUnicode_FSConverter, &py_path)) {
168185
return NULL;
169186
}
187+
if (py_path != NULL)
188+
path = PyBytes_AS_STRING(py_path);
170189

171190
err = git_repository_open(&repository, path);
191+
Py_XDECREF(py_path);
172192
if (err < 0) {
173193
Error_set_str(err, path);
174194
goto cleanup;

src/repository.c

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -724,13 +724,18 @@ PyObject *
724724
Repository_create_blob_fromworkdir(Repository *self, PyObject *args)
725725
{
726726
git_oid oid;
727-
const char* path;
727+
PyBytesObject *py_path = NULL;
728+
const char* path = NULL;
728729
int err;
729730

730-
if (!PyArg_ParseTuple(args, "s", &path))
731+
if (!PyArg_ParseTuple(args, "O&", PyUnicode_FSConverter, &py_path))
731732
return NULL;
732733

734+
if (py_path != NULL)
735+
path = PyBytes_AS_STRING(py_path);
736+
733737
err = git_blob_create_fromworkdir(&oid, self->repo, path);
738+
Py_XDECREF(py_path);
734739
if (err < 0)
735740
return Error_set(err);
736741

@@ -747,13 +752,18 @@ PyObject *
747752
Repository_create_blob_fromdisk(Repository *self, PyObject *args)
748753
{
749754
git_oid oid;
750-
const char* path;
755+
PyBytesObject *py_path = NULL;
756+
const char* path = NULL;
751757
int err;
752758

753-
if (!PyArg_ParseTuple(args, "s", &path))
759+
if (!PyArg_ParseTuple(args, "O&", PyUnicode_FSConverter, &py_path))
754760
return NULL;
755761

762+
if (py_path != NULL)
763+
path = PyBytes_AS_STRING(py_path);
764+
756765
err = git_blob_create_fromdisk(&oid, self->repo, path);
766+
Py_XDECREF(py_path);
757767
if (err < 0)
758768
return Error_set(err);
759769

@@ -1757,20 +1767,25 @@ PyObject *
17571767
Repository_add_worktree(Repository *self, PyObject *args)
17581768
{
17591769
char *c_name;
1760-
char *c_path;
1770+
PyBytesObject *py_path = NULL;
1771+
char *c_path = NULL;
17611772
Reference *py_reference = NULL;
17621773
git_worktree *wt;
17631774
git_worktree_add_options add_opts = GIT_WORKTREE_ADD_OPTIONS_INIT;
1764-
1775+
17651776
int err;
17661777

1767-
if (!PyArg_ParseTuple(args, "ss|O!", &c_name, &c_path, &ReferenceType, &py_reference))
1778+
if (!PyArg_ParseTuple(args, "sO&|O!", &c_name, PyUnicode_FSConverter, &py_path, &ReferenceType, &py_reference))
17681779
return NULL;
17691780

1781+
if (py_path != NULL)
1782+
c_path = PyBytes_AS_STRING(py_path);
1783+
17701784
if(py_reference != NULL)
17711785
add_opts.ref = py_reference->reference;
1772-
1786+
17731787
err = git_worktree_add(&wt, self->repo, c_name, c_path, &add_opts);
1788+
Py_XDECREF(py_path);
17741789
if (err < 0)
17751790
return Error_set(err);
17761791

src/utils.c

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,19 +72,32 @@ pgit_encode_fsdefault(PyObject *value)
7272
const char*
7373
pgit_borrow_encoding(PyObject *value, const char *encoding, PyObject **tvalue)
7474
{
75-
PyObject *py_str;
75+
PyObject *py_value = NULL;
76+
PyObject *py_str = NULL;
77+
78+
#if defined(HAS_FSPATH_SUPPORT)
79+
py_value = PyOS_FSPath(value);
80+
if (py_value == NULL) {
81+
Error_type_error("unexpected %.200s", value);
82+
return NULL;
83+
}
84+
#else
85+
py_value = value;
86+
Py_INCREF(value);
87+
#endif
7688

7789
// Get new PyBytes reference from value
78-
if (PyUnicode_Check(value)) { // Text string
79-
py_str = (encoding) ? PyUnicode_AsEncodedString(value, encoding, "strict")
80-
: PyUnicode_AsUTF8String(value);
90+
if (PyUnicode_Check(py_value)) { // Text string
91+
py_str = (encoding) ? PyUnicode_AsEncodedString(py_value, encoding, "strict")
92+
: PyUnicode_AsUTF8String(py_value);
93+
Py_DECREF(py_value);
8194
if (py_str == NULL)
8295
return NULL;
83-
} else if (PyBytes_Check(value)) { // Byte string
84-
py_str = value;
85-
Py_INCREF(py_str);
96+
} else if (PyBytes_Check(py_value)) { // Byte string
97+
py_str = py_value;
8698
} else { // Type error
8799
Error_type_error("unexpected %.200s", value);
100+
Py_DECREF(py_value);
88101
return NULL;
89102
}
90103

src/utils.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@
3939
# define PYGIT2_FN_UNUSED
4040
#endif
4141

42+
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 6 && (!defined(PYPY_VERSION) || PYPY_VERSION_NUM >= 0x07030000)
43+
#define HAS_FSPATH_SUPPORT
44+
#endif
45+
4246
#define to_path(x) to_unicode(x, Py_FileSystemDefaultEncoding, "strict")
4347
#define to_encoding(x) PyUnicode_DecodeASCII(x, strlen(x), "strict")
4448

test/test_attributes.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
# Boston, MA 02110-1301, USA.
2525

2626
# Standard Library
27+
import unittest
2728
from os.path import join
29+
from pathlib import Path
2830

2931
# pygit2
3032
from . import utils
@@ -49,3 +51,10 @@ def test_no_attr(self):
4951
assert self.repo.get_attr('file.py', 'text')
5052
assert not self.repo.get_attr('file.jpg', 'text')
5153
assert "lf" == self.repo.get_attr('file.sh', 'eol')
54+
55+
@unittest.skipIf(not utils.has_fspath, "Requires PEP-519 (FSPath) support")
56+
def test_no_attr_aspath(self):
57+
with open(join(self.repo.workdir, '.gitattributes'), 'w+') as f:
58+
print('*.py text\n', file=f)
59+
60+
assert self.repo.get_attr(Path('file.py'), 'text')

test/test_blob.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
"""Tests for Blob objects."""
2727

2828
import io
29+
import unittest
30+
from pathlib import Path
2931

3032
import pytest
3133

@@ -126,6 +128,13 @@ def test_create_blob_fromworkdir(self):
126128
assert len(BLOB_FILE_CONTENT) == blob.size
127129
assert BLOB_FILE_CONTENT == blob.read_raw()
128130

131+
@unittest.skipIf(not utils.has_fspath, "Requires PEP-519 (FSPath) support")
132+
def test_create_blob_fromworkdir_aspath(self):
133+
134+
blob_oid = self.repo.create_blob_fromworkdir(Path("bye.txt"))
135+
blob = self.repo[blob_oid]
136+
137+
assert isinstance(blob, pygit2.Blob)
129138

130139
def test_create_blob_outside_workdir(self):
131140
with pytest.raises(KeyError):

test/test_config.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
# Boston, MA 02110-1301, USA.
2525

2626
import os
27+
import unittest
28+
from pathlib import Path
2729

2830
import pytest
2931

@@ -89,6 +91,17 @@ def test_add(self):
8991
assert 'something.other.here' in config
9092
assert not config.get_bool('something.other.here')
9193

94+
@unittest.skipIf(not utils.has_fspath, "Requires PEP-519 (FSPath) support")
95+
def test_add_aspath(self):
96+
config = Config()
97+
98+
new_file = open(CONFIG_FILENAME, "w")
99+
new_file.write("[this]\n\tthat = true\n")
100+
new_file.close()
101+
102+
config.add_file(Path(CONFIG_FILENAME), 0)
103+
assert 'this.that' in config
104+
92105
def test_read(self):
93106
config = self.repo.config
94107

0 commit comments

Comments
 (0)