Skip to content
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions git/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ def _included_paths(self) -> List[Tuple[str, str]]:
value,
)
if self._repo.git_dir:
if fnmatch.fnmatchcase(str(self._repo.git_dir), value):
if fnmatch.fnmatchcase(os.fspath(self._repo.git_dir), value):
paths += self.items(section)

elif keyword == "onbranch":
Expand Down Expand Up @@ -634,7 +634,7 @@ def read(self) -> None: # type: ignore[override]
self._read(file_path, file_path.name)
else:
# Assume a path if it is not a file-object.
file_path = cast(PathLike, file_path)
file_path = os.fspath(file_path)
try:
with open(file_path, "rb") as fp:
file_ok = True
Expand Down
19 changes: 10 additions & 9 deletions git/index/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,10 +404,10 @@ def _iter_expand_paths(self: "IndexFile", paths: Sequence[PathLike]) -> Iterator
def raise_exc(e: Exception) -> NoReturn:
raise e

r = str(self.repo.working_tree_dir)
r = os.fspath(self.repo.working_tree_dir)
rs = r + os.sep
for path in paths:
abs_path = str(path)
abs_path = os.fspath(path)
if not osp.isabs(abs_path):
abs_path = osp.join(r, path)
# END make absolute path
Expand All @@ -434,7 +434,7 @@ def raise_exc(e: Exception) -> NoReturn:
# characters.
if abs_path not in resolved_paths:
for f in self._iter_expand_paths(glob.glob(abs_path)):
yield str(f).replace(rs, "")
yield os.fspath(f).replace(rs, "")
continue
# END glob handling
try:
Expand Down Expand Up @@ -569,7 +569,7 @@ def resolve_blobs(self, iter_blobs: Iterator[Blob]) -> "IndexFile":
for blob in iter_blobs:
stage_null_key = (blob.path, 0)
if stage_null_key in self.entries:
raise ValueError("Path %r already exists at stage 0" % str(blob.path))
raise ValueError("Path %r already exists at stage 0" % os.fspath(blob.path))
# END assert blob is not stage 0 already

# Delete all possible stages.
Expand Down Expand Up @@ -652,14 +652,15 @@ def _to_relative_path(self, path: PathLike) -> PathLike:

:raise ValueError:
"""
path = os.fspath(path)
if not osp.isabs(path):
return path
if self.repo.bare:
raise InvalidGitRepositoryError("require non-bare repository")
if not osp.normpath(str(path)).startswith(str(self.repo.working_tree_dir)):
if not osp.normpath(path).startswith(os.fspath(self.repo.working_tree_dir)):
raise ValueError("Absolute path %r is not in git repository at %r" % (path, self.repo.working_tree_dir))
result = os.path.relpath(path, self.repo.working_tree_dir)
if str(path).endswith(os.sep) and not result.endswith(os.sep):
if path.endswith(os.sep) and not result.endswith(os.sep):
result += os.sep
return result

Expand Down Expand Up @@ -731,7 +732,7 @@ def _entries_for_paths(
for path in paths:
if osp.isabs(path):
abspath = path
gitrelative_path = path[len(str(self.repo.working_tree_dir)) + 1 :]
gitrelative_path = path[len(os.fspath(self.repo.working_tree_dir)) + 1 :]
else:
gitrelative_path = path
if self.repo.working_tree_dir:
Expand Down Expand Up @@ -1359,11 +1360,11 @@ def make_exc() -> GitCommandError:
try:
self.entries[(co_path, 0)]
except KeyError:
folder = str(co_path)
folder = os.fspath(co_path)
if not folder.endswith("/"):
folder += "/"
for entry in self.entries.values():
if str(entry.path).startswith(folder):
if os.fspath(entry.path).startswith(folder):
p = entry.path
self._write_path_to_stdin(proc, p, p, make_exc, fprogress, read_from_stdout=False)
checked_out_files.append(p)
Expand Down
4 changes: 2 additions & 2 deletions git/index/fun.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def run_commit_hook(name: str, index: "IndexFile", *args: str) -> None:
return

env = os.environ.copy()
env["GIT_INDEX_FILE"] = safe_decode(str(index.path))
env["GIT_INDEX_FILE"] = safe_decode(os.fspath(index.path))
env["GIT_EDITOR"] = ":"
cmd = [hp]
try:
Expand Down Expand Up @@ -167,7 +167,7 @@ def write_cache(
beginoffset = tell()
write(entry.ctime_bytes) # ctime
write(entry.mtime_bytes) # mtime
path_str = str(entry.path)
path_str = os.fspath(entry.path)
path: bytes = force_bytes(path_str, encoding=defenc)
plen = len(path) & CE_NAMEMASK # Path length
assert plen == len(path), "Path %s too long to fit into index" % entry.path
Expand Down
4 changes: 2 additions & 2 deletions git/index/typ.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ def __init__(self, paths: Sequence[PathLike]) -> None:

def __call__(self, stage_blob: Tuple[StageType, Blob]) -> bool:
blob_pathlike: PathLike = stage_blob[1].path
blob_path: Path = blob_pathlike if isinstance(blob_pathlike, Path) else Path(blob_pathlike)
blob_path = Path(blob_pathlike)
for pathlike in self.paths:
path: Path = pathlike if isinstance(pathlike, Path) else Path(pathlike)
path = Path(pathlike)
# TODO: Change to use `PosixPath.is_relative_to` once Python 3.8 is no
# longer supported.
filter_parts = path.parts
Expand Down
4 changes: 2 additions & 2 deletions git/index/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class TemporaryFileSwap:
__slots__ = ("file_path", "tmp_file_path")

def __init__(self, file_path: PathLike) -> None:
self.file_path = file_path
self.file_path = os.fspath(file_path)
dirname, basename = osp.split(file_path)
fd, self.tmp_file_path = tempfile.mkstemp(prefix=basename, dir=dirname)
os.close(fd)
Expand Down Expand Up @@ -106,7 +106,7 @@ def git_working_dir(func: Callable[..., _T]) -> Callable[..., _T]:
@wraps(func)
def set_git_working_dir(self: "IndexFile", *args: Any, **kwargs: Any) -> _T:
cur_wd = os.getcwd()
os.chdir(str(self.repo.working_tree_dir))
os.chdir(os.fspath(self.repo.working_tree_dir))
try:
return func(self, *args, **kwargs)
finally:
Expand Down
3 changes: 2 additions & 1 deletion git/objects/blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
__all__ = ["Blob"]

from mimetypes import guess_type
import os
import sys

if sys.version_info >= (3, 8):
Expand Down Expand Up @@ -44,5 +45,5 @@ def mime_type(self) -> str:
"""
guesses = None
if self.path:
guesses = guess_type(str(self.path))
guesses = guess_type(os.fspath(self.path))
return guesses and guesses[0] or self.DEFAULT_MIME_TYPE
10 changes: 5 additions & 5 deletions git/objects/submodule/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ def _clone_repo(
module_abspath_dir = osp.dirname(module_abspath)
if not osp.isdir(module_abspath_dir):
os.makedirs(module_abspath_dir)
module_checkout_path = osp.join(str(repo.working_tree_dir), path)
module_checkout_path = osp.join(os.fspath(repo.working_tree_dir), path)

if url.startswith("../"):
remote_name = repo.active_branch.tracking_branch().remote_name
Expand Down Expand Up @@ -541,7 +541,7 @@ def add(
if sm.exists():
# Reretrieve submodule from tree.
try:
sm = repo.head.commit.tree[str(path)]
sm = repo.head.commit.tree[os.fspath(path)]
sm._name = name
return sm
except KeyError:
Expand Down Expand Up @@ -1016,7 +1016,7 @@ def move(self, module_path: PathLike, configuration: bool = True, module: bool =
return self
# END handle no change

module_checkout_abspath = join_path_native(str(self.repo.working_tree_dir), module_checkout_path)
module_checkout_abspath = join_path_native(os.fspath(self.repo.working_tree_dir), module_checkout_path)
if osp.isfile(module_checkout_abspath):
raise ValueError("Cannot move repository onto a file: %s" % module_checkout_abspath)
# END handle target files
Expand Down Expand Up @@ -1313,7 +1313,7 @@ def set_parent_commit(self, commit: Union[Commit_ish, str, None], check: bool =
# If check is False, we might see a parent-commit that doesn't even contain the
# submodule anymore. in that case, mark our sha as being NULL.
try:
self.binsha = pctree[str(self.path)].binsha
self.binsha = pctree[os.fspath(self.path)].binsha
except KeyError:
self.binsha = self.NULL_BIN_SHA

Expand Down Expand Up @@ -1395,7 +1395,7 @@ def rename(self, new_name: str) -> "Submodule":
destination_module_abspath = self._module_abspath(self.repo, self.path, new_name)
source_dir = mod.git_dir
# Let's be sure the submodule name is not so obviously tied to a directory.
if str(destination_module_abspath).startswith(str(mod.git_dir)):
if os.fspath(destination_module_abspath).startswith(os.fspath(mod.git_dir)):
tmp_dir = self._module_abspath(self.repo, self.path, str(uuid.uuid4()))
os.renames(source_dir, tmp_dir)
source_dir = tmp_dir
Expand Down
2 changes: 2 additions & 0 deletions git/refs/head.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

__all__ = ["HEAD", "Head"]

import os
from git.config import GitConfigParser, SectionConstraint
from git.exc import GitCommandError
from git.util import join_path
Expand Down Expand Up @@ -48,6 +49,7 @@ class HEAD(SymbolicReference):
commit: "Commit"

def __init__(self, repo: "Repo", path: PathLike = _HEAD_NAME) -> None:
path = os.fspath(path)
if path != self._HEAD_NAME:
raise ValueError("HEAD instance must point to %r, got %r" % (self._HEAD_NAME, path))
super().__init__(repo, path)
Expand Down
3 changes: 2 additions & 1 deletion git/refs/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
__all__ = ["RefLog", "RefLogEntry"]

from mmap import mmap
import os
import os.path as osp
import re
import time as _time
Expand Down Expand Up @@ -167,7 +168,7 @@ def __init__(self, filepath: Union[PathLike, None] = None) -> None:
"""Initialize this instance with an optional filepath, from which we will
initialize our data. The path is also used to write changes back using the
:meth:`write` method."""
self._path = filepath
self._path = None if filepath is None else os.fspath(filepath)
if filepath is not None:
self._read_from_file()
# END handle filepath
Expand Down
3 changes: 2 additions & 1 deletion git/refs/reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

__all__ = ["Reference"]

import os
from git.util import IterableObj, LazyMixin

from .symbolic import SymbolicReference, T_References
Expand Down Expand Up @@ -65,7 +66,7 @@ def __init__(self, repo: "Repo", path: PathLike, check_path: bool = True) -> Non
If ``False``, you can provide any path.
Otherwise the path must start with the default path prefix of this type.
"""
if check_path and not str(path).startswith(self._common_path_default + "/"):
if check_path and not os.fspath(path).startswith(self._common_path_default + "/"):
raise ValueError(f"Cannot instantiate {self.__class__.__name__!r} from path {path}")
self.path: str # SymbolicReference converts to string at the moment.
super().__init__(repo, path)
Expand Down
20 changes: 10 additions & 10 deletions git/refs/symbolic.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ class SymbolicReference:

def __init__(self, repo: "Repo", path: PathLike, check_path: bool = False) -> None:
self.repo = repo
self.path = path
self.path = os.fspath(path)

def __str__(self) -> str:
return str(self.path)
return os.fspath(self.path)

def __repr__(self) -> str:
return '<git.%s "%s">' % (self.__class__.__name__, self.path)
Expand All @@ -103,7 +103,7 @@ def name(self) -> str:
In case of symbolic references, the shortest assumable name is the path
itself.
"""
return str(self.path)
return os.fspath(self.path)

@property
def abspath(self) -> PathLike:
Expand Down Expand Up @@ -178,7 +178,7 @@ def _check_ref_name_valid(ref_path: PathLike) -> None:
"""
previous: Union[str, None] = None
one_before_previous: Union[str, None] = None
for c in str(ref_path):
for c in os.fspath(ref_path):
if c in " ~^:?*[\\":
raise ValueError(
f"Invalid reference '{ref_path}': references cannot contain spaces, tildes (~), carets (^),"
Expand Down Expand Up @@ -212,7 +212,7 @@ def _check_ref_name_valid(ref_path: PathLike) -> None:
raise ValueError(f"Invalid reference '{ref_path}': references cannot end with a forward slash (/)")
elif previous == "@" and one_before_previous is None:
raise ValueError(f"Invalid reference '{ref_path}': references cannot be '@'")
elif any(component.endswith(".lock") for component in str(ref_path).split("/")):
elif any(component.endswith(".lock") for component in os.fspath(ref_path).split("/")):
raise ValueError(
f"Invalid reference '{ref_path}': references cannot have slash-separated components that end with"
" '.lock'"
Expand All @@ -235,7 +235,7 @@ def _get_ref_info_helper(
tokens: Union[None, List[str], Tuple[str, str]] = None
repodir = _git_dir(repo, ref_path)
try:
with open(os.path.join(repodir, str(ref_path)), "rt", encoding="UTF-8") as fp:
with open(os.path.join(repodir, os.fspath(ref_path)), "rt", encoding="UTF-8") as fp:
value = fp.read().rstrip()
# Don't only split on spaces, but on whitespace, which allows to parse lines like:
# 60b64ef992065e2600bfef6187a97f92398a9144 branch 'master' of git-server:/path/to/repo
Expand Down Expand Up @@ -614,7 +614,7 @@ def to_full_path(cls, path: Union[PathLike, "SymbolicReference"]) -> PathLike:
full_ref_path = path
if not cls._common_path_default:
return full_ref_path
if not str(path).startswith(cls._common_path_default + "/"):
if not os.fspath(path).startswith(cls._common_path_default + "/"):
full_ref_path = "%s/%s" % (cls._common_path_default, path)
return full_ref_path

Expand Down Expand Up @@ -706,7 +706,7 @@ def _create(
if not force and os.path.isfile(abs_ref_path):
target_data = str(target)
if isinstance(target, SymbolicReference):
target_data = str(target.path)
target_data = os.fspath(target.path)
if not resolve:
target_data = "ref: " + target_data
with open(abs_ref_path, "rb") as fd:
Expand Down Expand Up @@ -842,7 +842,7 @@ def _iter_items(

# Read packed refs.
for _sha, rela_path in cls._iter_packed_refs(repo):
if rela_path.startswith(str(common_path)):
if rela_path.startswith(os.fspath(common_path)):
rela_paths.add(rela_path)
# END relative path matches common path
# END packed refs reading
Expand Down Expand Up @@ -931,4 +931,4 @@ def from_path(cls: Type[T_References], repo: "Repo", path: PathLike) -> T_Refere

def is_remote(self) -> bool:
""":return: True if this symbolic reference points to a remote branch"""
return str(self.path).startswith(self._remote_common_path_default + "/")
return os.fspath(self.path).startswith(self._remote_common_path_default + "/")
17 changes: 7 additions & 10 deletions git/repo/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,11 +219,9 @@ def __init__(
# Given how the tests are written, this seems more likely to catch Cygwin
# git used from Windows than Windows git used from Cygwin. Therefore
# changing to Cygwin-style paths is the relevant operation.
epath = cygpath(str(epath))
epath = cygpath(os.fspath(epath))

epath = epath or path or os.getcwd()
if not isinstance(epath, str):
epath = str(epath)
epath = os.fspath(epath)
if expand_vars and re.search(self.re_envvars, epath):
warnings.warn(
"The use of environment variables in paths is deprecated"
Expand Down Expand Up @@ -556,7 +554,7 @@ def tag(self, path: PathLike) -> TagReference:

@staticmethod
def _to_full_tag_path(path: PathLike) -> str:
path_str = str(path)
path_str = os.fspath(path)
if path_str.startswith(TagReference._common_path_default + "/"):
return path_str
if path_str.startswith(TagReference._common_default + "/"):
Expand Down Expand Up @@ -961,7 +959,7 @@ def is_dirty(
if not submodules:
default_args.append("--ignore-submodules")
if path:
default_args.extend(["--", str(path)])
default_args.extend(["--", os.fspath(path)])
if index:
# diff index against HEAD.
if osp.isfile(self.index.path) and len(self.git.diff("--cached", *default_args)):
Expand Down Expand Up @@ -1361,9 +1359,8 @@ def _clone(
) -> "Repo":
odbt = kwargs.pop("odbt", odb_default_type)

# When pathlib.Path or other class-based path is passed
if not isinstance(path, str):
path = str(path)
url = os.fspath(url)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

URL is not a path though.

Copy link
Contributor Author

@George-Ogden George-Ogden Nov 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, url can be a path if you're cloning a local repo, and if it's not os.fspath will leave strings alone.

path = os.fspath(path)

## A bug win cygwin's Git, when `--bare` or `--separate-git-dir`
# it prepends the cwd or(?) the `url` into the `path, so::
Expand All @@ -1380,7 +1377,7 @@ def _clone(
multi = shlex.split(" ".join(multi_options))

if not allow_unsafe_protocols:
Git.check_unsafe_protocols(str(url))
Git.check_unsafe_protocols(url)
if not allow_unsafe_options:
Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=cls.unsafe_git_clone_options)
if not allow_unsafe_options and multi_options:
Expand Down
1 change: 1 addition & 0 deletions git/repo/fun.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def is_git_dir(d: PathLike) -> bool:
clearly indicates that we don't support it. There is the unlikely danger to
throw if we see directories which just look like a worktree dir, but are none.
"""
d = os.fspath(d)
if osp.isdir(d):
if (osp.isdir(osp.join(d, "objects")) or "GIT_OBJECT_DIRECTORY" in os.environ) and osp.isdir(
osp.join(d, "refs")
Expand Down
Loading
Loading