| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent d160fb4 commit c7cf4d1
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,6 +9,7 @@ Security fixes for | |||
| 9 | 9 | ||
| 10 | 10 | * https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-g5vv-9gxw-82hx | |
| 11 | 11 | * https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-whh4-5q6c-9v3x | |
| 12 | + * https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-239g-whfq-7xj9 | ||
| 12 | 13 | ||
| 13 | 14 | If you can, also try and provide feedback on the upcoming v4 branch | |
| 14 | 15 | https://github.com/gitpython-developers/GitPython/pull/2177 - patches welcome. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -45,7 +45,6 @@ | |||
| 45 | 45 | ||
| 46 | 46 | from .fun import ( | |
| 47 | 47 | find_submodule_git_dir, | |
| 48 | - find_worktree_git_dir, | ||
| 49 | 48 | is_git_dir, | |
| 50 | 49 | rev_parse, | |
| 51 | 50 | touch, | |
@@ -234,6 +233,10 @@ def __init__( | |||
| 234 | 233 | ) -> None: | |
| 235 | 234 | R"""Create a new :class:`Repo` instance. | |
| 236 | 235 | ||
| 236 | + .. note:: | ||
| 237 | + Repositories using reftable may be opened, but GitPython's direct reference | ||
| 238 | + access does not support reftable. | ||
| 239 | + | ||
| 237 | 240 | :param path: | |
| 238 | 241 | The path to either the worktree directory or the .git directory itself:: | |
| 239 | 242 | ||
@@ -268,7 +271,11 @@ def __init__( | |||
| 268 | 271 | :class:`Repo` | |
| 269 | 272 | """ | |
| 270 | 273 | ||
| 271 | - epath = path or os.getenv("GIT_DIR") | ||
| 274 | + git_dir_env = os.getenv("GIT_DIR") | ||
| 275 | + object_dir_env = os.getenv("GIT_OBJECT_DIRECTORY") | ||
| 276 | + if object_dir_env is not None: | ||
| 277 | + object_dir_env = osp.abspath(object_dir_env) | ||
| 278 | + epath = path or git_dir_env | ||
| 272 | 279 | if not epath: | |
| 273 | 280 | epath = os.getcwd() | |
| 274 | 281 | epath = os.fspath(epath) | |
@@ -290,37 +297,48 @@ def __init__( | |||
| 290 | 297 | raise NoSuchPathError(epath) | |
| 291 | 298 | ||
| 292 | 299 | # Walk up the path to find the `.git` dir. | |
| 293 | - curpath = epath | ||
| 294 | - git_dir = None | ||
| 300 | + curpath = os.fspath(epath) if epath is not None else "" | ||
| 301 | + git_dir: Optional[str] = None | ||
| 302 | + explicit_git_dir = not path and bool(git_dir_env) | ||
| 295 | 303 | while curpath: | |
| 296 | 304 | # ABOUT osp.NORMPATH | |
| 297 | 305 | # It's important to normalize the paths, as submodules will otherwise | |
| 298 | 306 | # initialize their repo instances with paths that depend on path-portions | |
| 299 | 307 | # that will not exist after being removed. It's just cleaner. | |
| 300 | - if ( | ||
| 301 | - osp.isfile(osp.join(curpath, "gitdir")) | ||
| 302 | - and osp.isfile(osp.join(curpath, "commondir")) | ||
| 303 | - and osp.isfile(osp.join(curpath, "HEAD")) | ||
| 304 | - ): | ||
| 305 | - git_dir = curpath | ||
| 306 | - | ||
| 307 | - if "GIT_WORK_TREE" in os.environ: | ||
| 308 | - self._working_tree_dir = os.getenv("GIT_WORK_TREE") | ||
| 309 | - else: | ||
| 310 | - # Linked worktree administrative directories store the path to the | ||
| 311 | - # worktree's .git file in their gitdir file (without "gitdir: " prefix). | ||
| 312 | - with open(osp.join(git_dir, "gitdir")) as fp: | ||
| 313 | - worktree_gitfile = fp.read().strip() | ||
| 308 | + if not explicit_git_dir: | ||
| 309 | + dotgit = osp.join(curpath, ".git") | ||
| 310 | + try: | ||
| 311 | + sm_gitpath = find_submodule_git_dir(dotgit) | ||
| 312 | + except OSError: | ||
| 313 | + break | ||
| 314 | + if sm_gitpath is not None: | ||
| 315 | + # Worktrees can use relative paths as of Git 2.48, so join to curpath. | ||
| 316 | + git_dir = osp.normpath(osp.join(curpath, os.fspath(sm_gitpath))) | ||
| 317 | + self._working_tree_dir = curpath | ||
| 318 | + break | ||
| 319 | + | ||
| 320 | + # Like Git, do not fall back to a bare repository or parent directory when | ||
| 321 | + # a non-directory .git entry exists but is not a valid gitfile. | ||
| 322 | + if osp.exists(dotgit) and not osp.isdir(dotgit): | ||
| 323 | + break | ||
| 314 | 324 | ||
| 315 | - if not osp.isabs(worktree_gitfile): | ||
| 316 | - worktree_gitfile = osp.normpath(osp.join(git_dir, worktree_gitfile)) | ||
| 325 | + if is_git_dir(curpath): | ||
| 326 | + git_dir = curpath | ||
| 327 | + if osp.isfile(osp.join(curpath, "gitdir")) and osp.isfile(osp.join(curpath, "commondir")): | ||
| 328 | + if "GIT_WORK_TREE" in os.environ: | ||
| 329 | + self._working_tree_dir = os.getenv("GIT_WORK_TREE") | ||
| 330 | + else: | ||
| 331 | + # Linked worktree administrative directories store the path to | ||
| 332 | + # the worktree's .git file in gitdir (without a "gitdir: " prefix). | ||
| 333 | + with open(osp.join(git_dir, "gitdir")) as fp: | ||
| 334 | + worktree_gitfile = fp.read().strip() | ||
| 317 | 335 | ||
| 318 | - self._working_tree_dir = osp.dirname(worktree_gitfile) | ||
| 336 | + if not osp.isabs(worktree_gitfile): | ||
| 337 | + worktree_gitfile = osp.normpath(osp.join(git_dir, worktree_gitfile)) | ||
| 319 | 338 | ||
| 320 | - break | ||
| 339 | + self._working_tree_dir = osp.dirname(worktree_gitfile) | ||
| 340 | + break | ||
| 321 | 341 | ||
| 322 | - if is_git_dir(curpath): | ||
| 323 | - git_dir = curpath | ||
| 324 | 342 | # from man git-config : core.worktree | |
| 325 | 343 | # Set the path to the root of the working tree. If GIT_COMMON_DIR | |
| 326 | 344 | # environment variable is set, core.worktree is ignored and not used for | |
@@ -340,22 +358,7 @@ def __init__( | |||
| 340 | 358 | self._working_tree_dir = os.getenv("GIT_WORK_TREE") | |
| 341 | 359 | break | |
| 342 | 360 | ||
| 343 | - dotgit = osp.join(curpath, ".git") | ||
| 344 | - sm_gitpath = find_submodule_git_dir(dotgit) | ||
| 345 | - if sm_gitpath is not None: | ||
| 346 | - git_dir = osp.normpath(sm_gitpath) | ||
| 347 | - | ||
| 348 | - sm_gitpath = find_submodule_git_dir(dotgit) | ||
| 349 | - if sm_gitpath is None: | ||
| 350 | - sm_gitpath = find_worktree_git_dir(dotgit) | ||
| 351 | - | ||
| 352 | - if sm_gitpath is not None: | ||
| 353 | - # worktrees can use relative paths as of Git 2.48, so we join to curpath | ||
| 354 | - git_dir = osp.normpath(osp.join(curpath, sm_gitpath)) | ||
| 355 | - self._working_tree_dir = curpath | ||
| 356 | - break | ||
| 357 | - | ||
| 358 | - if not search_parent_directories: | ||
| 361 | + if explicit_git_dir or not search_parent_directories: | ||
| 359 | 362 | break | |
| 360 | 363 | curpath, tail = osp.split(curpath) | |
| 361 | 364 | if not tail: | |
@@ -366,19 +369,23 @@ def __init__( | |||
| 366 | 369 | raise InvalidGitRepositoryError(epath) | |
| 367 | 370 | self.git_dir = git_dir | |
| 368 | 371 | ||
| 372 | + common_dir_env = os.getenv("GIT_COMMON_DIR") | ||
| 373 | + if common_dir_env is not None: | ||
| 374 | + self._common_dir = osp.abspath(common_dir_env) | ||
| 375 | + else: | ||
| 376 | + try: | ||
| 377 | + common_dir = os.fsdecode((Path(self.git_dir) / "commondir").read_bytes()).rstrip("\r\n") | ||
| 378 | + self._common_dir = osp.join(self.git_dir, common_dir) | ||
| 379 | + except OSError: | ||
| 380 | + self._common_dir = "" | ||
| 381 | + | ||
| 369 | 382 | self._bare = False | |
| 370 | 383 | try: | |
| 371 | 384 | self._bare = self.config_reader("repository").getboolean("core", "bare") | |
| 372 | 385 | except Exception: | |
| 373 | 386 | # Let's not assume the option exists, although it should. | |
| 374 | 387 | pass | |
| 375 | 388 | ||
| 376 | - try: | ||
| 377 | - common_dir = (Path(self.git_dir) / "commondir").read_text().splitlines()[0].strip() | ||
| 378 | - self._common_dir = osp.join(self.git_dir, common_dir) | ||
| 379 | - except OSError: | ||
| 380 | - self._common_dir = "" | ||
| 381 | - | ||
| 382 | 389 | # Adjust the working directory in case we are actually bare - we didn't know | |
| 383 | 390 | # that in the first place. | |
| 384 | 391 | if self._bare: | |
@@ -387,9 +394,15 @@ def __init__( | |||
| 387 | 394 | ||
| 388 | 395 | self.working_dir: PathLike = self._working_tree_dir or self.common_dir | |
| 389 | 396 | self.git = self.GitCommandWrapperType(self.working_dir) | |
| 397 | + if common_dir_env is not None: | ||
| 398 | + self.git.update_environment(GIT_DIR=os.fspath(self.git_dir), GIT_COMMON_DIR=os.fspath(self.common_dir)) | ||
| 399 | + elif git_dir_env is not None: | ||
| 400 | + self.git.update_environment(GIT_DIR=os.fspath(self.git_dir)) | ||
| 401 | + if object_dir_env is not None: | ||
| 402 | + self.git.update_environment(GIT_OBJECT_DIRECTORY=object_dir_env) | ||
| 390 | 403 | ||
| 391 | 404 | # Special handling, in special times. | |
| 392 | - rootpath = osp.join(self.common_dir, "objects") | ||
| 405 | + rootpath = object_dir_env if object_dir_env is not None else osp.join(self.common_dir, "objects") | ||
| 393 | 406 | if issubclass(odbt, GitCmdObjectDB): | |
| 394 | 407 | self.odb = odbt(rootpath, self.git) | |
| 395 | 408 | else: | |
@@ -990,7 +1003,7 @@ def _get_alternates(self) -> List[str]: | |||
| 990 | 1003 | :return: | |
| 991 | 1004 | List of strings being pathnames of alternates | |
| 992 | 1005 | """ | |
| 993 | - alternates_path = osp.join(self.common_dir, "objects", "info", "alternates") | ||
| 1006 | + alternates_path = osp.join(self.odb.root_path(), "info", "alternates") | ||
| 994 | 1007 | ||
| 995 | 1008 | if osp.exists(alternates_path): | |
| 996 | 1009 | with open(alternates_path, "rb") as f: | |
@@ -1011,7 +1024,7 @@ def _set_alternates(self, alts: List[str]) -> None: | |||
| 1011 | 1024 | The method does not check for the existence of the paths in `alts`, as the | |
| 1012 | 1025 | caller is responsible. | |
| 1013 | 1026 | """ | |
| 1014 | - alternates_path = osp.join(self.common_dir, "objects", "info", "alternates") | ||
| 1027 | + alternates_path = osp.join(self.odb.root_path(), "info", "alternates") | ||
| 1015 | 1028 | if not alts: | |
| 1016 | 1029 | if osp.isfile(alternates_path): | |
| 1017 | 1030 | os.remove(alternates_path) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -60,22 +60,61 @@ def touch(filename: str) -> str: | |||
| 60 | 60 | def is_git_dir(d: PathLike) -> bool: | |
| 61 | 61 | """This is taken from the git setup.c:is_git_directory function. | |
| 62 | 62 | ||
| 63 | + .. note:: | ||
| 64 | + This function recognizes repositories using reftable through their | ||
| 65 | + compatibility files, but GitPython's direct reference access does not support | ||
| 66 | + reftable. | ||
| 67 | + | ||
| 63 | 68 | :raise git.exc.WorkTreeRepositoryUnsupported: | |
| 64 | 69 | If it sees a worktree directory. It's quite hacky to do that here, but at least | |
| 65 | 70 | clearly indicates that we don't support it. There is the unlikely danger to | |
| 66 | 71 | throw if we see directories which just look like a worktree dir, but are none. | |
| 67 | 72 | """ | |
| 68 | 73 | if osp.isdir(d): | |
| 69 | - if (osp.isdir(osp.join(d, "objects")) or "GIT_OBJECT_DIRECTORY" in os.environ) and osp.isdir( | ||
| 70 | - osp.join(d, "refs") | ||
| 71 | - ): | ||
| 72 | - headref = osp.join(d, "HEAD") | ||
| 73 | - return osp.isfile(headref) or (osp.islink(headref) and os.readlink(headref).startswith("refs")) | ||
| 74 | - elif ( | ||
| 75 | - osp.isfile(osp.join(d, "gitdir")) | ||
| 76 | - and osp.isfile(osp.join(d, "commondir")) | ||
| 77 | - and osp.isfile(osp.join(d, "gitfile")) | ||
| 78 | - ): | ||
| 74 | + headref = osp.join(d, "HEAD") | ||
| 75 | + if osp.islink(headref): | ||
| 76 | + try: | ||
| 77 | + valid_head = os.readlink(headref).startswith("refs/") | ||
| 78 | + except OSError: | ||
| 79 | + valid_head = False | ||
| 80 | + else: | ||
| 81 | + try: | ||
| 82 | + with open(headref, "rb") as fp: | ||
| 83 | + head = fp.read(256) | ||
| 84 | + except OSError: | ||
| 85 | + valid_head = False | ||
| 86 | + else: | ||
| 87 | + valid_head = (head.startswith(b"ref:") and head[4:].lstrip().startswith(b"refs/")) or bool( | ||
| 88 | + re.match(rb"(?:[0-9A-Fa-f]{64}|[0-9A-Fa-f]{40})", head) | ||
| 89 | + ) | ||
| 90 | + | ||
| 91 | + common_dir = os.getenv("GIT_COMMON_DIR") | ||
| 92 | + if common_dir == "": | ||
| 93 | + return False | ||
| 94 | + if common_dir is None: | ||
| 95 | + common_dir_file = Path(d) / "commondir" | ||
| 96 | + try: | ||
| 97 | + common_dir = os.fsdecode(common_dir_file.read_bytes()).rstrip("\r\n") | ||
| 98 | + except FileNotFoundError: | ||
| 99 | + if osp.lexists(common_dir_file): | ||
| 100 | + return False | ||
| 101 | + common_dir = os.fspath(d) | ||
| 102 | + except (OSError, UnicodeError): | ||
| 103 | + return False | ||
| 104 | + else: | ||
| 105 | + if not common_dir: | ||
| 106 | + return False | ||
| 107 | + try: | ||
| 108 | + common_dir = osp.realpath(osp.join(d, common_dir)) | ||
| 109 | + except (OSError, ValueError): | ||
| 110 | + return False | ||
| 111 | + | ||
| 112 | + object_dir = os.getenv("GIT_OBJECT_DIRECTORY") | ||
| 113 | + if object_dir is None: | ||
| 114 | + object_dir = osp.join(common_dir, "objects") | ||
| 115 | + if valid_head and osp.isdir(object_dir) and osp.isdir(osp.join(common_dir, "refs")): | ||
| 116 | + return True | ||
| 117 | + if osp.isfile(osp.join(d, "gitdir")) and osp.isfile(osp.join(d, "commondir")) and osp.isfile(headref): | ||
| 79 | 118 | raise WorkTreeRepositoryUnsupported(d) | |
| 80 | 119 | return False | |
| 81 | 120 | ||
@@ -84,46 +123,39 @@ def find_worktree_git_dir(dotgit: PathLike) -> Optional[str]: | |||
| 84 | 123 | """Search for a gitdir for this worktree.""" | |
| 85 | 124 | try: | |
| 86 | 125 | statbuf = os.stat(dotgit) | |
| 87 | - except OSError: | ||
| 126 | + except (FileNotFoundError, NotADirectoryError): | ||
| 88 | 127 | return None | |
| 89 | - if not stat.S_ISREG(statbuf.st_mode): | ||
| 128 | + if not stat.S_ISREG(statbuf.st_mode) or statbuf.st_size > (1 << 20): | ||
| 90 | 129 | return None | |
| 91 | 130 | ||
| 92 | 131 | try: | |
| 93 | - lines = Path(dotgit).read_text().splitlines() | ||
| 94 | - for key, value in [line.strip().split(": ") for line in lines]: | ||
| 95 | - if key == "gitdir": | ||
| 96 | - return value | ||
| 97 | - except ValueError: | ||
| 98 | - pass | ||
| 99 | - return None | ||
| 132 | + with open(dotgit, "rb") as fp: | ||
| 133 | + content_bytes = fp.read(statbuf.st_size) | ||
| 134 | + if len(content_bytes) != statbuf.st_size: | ||
| 135 | + return None | ||
| 136 | + content = os.fsdecode(content_bytes).rstrip("\r\n") | ||
| 137 | + except (OSError, UnicodeError): | ||
| 138 | + return None | ||
| 139 | + return content[8:] if len(content) >= 9 and content.startswith("gitdir: ") else None | ||
| 100 | 140 | ||
| 101 | 141 | ||
| 102 | 142 | def find_submodule_git_dir(d: PathLike) -> Optional[PathLike]: | |
| 103 | 143 | """Search for a submodule repo.""" | |
| 104 | 144 | if is_git_dir(d): | |
| 105 | 145 | return d | |
| 106 | 146 | ||
| 107 | - try: | ||
| 108 | - with open(d) as fp: | ||
| 109 | - content = fp.read().rstrip() | ||
| 110 | - except IOError: | ||
| 111 | - # It's probably not a file. | ||
| 112 | - pass | ||
| 113 | - else: | ||
| 114 | - if content.startswith("gitdir: "): | ||
| 115 | - path = content[8:] | ||
| 116 | - | ||
| 117 | - if Git.is_cygwin(): | ||
| 118 | - # Cygwin creates submodules prefixed with `/cygdrive/...`. | ||
| 119 | - # Cygwin git understands Cygwin paths much better than Windows ones. | ||
| 120 | - # Also the Cygwin tests are assuming Cygwin paths. | ||
| 121 | - path = cygpath(path) | ||
| 122 | - if not osp.isabs(path): | ||
| 123 | - path = osp.normpath(osp.join(osp.dirname(d), path)) | ||
| 124 | - return find_submodule_git_dir(path) | ||
| 125 | - # END handle exception | ||
| 126 | - return None | ||
| 147 | + path = find_worktree_git_dir(d) | ||
| 148 | + if path is None: | ||
| 149 | + return None | ||
| 150 | + | ||
| 151 | + if Git.is_cygwin(): | ||
| 152 | + # Cygwin creates submodules prefixed with `/cygdrive/...`. | ||
| 153 | + # Cygwin git understands Cygwin paths much better than Windows ones. | ||
| 154 | + # Also the Cygwin tests are assuming Cygwin paths. | ||
| 155 | + path = cygpath(path) | ||
| 156 | + if not osp.isabs(path): | ||
| 157 | + path = osp.normpath(osp.join(osp.dirname(d), path)) | ||
| 158 | + return path if is_git_dir(path) else None | ||
| 127 | 159 | ||
| 128 | 160 | ||
| 129 | 161 | def short_to_long(odb: "GitCmdObjectDB", hexsha: str) -> Optional[bytes]: | |
| Back | FazBrowse Home | New Git URL |
0 commit comments