Skip to content

Commit 7d14acb

Browse files
authored
3.13 support for Windows (#10667)
python/cpython#113829 changed `os.path.isabs` on Windows to not consider a path starting a single backslash to be an absolute path. We do a lot of naive `posixpath` to `ntpath` conversions, and it broke DVC in multiple places. DVC is likely broken in many other places, and these bugs are difficult to identify. In this commit, I have tried to fix in the places where the tests failed. And mostly by trying to imitate pre-3.13 behaviour. Some of the changes may not be correct but keeps the old behaviour in place. Also re-enabled the CI for all Python versions supported for Windows.
1 parent dd50155 commit 7d14acb

File tree

4 files changed

+51
-3
lines changed

4 files changed

+51
-3
lines changed

.github/workflows/tests.yaml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,30 @@ jobs:
6969
- os: windows-latest
7070
pyv: "3.9"
7171
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 4"
72+
- os: windows-latest
73+
pyv: "3.10"
74+
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 1"
75+
- os: windows-latest
76+
pyv: "3.10"
77+
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 2"
78+
- os: windows-latest
79+
pyv: "3.10"
80+
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 3"
81+
- os: windows-latest
82+
pyv: "3.10"
83+
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 4"
84+
- os: windows-latest
85+
pyv: "3.11"
86+
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 1"
87+
- os: windows-latest
88+
pyv: "3.11"
89+
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 2"
90+
- os: windows-latest
91+
pyv: "3.11"
92+
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 3"
93+
- os: windows-latest
94+
pyv: "3.11"
95+
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 4"
7296
- os: windows-latest
7397
pyv: "3.12"
7498
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 1"
@@ -81,6 +105,18 @@ jobs:
81105
- os: windows-latest
82106
pyv: "3.12"
83107
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 4"
108+
- os: windows-latest
109+
pyv: "3.13"
110+
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 1"
111+
- os: windows-latest
112+
pyv: "3.13"
113+
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 2"
114+
- os: windows-latest
115+
pyv: "3.13"
116+
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 3"
117+
- os: windows-latest
118+
pyv: "3.13"
119+
pytestargs: "--splitting-algorithm=least_duration --splits 4 --group 4"
84120
steps:
85121
- uses: actions/checkout@v4
86122
with:

dvc/config.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""DVC config objects."""
22

3+
import ntpath
34
import os
5+
import posixpath
46
import re
57
from contextlib import contextmanager
68
from functools import partial
@@ -268,12 +270,17 @@ def _resolve(conf_dir, path):
268270
if re.match(r"\w+://", path):
269271
return path
270272

273+
if os.name == "nt" and posixpath.isabs(path) and ntpath.sep not in path:
274+
return path
275+
271276
if os.path.isabs(path):
272277
return path
273278

274279
# on windows convert slashes to backslashes
275280
# to have path compatible with abs_conf_dir
276281
if os.path.sep == "\\" and "/" in path:
282+
if path.startswith("/"):
283+
path = path.replace("/", "\\\\", 1)
277284
path = path.replace("/", "\\")
278285

279286
expanded = os.path.expanduser(path)
@@ -305,6 +312,9 @@ def _to_relpath(conf_dir, path):
305312
if os.path.expanduser(path) != path:
306313
return localfs.as_posix(path)
307314

315+
if os.name == "nt" and posixpath.isabs(path) and ntpath.sep not in path:
316+
return path
317+
308318
if isinstance(path, RelPath) or not os.path.isabs(path):
309319
path = relpath(path, conf_dir)
310320
return localfs.as_posix(path)

dvc/fs/dvc.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -732,9 +732,10 @@ def repo_url(self) -> str:
732732
return self.fs.repo_url
733733

734734
def from_os_path(self, path: str) -> str:
735-
if os.path.isabs(path):
735+
if os.path.isabs(path) or (
736+
os.name == "nt" and posixpath.isabs(path) and ntpath.sep not in path
737+
):
736738
path = os.path.relpath(path, self.repo.root_dir)
737-
738739
return as_posix(path)
739740

740741
def close(self):

tests/unit/test_ignore.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,9 @@ def test_match_ignore_from_file(
168168
):
169169
from dvc.fs import localfs
170170

171+
root = r"\\" if os.name == "nt" else "/"
171172
dvcignore_path = os.path.join(
172-
os.path.sep, "full", "path", "to", "ignore", "file", ".dvcignore"
173+
root, "full", "path", "to", "ignore", "file", ".dvcignore"
173174
)
174175
dvcignore_dirname = os.path.dirname(dvcignore_path)
175176

0 commit comments

Comments
 (0)