GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing github.com — GitPython/git/repo/base.py at 3.1.45 · gitpython-developers/GitPython Expand file tree/ Copy pathbase.pyMore file actions 1638 lines (1383 loc) · 58.6 KB / Copy pathbase.pyFile metadata and controls1638 lines (1383 loc) · 58.6 KB 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 # Copyright (C) 2008, 2009 Michael Trier ([email protected]) and contributors # # This module is part of GitPython and is released under the # 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ from __future__ import annotations __all__ = ["Repo"] import gc import logging import os import os.path as osp from pathlib import Path import re import shlex import sys import warnings import gitdb from gitdb.db.loose import LooseObjectDB from gitdb.exc import BadObject from git.cmd import Git, handle_process_output from git.compat import defenc, safe_decode from git.config import GitConfigParser from git.db import GitCmdObjectDB from git.exc import ( GitCommandError, InvalidGitRepositoryError, NoSuchPathError, ) from git.index import IndexFile from git.objects import Submodule, RootModule, Commit from git.refs import HEAD, Head, Reference, TagReference from git.remote import Remote, add_progress, to_progress_instance from git.util import ( Actor, cygpath, expand_path, finalize_process, hex_to_bin, remove_password_if_present, ) from .fun import ( find_submodule_git_dir, find_worktree_git_dir, is_git_dir, rev_parse, touch, ) # typing ------------------------------------------------------ from git.types import ( CallableProgress, Commit_ish, Lit_config_levels, PathLike, TBD, Tree_ish, assert_never, ) from typing import ( Any, BinaryIO, Callable, Dict, Iterator, List, Mapping, NamedTuple, Optional, Sequence, TYPE_CHECKING, TextIO, Tuple, Type, Union, cast, ) from git.types import ConfigLevels_Tup, TypedDict if TYPE_CHECKING: from git.objects import Tree from git.objects.submodule.base import UpdateProgress from git.refs.symbolic import SymbolicReference from git.remote import RemoteProgress from git.util import IterableList # ----------------------------------------------------------- _logger = logging.getLogger(__name__) class BlameEntry(NamedTuple): commit: Dict[str, Commit] linenos: range orig_path: Optional[str] orig_linenos: range class Repo: """Represents a git repository and allows you to query references, create commit information, generate diffs, create and clone repositories, and query the log. The following attributes are worth using: * :attr:`working_dir` is the working directory of the git command, which is the working tree directory if available or the ``.git`` directory in case of bare repositories. * :attr:`working_tree_dir` is the working tree directory, but will return ``None`` if we are a bare repository. * :attr:`git_dir` is the ``.git`` repository directory, which is always set. """ DAEMON_EXPORT_FILE = "git-daemon-export-ok" # Must exist, or __del__ will fail in case we raise on `__init__()`. git = cast("Git", None) working_dir: PathLike """The working directory of the git command.""" _working_tree_dir: Optional[PathLike] = None git_dir: PathLike """The ``.git`` repository directory.""" _common_dir: PathLike = "" # Precompiled regex re_whitespace = re.compile(r"\s+") re_hexsha_only = re.compile(r"^[0-9A-Fa-f]{40}$") re_hexsha_shortened = re.compile(r"^[0-9A-Fa-f]{4,40}$") re_envvars = re.compile(r"(\$(\{\s?)?[a-zA-Z_]\w*(\}\s?)?|%\s?[a-zA-Z_]\w*\s?%)") re_author_committer_start = re.compile(r"^(author|committer)") re_tab_full_line = re.compile(r"^\t(.*)$") unsafe_git_clone_options = [ # Executes arbitrary commands: "--upload-pack", "-u", # Can override configuration variables that execute arbitrary commands: "--config", "-c", ] """Options to :manpage:`git-clone(1)` that allow arbitrary commands to be executed. The ``--upload-pack``/``-u`` option allows users to execute arbitrary commands directly: https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---upload-packltupload-packgt The ``--config``/``-c`` option allows users to override configuration variables like ``protocol.allow`` and ``core.gitProxy`` to execute arbitrary commands: https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---configltkeygtltvaluegt """ # Invariants config_level: ConfigLevels_Tup = ("system", "user", "global", "repository") """Represents the configuration level of a configuration file.""" # Subclass configuration GitCommandWrapperType = Git """Subclasses may easily bring in their own custom types by placing a constructor or type here.""" def __init__( self, path: Optional[PathLike] = None, odbt: Type[LooseObjectDB] = GitCmdObjectDB, search_parent_directories: bool = False, expand_vars: bool = True, ) -> None: R"""Create a new :class:`Repo` instance. :param path: The path to either the worktree directory or the .git directory itself:: repo = Repo("/Users/mtrier/Development/git-python") repo = Repo("/Users/mtrier/Development/git-python.git") repo = Repo("~/Development/git-python.git") repo = Repo("$REPOSITORIES/Development/git-python.git") repo = Repo(R"C:\Users\mtrier\Development\git-python\.git") - In *Cygwin*, `path` may be a ``cygdrive/...`` prefixed path. - If `path` is ``None`` or an empty string, :envvar:`GIT_DIR` is used. If that environment variable is absent or empty, the current directory is used. :param odbt: Object DataBase type - a type which is constructed by providing the directory containing the database objects, i.e. ``.git/objects``. It will be used to access all object data. :param search_parent_directories: If ``True``, all parent directories will be searched for a valid repo as well. Please note that this was the default behaviour in older versions of GitPython, which is considered a bug though. :raise git.exc.InvalidGitRepositoryError: :raise git.exc.NoSuchPathError: :return: :class:`Repo` """ epath = path or os.getenv("GIT_DIR") if not epath: epath = os.getcwd() 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 = 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" + "\nfor security reasons and may be removed in the future!!", stacklevel=1, ) epath = expand_path(epath, expand_vars) if epath is not None: if not os.path.exists(epath): raise NoSuchPathError(epath) # Walk up the path to find the `.git` dir. curpath = epath git_dir = None while curpath: # ABOUT osp.NORMPATH # It's important to normalize the paths, as submodules will otherwise # initialize their repo instances with paths that depend on path-portions # that will not exist after being removed. It's just cleaner. if is_git_dir(curpath): git_dir = curpath # from man git-config : core.worktree # Set the path to the root of the working tree. If GIT_COMMON_DIR # environment variable is set, core.worktree is ignored and not used for # determining the root of working tree. This can be overridden by the # GIT_WORK_TREE environment variable. The value can be an absolute path # or relative to the path to the .git directory, which is either # specified by GIT_DIR, or automatically discovered. If GIT_DIR is # specified but none of GIT_WORK_TREE and core.worktree is specified, # the current working directory is regarded as the top level of your # working tree. self._working_tree_dir = os.path.dirname(git_dir) if os.environ.get("GIT_COMMON_DIR") is None: gitconf = self._config_reader("repository", git_dir) if gitconf.has_option("core", "worktree"): self._working_tree_dir = gitconf.get("core", "worktree") if "GIT_WORK_TREE" in os.environ: self._working_tree_dir = os.getenv("GIT_WORK_TREE") break dotgit = osp.join(curpath, ".git") sm_gitpath = find_submodule_git_dir(dotgit) if sm_gitpath is not None: git_dir = osp.normpath(sm_gitpath) sm_gitpath = find_submodule_git_dir(dotgit) if sm_gitpath is None: sm_gitpath = find_worktree_git_dir(dotgit) if sm_gitpath is not None: git_dir = expand_path(sm_gitpath, expand_vars) self._working_tree_dir = curpath break if not search_parent_directories: break curpath, tail = osp.split(curpath) if not tail: break # END while curpath if git_dir is None: raise InvalidGitRepositoryError(epath) self.git_dir = git_dir self._bare = False try: self._bare = self.config_reader("repository").getboolean("core", "bare") except Exception: # Let's not assume the option exists, although it should. pass try: common_dir = (Path(self.git_dir) / "commondir").read_text().splitlines()[0].strip() self._common_dir = osp.join(self.git_dir, common_dir) except OSError: self._common_dir = "" # Adjust the working directory in case we are actually bare - we didn't know # that in the first place. if self._bare: self._working_tree_dir = None # END working dir handling self.working_dir: PathLike = self._working_tree_dir or self.common_dir self.git = self.GitCommandWrapperType(self.working_dir) # Special handling, in special times. rootpath = osp.join(self.common_dir, "objects") if issubclass(odbt, GitCmdObjectDB): self.odb = odbt(rootpath, self.git) else: self.odb = odbt(rootpath) def __enter__(self) -> "Repo": return self def __exit__(self, *args: Any) -> None: self.close() def __del__(self) -> None: try: self.close() except Exception: pass def close(self) -> None: if self.git: self.git.clear_cache() # Tempfiles objects on Windows are holding references to open files until # they are collected by the garbage collector, thus preventing deletion. # TODO: Find these references and ensure they are closed and deleted # synchronously rather than forcing a gc collection. if sys.platform == "win32": gc.collect() gitdb.util.mman.collect() if sys.platform == "win32": gc.collect() def __eq__(self, rhs: object) -> bool: if isinstance(rhs, Repo): return self.git_dir == rhs.git_dir return False def __ne__(self, rhs: object) -> bool: return not self.__eq__(rhs) def __hash__(self) -> int: return hash(self.git_dir) @property def description(self) -> str: """The project's description""" filename = osp.join(self.git_dir, "description") with open(filename, "rb") as fp: return fp.read().rstrip().decode(defenc) @description.setter def description(self, descr: str) -> None: filename = osp.join(self.git_dir, "description") with open(filename, "wb") as fp: fp.write((descr + "\n").encode(defenc)) @property def working_tree_dir(self) -> Optional[PathLike]: """ :return: The working tree directory of our git repository. If this is a bare repository, ``None`` is returned. """ return self._working_tree_dir @property def common_dir(self) -> PathLike: """ :return: The git dir that holds everything except possibly HEAD, FETCH_HEAD, ORIG_HEAD, COMMIT_EDITMSG, index, and logs/. """ return self._common_dir or self.git_dir @property def bare(self) -> bool: """:return: ``True`` if the repository is bare""" return self._bare @property def heads(self) -> "IterableList[Head]": """A list of :class:`~git.refs.head.Head` objects representing the branch heads in this repo. :return: ``git.IterableList(Head, ...)`` """ return Head.list_items(self) @property def branches(self) -> "IterableList[Head]": """Alias for heads. A list of :class:`~git.refs.head.Head` objects representing the branch heads in this repo. :return: ``git.IterableList(Head, ...)`` """ return self.heads @property def references(self) -> "IterableList[Reference]": """A list of :class:`~git.refs.reference.Reference` objects representing tags, heads and remote references. :return: ``git.IterableList(Reference, ...)`` """ return Reference.list_items(self) @property def refs(self) -> "IterableList[Reference]": """Alias for references. A list of :class:`~git.refs.reference.Reference` objects representing tags, heads and remote references. :return: ``git.IterableList(Reference, ...)`` """ return self.references @property def index(self) -> "IndexFile": """ :return: A :class:`~git.index.base.IndexFile` representing this repository's index. :note: This property can be expensive, as the returned :class:`~git.index.base.IndexFile` will be reinitialized. It is recommended to reuse the object. """ return IndexFile(self) @property def head(self) -> "HEAD": """ :return: :class:`~git.refs.head.HEAD` object pointing to the current head reference """ return HEAD(self, "HEAD") @property def remotes(self) -> "IterableList[Remote]": """A list of :class:`~git.remote.Remote` objects allowing to access and manipulate remotes. :return: ``git.IterableList(Remote, ...)`` """ return Remote.list_items(self) def remote(self, name: str = "origin") -> "Remote": """:return: The remote with the specified name :raise ValueError: If no remote with such a name exists. """ r = Remote(self, name) if not r.exists(): raise ValueError("Remote named '%s' didn't exist" % name) return r # { Submodules @property def submodules(self) -> "IterableList[Submodule]": """ :return: git.IterableList(Submodule, ...) of direct submodules available from the current head """ return Submodule.list_items(self) def submodule(self, name: str) -> "Submodule": """:return: The submodule with the given name :raise ValueError: If no such submodule exists. """ try: return self.submodules[name] except IndexError as e: raise ValueError("Didn't find submodule named %r" % name) from e # END exception handling def create_submodule(self, *args: Any, **kwargs: Any) -> Submodule: """Create a new submodule. :note: For a description of the applicable parameters, see the documentation of :meth:`Submodule.add <git.objects.submodule.base.Submodule.add>`. :return: The created submodule. """ return Submodule.add(self, *args, **kwargs) def iter_submodules(self, *args: Any, **kwargs: Any) -> Iterator[Submodule]: """An iterator yielding Submodule instances. See the :class:`~git.objects.util.Traversable` interface for a description of `args` and `kwargs`. :return: Iterator """ return RootModule(self).traverse(*args, **kwargs) def submodule_update(self, *args: Any, **kwargs: Any) -> Iterator[Submodule]: """Update the submodules, keeping the repository consistent as it will take the previous state into consideration. :note: For more information, please see the documentation of :meth:`RootModule.update <git.objects.submodule.root.RootModule.update>`. """ return RootModule(self).update(*args, **kwargs) # }END submodules @property def tags(self) -> "IterableList[TagReference]": """A list of :class:`~git.refs.tag.TagReference` objects that are available in this repo. :return: ``git.IterableList(TagReference, ...)`` """ return TagReference.list_items(self) def tag(self, path: PathLike) -> TagReference: """ :return: :class:`~git.refs.tag.TagReference` object, reference pointing to a :class:`~git.objects.commit.Commit` or tag :param path: Path to the tag reference, e.g. ``0.1.5`` or ``tags/0.1.5``. """ full_path = self._to_full_tag_path(path) return TagReference(self, full_path) @staticmethod def _to_full_tag_path(path: PathLike) -> str: path_str = str(path) if path_str.startswith(TagReference._common_path_default + "/"): return path_str if path_str.startswith(TagReference._common_default + "/"): return Reference._common_path_default + "/" + path_str else: return TagReference._common_path_default + "/" + path_str def create_head( self, path: PathLike, commit: Union["SymbolicReference", "str"] = "HEAD", force: bool = False, logmsg: Optional[str] = None, ) -> "Head": """Create a new head within the repository. :note: For more documentation, please see the :meth:`Head.create <git.refs.head.Head.create>` method. :return: Newly created :class:`~git.refs.head.Head` Reference. """ return Head.create(self, path, commit, logmsg, force) def delete_head(self, *heads: "Union[str, Head]", **kwargs: Any) -> None: """Delete the given heads. :param kwargs: Additional keyword arguments to be passed to :manpage:`git-branch(1)`. """ return Head.delete(self, *heads, **kwargs) def create_tag( self, path: PathLike, ref: Union[str, "SymbolicReference"] = "HEAD", message: Optional[str] = None, force: bool = False, **kwargs: Any, ) -> TagReference: """Create a new tag reference. :note: For more documentation, please see the :meth:`TagReference.create <git.refs.tag.TagReference.create>` method. :return: :class:`~git.refs.tag.TagReference` object """ return TagReference.create(self, path, ref, message, force, **kwargs) def delete_tag(self, *tags: TagReference) -> None: """Delete the given tag references.""" return TagReference.delete(self, *tags) def create_remote(self, name: str, url: str, **kwargs: Any) -> Remote: """Create a new remote. For more information, please see the documentation of the :meth:`Remote.create <git.remote.Remote.create>` method. :return: :class:`~git.remote.Remote` reference """ return Remote.create(self, name, url, **kwargs) def delete_remote(self, remote: "Remote") -> str: """Delete the given remote.""" return Remote.remove(self, remote) def _get_config_path(self, config_level: Lit_config_levels, git_dir: Optional[PathLike] = None) -> str: if git_dir is None: git_dir = self.git_dir # We do not support an absolute path of the gitconfig on Windows. # Use the global config instead. if sys.platform == "win32" and config_level == "system": config_level = "global" if config_level == "system": return "/etc/gitconfig" elif config_level == "user": config_home = os.environ.get("XDG_CONFIG_HOME") or osp.join(os.environ.get("HOME", "~"), ".config") return osp.normpath(osp.expanduser(osp.join(config_home, "git", "config"))) elif config_level == "global": return osp.normpath(osp.expanduser("~/.gitconfig")) elif config_level == "repository": repo_dir = self._common_dir or git_dir if not repo_dir: raise NotADirectoryError else: return osp.normpath(osp.join(repo_dir, "config")) else: assert_never( # type: ignore[unreachable] config_level, ValueError(f"Invalid configuration level: {config_level!r}"), ) def config_reader( self, config_level: Optional[Lit_config_levels] = None, ) -> GitConfigParser: """ :return: :class:`~git.config.GitConfigParser` allowing to read the full git configuration, but not to write it. The configuration will include values from the system, user and repository configuration files. :param config_level: For possible values, see the :meth:`config_writer` method. If ``None``, all applicable levels will be used. Specify a level in case you know which file you wish to read to prevent reading multiple files. :note: On Windows, system configuration cannot currently be read as the path is unknown, instead the global path will be used. """ return self._config_reader(config_level=config_level) def _config_reader( self, config_level: Optional[Lit_config_levels] = None, git_dir: Optional[PathLike] = None, ) -> GitConfigParser: if config_level is None: files = [ self._get_config_path(cast(Lit_config_levels, f), git_dir) for f in self.config_level if cast(Lit_config_levels, f) ] else: files = [self._get_config_path(config_level, git_dir)] return GitConfigParser(files, read_only=True, repo=self) def config_writer(self, config_level: Lit_config_levels = "repository") -> GitConfigParser: """ :return: A :class:`~git.config.GitConfigParser` allowing to write values of the specified configuration file level. Config writers should be retrieved, used to change the configuration, and written right away as they will lock the configuration file in question and prevent other's to write it. :param config_level: One of the following values: * ``"system"`` = system wide configuration file * ``"global"`` = user level configuration file * ``"`repository"`` = configuration file for this repository only """ return GitConfigParser(self._get_config_path(config_level), read_only=False, repo=self, merge_includes=False) def commit(self, rev: Union[str, Commit_ish, None] = None) -> Commit: """The :class:`~git.objects.commit.Commit` object for the specified revision. :param rev: Revision specifier, see :manpage:`git-rev-parse(1)` for viable options. :return: :class:`~git.objects.commit.Commit` """ if rev is None: return self.head.commit return self.rev_parse(str(rev) + "^0") def iter_trees(self, *args: Any, **kwargs: Any) -> Iterator["Tree"]: """:return: Iterator yielding :class:`~git.objects.tree.Tree` objects :note: Accepts all arguments known to the :meth:`iter_commits` method. """ return (c.tree for c in self.iter_commits(*args, **kwargs)) def tree(self, rev: Union[Tree_ish, str, None] = None) -> "Tree": """The :class:`~git.objects.tree.Tree` object for the given tree-ish revision. Examples:: repo.tree(repo.heads[0]) :param rev: A revision pointing to a Treeish (being a commit or tree). :return: :class:`~git.objects.tree.Tree` :note: If you need a non-root level tree, find it by iterating the root tree. Otherwise it cannot know about its path relative to the repository root and subsequent operations might have unexpected results. """ if rev is None: return self.head.commit.tree return self.rev_parse(str(rev) + "^{tree}") def iter_commits( self, rev: Union[str, Commit, "SymbolicReference", None] = None, paths: Union[PathLike, Sequence[PathLike]] = "", **kwargs: Any, ) -> Iterator[Commit]: """An iterator of :class:`~git.objects.commit.Commit` objects representing the history of a given ref/commit. :param rev: Revision specifier, see :manpage:`git-rev-parse(1)` for viable options. If ``None``, the active branch will be used. :param paths: An optional path or a list of paths. If set, only commits that include the path or paths will be returned. :param kwargs: Arguments to be passed to :manpage:`git-rev-list(1)`. Common ones are ``max_count`` and ``skip``. :note: To receive only commits between two named revisions, use the ``"revA...revB"`` revision specifier. :return: Iterator of :class:`~git.objects.commit.Commit` objects """ if rev is None: rev = self.head.commit return Commit.iter_items(self, rev, paths, **kwargs) def merge_base(self, *rev: TBD, **kwargs: Any) -> List[Commit]: R"""Find the closest common ancestor for the given revision (:class:`~git.objects.commit.Commit`\s, :class:`~git.refs.tag.Tag`\s, :class:`~git.refs.reference.Reference`\s, etc.). :param rev: At least two revs to find the common ancestor for. :param kwargs: Additional arguments to be passed to the ``repo.git.merge_base()`` command which does all the work. :return: A list of :class:`~git.objects.commit.Commit` objects. If ``--all`` was not passed as a keyword argument, the list will have at max one :class:`~git.objects.commit.Commit`, or is empty if no common merge base exists. :raise ValueError: If fewer than two revisions are provided. """ if len(rev) < 2: raise ValueError("Please specify at least two revs, got only %i" % len(rev)) # END handle input res: List[Commit] = [] try: lines: List[str] = self.git.merge_base(*rev, **kwargs).splitlines() except GitCommandError as err: if err.status == 128: raise # END handle invalid rev # Status code 1 is returned if there is no merge-base. # (See: https://github.com/git/git/blob/v2.44.0/builtin/merge-base.c#L19) return res # END exception handling for line in lines: res.append(self.commit(line)) # END for each merge-base return res def is_ancestor(self, ancestor_rev: Commit, rev: Commit) -> bool: """Check if a commit is an ancestor of another. :param ancestor_rev: Rev which should be an ancestor. :param rev: Rev to test against `ancestor_rev`. :return: ``True`` if `ancestor_rev` is an ancestor to `rev`. """ try: self.git.merge_base(ancestor_rev, rev, is_ancestor=True) except GitCommandError as err: if err.status == 1: return False raise return True def is_valid_object(self, sha: str, object_type: Union[str, None] = None) -> bool: try: complete_sha = self.odb.partial_to_complete_sha_hex(sha) object_info = self.odb.info(complete_sha) if object_type: if object_info.type == object_type.encode(): return True else: _logger.debug( "Commit hash points to an object of type '%s'. Requested were objects of type '%s'", object_info.type.decode(), object_type, ) return False else: return True except BadObject: _logger.debug("Commit hash is invalid.") return False def _get_daemon_export(self) -> bool: if self.git_dir: filename = osp.join(self.git_dir, self.DAEMON_EXPORT_FILE) return osp.exists(filename) def _set_daemon_export(self, value: object) -> None: if self.git_dir: filename = osp.join(self.git_dir, self.DAEMON_EXPORT_FILE) fileexists = osp.exists(filename) if value and not fileexists: touch(filename) elif not value and fileexists: os.unlink(filename) @property def daemon_export(self) -> bool: """If True, git-daemon may export this repository""" return self._get_daemon_export() @daemon_export.setter def daemon_export(self, value: object) -> None: self._set_daemon_export(value) def _get_alternates(self) -> List[str]: """The list of alternates for this repo from which objects can be retrieved. :return: List of strings being pathnames of alternates """ if self.git_dir: alternates_path = osp.join(self.git_dir, "objects", "info", "alternates") if osp.exists(alternates_path): with open(alternates_path, "rb") as f: alts = f.read().decode(defenc) return alts.strip().splitlines() return [] def _set_alternates(self, alts: List[str]) -> None: """Set the alternates. :param alts: The array of string paths representing the alternates at which git should look for objects, i.e. ``/home/user/repo/.git/objects``. :raise git.exc.NoSuchPathError: :note: The method does not check for the existence of the paths in `alts`, as the caller is responsible. """ alternates_path = osp.join(self.common_dir, "objects", "info", "alternates") if not alts: if osp.isfile(alternates_path): os.remove(alternates_path) else: with open(alternates_path, "wb") as f: f.write("\n".join(alts).encode(defenc)) @property def alternates(self) -> List[str]: """Retrieve a list of alternates paths or set a list paths to be used as alternates""" return self._get_alternates() @alternates.setter def alternates(self, alts: List[str]) -> None: self._set_alternates(alts) def is_dirty( self, index: bool = True, working_tree: bool = True, untracked_files: bool = False, submodules: bool = True, path: Optional[PathLike] = None, ) -> bool: """ :return: ``True`` if the repository is considered dirty. By default it will react like a :manpage:`git-status(1)` without untracked files, hence it is dirty if the index or the working copy have changes. """ if self._bare: # Bare repositories with no associated working directory are # always considered to be clean. return False # Start from the one which is fastest to evaluate. default_args = ["--abbrev=40", "--full-index", "--raw"] if not submodules: default_args.append("--ignore-submodules") if path: default_args.extend(["--", str(path)]) if index: # diff index against HEAD. if osp.isfile(self.index.path) and len(self.git.diff("--cached", *default_args)): return True # END index handling if working_tree: # diff index against working tree. if len(self.git.diff(*default_args)): return True # END working tree handling if untracked_files: if len(self._get_untracked_files(path, ignore_submodules=not submodules)): return True # END untracked files return False @property def untracked_files(self) -> List[str]: """ :return: list(str,...) Files currently untracked as they have not been staged yet. Paths are relative to the current working directory of the git command. :note: Ignored files will not appear here, i.e. files mentioned in ``.gitignore``. :note: This property is expensive, as no cache is involved. To process the result, please consider caching it yourself. """ return self._get_untracked_files() def _get_untracked_files(self, *args: Any, **kwargs: Any) -> List[str]: # Make sure we get all files, not only untracked directories. |
| You are visitor 01402520 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |