Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion 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
12 changes: 6 additions & 6 deletions git/index/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ def raise_exc(e: Exception) -> NoReturn:
r = str(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 Down Expand Up @@ -656,10 +656,10 @@ def _to_relative_path(self, path: PathLike) -> PathLike:
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(str(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 os.fspath(path).endswith(os.sep) and not result.endswith(os.sep):
result += os.sep
return result

Expand Down Expand Up @@ -1036,7 +1036,7 @@ def remove(
args.append("--")

# Preprocess paths.
paths = self._items_to_rela_paths(items)
paths = list(map(os.fspath, self._items_to_rela_paths(items))) # type: ignore[arg-type]
removed_paths = self.repo.git.rm(args, paths, **kwargs).splitlines()

# Process output to gain proper paths.
Expand Down Expand Up @@ -1359,11 +1359,11 @@ def make_exc() -> GitCommandError:
try:
self.entries[(co_path, 0)]
except KeyError:
folder = str(co_path)
folder = 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
2 changes: 1 addition & 1 deletion 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
4 changes: 2 additions & 2 deletions git/index/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

# typing ----------------------------------------------------------------------

from typing import Any, Callable, TYPE_CHECKING, Optional, Type
from typing import Any, Callable, TYPE_CHECKING, Optional, Type, cast

from git.types import Literal, PathLike, _T

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(cast(PathLike, 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
4 changes: 2 additions & 2 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(repo.working_tree_dir, path) # type: ignore[arg-type]

if url.startswith("../"):
remote_name = cast("RemoteReference", 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
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
21 changes: 11 additions & 10 deletions git/refs/symbolic.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
__all__ = ["SymbolicReference"]

import os
from pathlib import Path

from gitdb.exc import BadName, BadObject

Expand Down Expand Up @@ -76,10 +77,10 @@ class SymbolicReference:

def __init__(self, repo: "Repo", path: PathLike, check_path: bool = False) -> None:
self.repo = repo
self.path = path
self.path: PathLike = 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 +104,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 +179,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 +213,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 Path(ref_path).parts):
raise ValueError(
f"Invalid reference '{ref_path}': references cannot have slash-separated components that end with"
" '.lock'"
Expand All @@ -235,7 +236,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, ref_path), "rt", encoding="UTF-8") as fp: # type: ignore[arg-type]
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 +615,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 +707,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 +843,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 @@ -930,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 + "/")
19 changes: 9 additions & 10 deletions git/repo/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class Repo:
working_dir: PathLike
"""The working directory of the git command."""

# stored as string for easier processing, but annotated as path for clearer intention
_working_tree_dir: Optional[PathLike] = None

git_dir: PathLike
Expand Down Expand Up @@ -215,15 +216,13 @@ def __init__(
epath = path or os.getenv("GIT_DIR")
if not epath:
epath = os.getcwd()
epath = os.fspath(epath)
if Git.is_cygwin():
# 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(epath)

epath = epath or path or os.getcwd()
if not isinstance(epath, str):
epath = str(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 @@ -957,7 +956,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 @@ -1357,9 +1356,9 @@ 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 may be a path and this has no effect if it is a string
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 @@ -1376,7 +1375,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 All @@ -1385,7 +1384,7 @@ def _clone(
proc = git.clone(
multi,
"--",
Git.polish_url(str(url)),
Git.polish_url(url),
clone_path,
with_extended_output=True,
as_process=True,
Expand Down
24 changes: 12 additions & 12 deletions git/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
import logging
import os
import os.path as osp
import pathlib
from pathlib import Path
import platform
import re
import shutil
Expand Down Expand Up @@ -272,9 +272,9 @@ def stream_copy(source: BinaryIO, destination: BinaryIO, chunk_size: int = 512 *
def join_path(a: PathLike, *p: PathLike) -> PathLike:
R"""Join path tokens together similar to osp.join, but always use ``/`` instead of
possibly ``\`` on Windows."""
path = str(a)
path = os.fspath(a)
for b in p:
b = str(b)
b = os.fspath(b)
if not b:
continue
if b.startswith("/"):
Expand All @@ -290,18 +290,18 @@ def join_path(a: PathLike, *p: PathLike) -> PathLike:
if sys.platform == "win32":

def to_native_path_windows(path: PathLike) -> PathLike:
path = str(path)
path = os.fspath(path)
return path.replace("/", "\\")

def to_native_path_linux(path: PathLike) -> str:
path = str(path)
path = os.fspath(path)
return path.replace("\\", "/")

to_native_path = to_native_path_windows
else:
# No need for any work on Linux.
def to_native_path_linux(path: PathLike) -> str:
return str(path)
return os.fspath(path)

to_native_path = to_native_path_linux

Expand Down Expand Up @@ -372,7 +372,7 @@ def is_exec(fpath: str) -> bool:
progs = []
if not path:
path = os.environ["PATH"]
for folder in str(path).split(os.pathsep):
for folder in os.fspath(path).split(os.pathsep):
folder = folder.strip('"')
if folder:
exe_path = osp.join(folder, program)
Expand All @@ -397,7 +397,7 @@ def _cygexpath(drive: Optional[str], path: str) -> str:
p = cygpath(p)
elif drive:
p = "/proc/cygdrive/%s/%s" % (drive.lower(), p)
p_str = str(p) # ensure it is a str and not AnyPath
p_str = os.fspath(p) # ensure it is a str and not AnyPath
return p_str.replace("\\", "/")


Expand All @@ -418,7 +418,7 @@ def _cygexpath(drive: Optional[str], path: str) -> str:

def cygpath(path: str) -> str:
"""Use :meth:`git.cmd.Git.polish_url` instead, that works on any environment."""
path = str(path) # Ensure is str and not AnyPath.
path = os.fspath(path) # Ensure is str and not AnyPath.
# Fix to use Paths when 3.5 dropped. Or to be just str if only for URLs?
if not path.startswith(("/cygdrive", "//", "/proc/cygdrive")):
for regex, parser, recurse in _cygpath_parsers:
Expand All @@ -438,7 +438,7 @@ def cygpath(path: str) -> str:


def decygpath(path: PathLike) -> str:
path = str(path)
path = os.fspath(path)
m = _decygpath_regex.match(path)
if m:
drive, rest_path = m.groups()
Expand All @@ -465,7 +465,7 @@ def _is_cygwin_git(git_executable: str) -> bool:
# Just a name given, not a real path.
uname_cmd = osp.join(git_dir, "uname")

if not (pathlib.Path(uname_cmd).is_file() and os.access(uname_cmd, os.X_OK)):
if not (Path(uname_cmd).is_file() and os.access(uname_cmd, os.X_OK)):
_logger.debug(f"Failed checking if running in CYGWIN: {uname_cmd} is not an executable")
_is_cygwin_cache[git_executable] = is_cygwin
return is_cygwin
Expand Down Expand Up @@ -523,7 +523,7 @@ def expand_path(p: PathLike, expand_vars: bool = ...) -> str:


def expand_path(p: Union[None, PathLike], expand_vars: bool = True) -> Optional[PathLike]:
if isinstance(p, pathlib.Path):
if isinstance(p, Path):
return p.resolve()
try:
p = osp.expanduser(p) # type: ignore[arg-type]
Expand Down
Loading
Loading