diff options
Diffstat (limited to 'git/repo/base.py')
-rw-r--r-- | git/repo/base.py | 281 |
1 files changed, 169 insertions, 112 deletions
diff --git a/git/repo/base.py b/git/repo/base.py index e68bc077..a28c9d28 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -4,7 +4,6 @@ # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php -from collections import namedtuple import logging import os import re @@ -33,20 +32,37 @@ from .fun import rev_parse, is_git_dir, find_submodule_git_dir, touch, find_work import gc import gitdb -try: - import pathlib -except ImportError: - pathlib = None +# typing ------------------------------------------------------ +from git.types import TBD, PathLike +from typing_extensions import Literal +from typing import (Any, BinaryIO, Callable, Dict, + Iterator, List, Mapping, Optional, + TextIO, Tuple, Type, Union, + NamedTuple, cast, TYPE_CHECKING) -log = logging.getLogger(__name__) +if TYPE_CHECKING: # only needed for types + from git.util import IterableList + from git.refs.symbolic import SymbolicReference + from git.objects import TagObject, Blob, Tree # NOQA: F401 + +Lit_config_levels = Literal['system', 'global', 'user', 'repository'] -BlameEntry = namedtuple('BlameEntry', ['commit', 'linenos', 'orig_path', 'orig_linenos']) +# ----------------------------------------------------------- +log = logging.getLogger(__name__) __all__ = ('Repo',) +BlameEntry = NamedTuple('BlameEntry', [ + ('commit', Dict[str, TBD]), + ('linenos', range), + ('orig_path', Optional[str]), + ('orig_linenos', range)] +) + + class Repo(object): """Represents a git repository and allows you to query references, gather commit information, generate diffs, create and clone repositories query @@ -63,11 +79,11 @@ class Repo(object): 'git_dir' is the .git repository directory, which is always set.""" DAEMON_EXPORT_FILE = 'git-daemon-export-ok' - git = None # Must exist, or __del__ will fail in case we raise on `__init__()` - working_dir = None - _working_tree_dir = None - git_dir = None - _common_dir = None + git = cast('Git', None) # Must exist, or __del__ will fail in case we raise on `__init__()` + working_dir = None # type: Optional[PathLike] + _working_tree_dir = None # type: Optional[PathLike] + git_dir = None # type: Optional[PathLike] + _common_dir = None # type: Optional[PathLike] # precompiled regex re_whitespace = re.compile(r'\s+') @@ -79,13 +95,14 @@ class Repo(object): # invariants # represents the configuration level of a configuration file - config_level = ("system", "user", "global", "repository") + config_level = ("system", "user", "global", "repository") # type: Tuple[Lit_config_levels, ...] # Subclass configuration # Subclasses may easily bring in their own custom types by placing a constructor or type here GitCommandWrapperType = Git - def __init__(self, path=None, odbt=GitCmdObjectDB, search_parent_directories=False, expand_vars=True): + def __init__(self, path: Optional[PathLike] = None, odbt: Type[GitCmdObjectDB] = GitCmdObjectDB, + search_parent_directories: bool = False, expand_vars: bool = True) -> None: """Create a new Repo instance :param path: @@ -126,8 +143,9 @@ class Repo(object): warnings.warn("The use of environment variables in paths is deprecated" + "\nfor security reasons and may be removed in the future!!") epath = expand_path(epath, expand_vars) - if not os.path.exists(epath): - raise NoSuchPathError(epath) + if epath is not None: + if not os.path.exists(epath): + raise NoSuchPathError(epath) ## Walk up the path to find the `.git` dir. # @@ -178,6 +196,7 @@ class Repo(object): # END while curpath if self.git_dir is None: + self.git_dir = cast(PathLike, self.git_dir) raise InvalidGitRepositoryError(epath) self._bare = False @@ -190,7 +209,7 @@ class Repo(object): try: common_dir = open(osp.join(self.git_dir, 'commondir'), 'rt').readlines()[0].strip() self._common_dir = osp.join(self.git_dir, common_dir) - except (OSError, IOError): + except OSError: self._common_dir = None # adjust the wd in case we are actually bare - we didn't know that @@ -199,28 +218,29 @@ class Repo(object): self._working_tree_dir = None # END working dir handling - self.working_dir = self._working_tree_dir or self.common_dir + self.working_dir = self._working_tree_dir or self.common_dir # type: Optional[PathLike] self.git = self.GitCommandWrapperType(self.working_dir) # special handling, in special times - args = [osp.join(self.common_dir, 'objects')] + rootpath = osp.join(self.common_dir, 'objects') if issubclass(odbt, GitCmdObjectDB): - args.append(self.git) - self.odb = odbt(*args) + self.odb = odbt(rootpath, self.git) + else: + self.odb = odbt(rootpath) - def __enter__(self): + def __enter__(self) -> 'Repo': return self - def __exit__(self, exc_type, exc_value, traceback): + def __exit__(self, exc_type: TBD, exc_value: TBD, traceback: TBD) -> None: self.close() - def __del__(self): + def __del__(self) -> None: try: self.close() except Exception: pass - def close(self): + def close(self) -> None: if self.git: self.git.clear_cache() # Tempfiles objects on Windows are holding references to @@ -235,25 +255,27 @@ class Repo(object): if is_win: gc.collect() - def __eq__(self, rhs): - if isinstance(rhs, Repo): + def __eq__(self, rhs: object) -> bool: + if isinstance(rhs, Repo) and self.git_dir: return self.git_dir == rhs.git_dir return False - def __ne__(self, rhs): + def __ne__(self, rhs: object) -> bool: return not self.__eq__(rhs) - def __hash__(self): + def __hash__(self) -> int: return hash(self.git_dir) # Description property - def _get_description(self): - filename = osp.join(self.git_dir, 'description') + def _get_description(self) -> str: + if self.git_dir: + filename = osp.join(self.git_dir, 'description') with open(filename, 'rb') as fp: return fp.read().rstrip().decode(defenc) - def _set_description(self, descr): - filename = osp.join(self.git_dir, 'description') + def _set_description(self, descr: str) -> None: + if self.git_dir: + filename = osp.join(self.git_dir, 'description') with open(filename, 'wb') as fp: fp.write((descr + '\n').encode(defenc)) @@ -263,25 +285,31 @@ class Repo(object): del _set_description @property - def working_tree_dir(self): + 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): + 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 + if self._common_dir: + return self._common_dir + elif self.git_dir: + return self.git_dir + else: + # or could return "" + raise InvalidGitRepositoryError() @property - def bare(self): + def bare(self) -> bool: """:return: True if the repository is bare""" return self._bare @property - def heads(self): + def heads(self) -> 'IterableList': """A list of ``Head`` objects representing the branch heads in this repo @@ -289,7 +317,7 @@ class Repo(object): return Head.list_items(self) @property - def references(self): + def references(self) -> 'IterableList': """A list of Reference objects representing tags, heads and remote references. :return: IterableList(Reference, ...)""" @@ -302,24 +330,24 @@ class Repo(object): branches = heads @property - def index(self): + def index(self) -> 'IndexFile': """:return: IndexFile representing this repository's index. :note: This property can be expensive, as the returned ``IndexFile`` will be reinitialized. It's recommended to re-use the object.""" return IndexFile(self) @property - def head(self): + def head(self) -> 'HEAD': """:return: HEAD Object pointing to the current head reference""" return HEAD(self, 'HEAD') @property - def remotes(self): + def remotes(self) -> 'IterableList': """A list of Remote objects allowing to access and manipulate remotes :return: ``git.IterableList(Remote, ...)``""" return Remote.list_items(self) - def remote(self, name='origin'): + def remote(self, name: str = 'origin') -> 'Remote': """:return: Remote with the specified name :raise ValueError: if no remote with such a name exists""" r = Remote(self, name) @@ -330,13 +358,13 @@ class Repo(object): #{ Submodules @property - def submodules(self): + def submodules(self) -> 'IterableList': """ :return: git.IterableList(Submodule, ...) of direct submodules available from the current head""" return Submodule.list_items(self) - def submodule(self, name): + def submodule(self, name: str) -> 'IterableList': """ :return: Submodule with the given name :raise ValueError: If no such submodule exists""" try: @@ -345,7 +373,7 @@ class Repo(object): raise ValueError("Didn't find submodule named %r" % name) from e # END exception handling - def create_submodule(self, *args, **kwargs): + def create_submodule(self, *args: Any, **kwargs: Any) -> Submodule: """Create a new submodule :note: See the documentation of Submodule.add for a description of the @@ -353,13 +381,13 @@ class Repo(object): :return: created submodules""" return Submodule.add(self, *args, **kwargs) - def iter_submodules(self, *args, **kwargs): + def iter_submodules(self, *args: Any, **kwargs: Any) -> Iterator: """An iterator yielding Submodule instances, see Traversable interface for a description of args and kwargs :return: Iterator""" return RootModule(self).traverse(*args, **kwargs) - def submodule_update(self, *args, **kwargs): + def submodule_update(self, *args: Any, **kwargs: Any) -> Iterator: """Update the submodules, keeping the repository consistent as it will take the previous state into consideration. For more information, please see the documentation of RootModule.update""" @@ -368,41 +396,45 @@ class Repo(object): #}END submodules @property - def tags(self): + def tags(self) -> 'IterableList': """A list of ``Tag`` objects that are available in this repo :return: ``git.IterableList(TagReference, ...)`` """ return TagReference.list_items(self) - def tag(self, path): + def tag(self, path: PathLike) -> TagReference: """:return: TagReference Object, reference pointing to a Commit or Tag :param path: path to the tag reference, i.e. 0.1.5 or tags/0.1.5 """ return TagReference(self, path) - def create_head(self, path, commit='HEAD', force=False, logmsg=None): + def create_head(self, path: PathLike, commit: str = 'HEAD', + force: bool = False, logmsg: Optional[str] = None + ) -> 'SymbolicReference': """Create a new head within the repository. For more documentation, please see the Head.create method. :return: newly created Head Reference""" return Head.create(self, path, commit, force, logmsg) - def delete_head(self, *heads, **kwargs): + def delete_head(self, *heads: 'SymbolicReference', **kwargs: Any) -> None: """Delete the given heads :param kwargs: Additional keyword arguments to be passed to git-branch""" return Head.delete(self, *heads, **kwargs) - def create_tag(self, path, ref='HEAD', message=None, force=False, **kwargs): + def create_tag(self, path: PathLike, ref: str = 'HEAD', + message: Optional[str] = None, force: bool = False, **kwargs: Any + ) -> TagReference: """Create a new tag reference. For more documentation, please see the TagReference.create method. :return: TagReference object """ return TagReference.create(self, path, ref, message, force, **kwargs) - def delete_tag(self, *tags): + def delete_tag(self, *tags: TBD) -> None: """Delete the given tag references""" return TagReference.delete(self, *tags) - def create_remote(self, name, url, **kwargs): + def create_remote(self, name: str, url: PathLike, **kwargs: Any) -> Remote: """Create a new remote. For more information, please see the documentation of the Remote.create @@ -411,11 +443,11 @@ class Repo(object): :return: Remote reference""" return Remote.create(self, name, url, **kwargs) - def delete_remote(self, remote): + def delete_remote(self, remote: 'Remote') -> Type['Remote']: """Delete the given remote.""" return Remote.remove(self, remote) - def _get_config_path(self, config_level): + def _get_config_path(self, config_level: Lit_config_levels) -> str: # we do not support an absolute path of the gitconfig on windows , # use the global config instead if is_win and config_level == "system": @@ -429,11 +461,15 @@ class Repo(object): elif config_level == "global": return osp.normpath(osp.expanduser("~/.gitconfig")) elif config_level == "repository": - return osp.normpath(osp.join(self._common_dir or self.git_dir, "config")) + repo_dir = self._common_dir or self.git_dir + if not repo_dir: + raise NotADirectoryError + else: + return osp.normpath(osp.join(repo_dir, "config")) raise ValueError("Invalid configuration level: %r" % config_level) - def config_reader(self, config_level=None): + def config_reader(self, config_level: Optional[Lit_config_levels] = None) -> GitConfigParser: """ :return: GitConfigParser allowing to read the full git configuration, but not to write it @@ -454,7 +490,7 @@ class Repo(object): files = [self._get_config_path(config_level)] return GitConfigParser(files, read_only=True, repo=self) - def config_writer(self, config_level="repository"): + def config_writer(self, config_level: Lit_config_levels = "repository") -> GitConfigParser: """ :return: GitConfigParser allowing to write values of the specified configuration file level. @@ -469,7 +505,8 @@ class Repo(object): repository = configuration file for this repository only""" return GitConfigParser(self._get_config_path(config_level), read_only=False, repo=self) - def commit(self, rev=None): + def commit(self, rev: Optional[TBD] = None + ) -> Union['SymbolicReference', Commit, 'TagObject', 'Blob', 'Tree']: """The Commit object for the specified revision :param rev: revision specifier, see git-rev-parse for viable options. @@ -479,12 +516,12 @@ class Repo(object): return self.head.commit return self.rev_parse(str(rev) + "^0") - def iter_trees(self, *args, **kwargs): + def iter_trees(self, *args: Any, **kwargs: Any) -> Iterator['Tree']: """:return: Iterator yielding Tree objects :note: Takes all arguments known to iter_commits method""" return (c.tree for c in self.iter_commits(*args, **kwargs)) - def tree(self, rev=None): + def tree(self, rev: Union['Commit', 'Tree', None] = None) -> 'Tree': """The Tree object for the given treeish revision Examples:: @@ -501,7 +538,8 @@ class Repo(object): return self.head.commit.tree return self.rev_parse(str(rev) + "^{tree}") - def iter_commits(self, rev=None, paths='', **kwargs): + def iter_commits(self, rev: Optional[TBD] = None, paths: Union[PathLike, List[PathLike]] = '', + **kwargs: Any) -> Iterator[Commit]: """A list of Commit objects representing the history of a given ref/commit :param rev: @@ -525,7 +563,8 @@ class Repo(object): return Commit.iter_items(self, rev, paths, **kwargs) - def merge_base(self, *rev, **kwargs): + def merge_base(self, *rev: TBD, **kwargs: Any + ) -> List[Union['SymbolicReference', Commit, 'TagObject', 'Blob', 'Tree', None]]: """Find the closest common ancestor for the given revision (e.g. Commits, Tags, References, etc) :param rev: At least two revs to find the common ancestor for. @@ -538,9 +577,9 @@ class Repo(object): raise ValueError("Please specify at least two revs, got only %i" % len(rev)) # end handle input - res = [] + res = [] # type: List[Union['SymbolicReference', Commit, 'TagObject', 'Blob', 'Tree', None]] try: - lines = self.git.merge_base(*rev, **kwargs).splitlines() + lines = self.git.merge_base(*rev, **kwargs).splitlines() # List[str] except GitCommandError as err: if err.status == 128: raise @@ -556,7 +595,7 @@ class Repo(object): return res - def is_ancestor(self, ancestor_rev, rev): + 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 @@ -571,12 +610,14 @@ class Repo(object): raise return True - def _get_daemon_export(self): - filename = osp.join(self.git_dir, self.DAEMON_EXPORT_FILE) + 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): - filename = osp.join(self.git_dir, self.DAEMON_EXPORT_FILE) + 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) @@ -588,11 +629,12 @@ class Repo(object): del _get_daemon_export del _set_daemon_export - def _get_alternates(self): + 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""" - alternates_path = osp.join(self.git_dir, 'objects', 'info', '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: @@ -600,7 +642,7 @@ class Repo(object): return alts.strip().splitlines() return [] - def _set_alternates(self, alts): + def _set_alternates(self, alts: List[str]) -> None: """Sets the alternates :param alts: @@ -622,8 +664,8 @@ class Repo(object): alternates = property(_get_alternates, _set_alternates, doc="Retrieve a list of alternates paths or set a list paths to be used as alternates") - def is_dirty(self, index=True, working_tree=True, untracked_files=False, - submodules=True, path=None): + 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``, the repository is considered dirty. By default it will react @@ -639,7 +681,7 @@ class Repo(object): if not submodules: default_args.append('--ignore-submodules') if path: - default_args.extend(["--", path]) + default_args.extend(["--", str(path)]) if index: # diff index against HEAD if osp.isfile(self.index.path) and \ @@ -658,7 +700,7 @@ class Repo(object): return False @property - def untracked_files(self): + def untracked_files(self) -> List[str]: """ :return: list(str,...) @@ -673,7 +715,7 @@ class Repo(object): consider caching it yourself.""" return self._get_untracked_files() - def _get_untracked_files(self, *args, **kwargs): + def _get_untracked_files(self, *args: Any, **kwargs: Any) -> List[str]: # make sure we get all files, not only untracked directories proc = self.git.status(*args, porcelain=True, @@ -697,7 +739,7 @@ class Repo(object): finalize_process(proc) return untracked_files - def ignored(self, *paths): + def ignored(self, *paths: PathLike) -> List[PathLike]: """Checks if paths are ignored via .gitignore Doing so using the "git check-ignore" method. @@ -711,13 +753,13 @@ class Repo(object): return proc.replace("\\\\", "\\").replace('"', "").split("\n") @property - def active_branch(self): + def active_branch(self) -> 'SymbolicReference': """The name of the currently active branch. :return: Head to the active branch""" return self.head.reference - def blame_incremental(self, rev, file, **kwargs): + def blame_incremental(self, rev: TBD, file: TBD, **kwargs: Any) -> Optional[Iterator['BlameEntry']]: """Iterator for blame information for the given file at the given revision. Unlike .blame(), this does not return the actual file's contents, only @@ -732,7 +774,7 @@ class Repo(object): should get a continuous range spanning all line numbers in the file. """ data = self.git.blame(rev, '--', file, p=True, incremental=True, stdout_as_string=False, **kwargs) - commits = {} + commits = {} # type: Dict[str, TBD] stream = (line for line in data.split(b'\n') if line) while True: @@ -740,10 +782,11 @@ class Repo(object): line = next(stream) # when exhausted, causes a StopIteration, terminating this function except StopIteration: return - hexsha, orig_lineno, lineno, num_lines = line.split() - lineno = int(lineno) - num_lines = int(num_lines) - orig_lineno = int(orig_lineno) + split_line = line.split() # type: Tuple[str, str, str, str] + hexsha, orig_lineno_str, lineno_str, num_lines_str = split_line + lineno = int(lineno_str) + num_lines = int(num_lines_str) + orig_lineno = int(orig_lineno_str) if hexsha not in commits: # Now read the next few lines and build up a dict of properties # for this commit @@ -791,22 +834,24 @@ class Repo(object): safe_decode(orig_filename), range(orig_lineno, orig_lineno + num_lines)) - def blame(self, rev, file, incremental=False, **kwargs): + def blame(self, rev: TBD, file: TBD, incremental: bool = False, **kwargs: Any + ) -> Union[List[List[Union[Optional['Commit'], List[str]]]], Optional[Iterator[BlameEntry]]]: """The blame information for the given file at the given revision. :param rev: revision specifier, see git-rev-parse for viable options. :return: list: [git.Commit, list: [<line>]] - A list of tuples associating a Commit object with a list of lines that + A list of lists associating a Commit object with a list of lines that changed within the given commit. The Commit objects will be given in order of appearance.""" if incremental: return self.blame_incremental(rev, file, **kwargs) data = self.git.blame(rev, '--', file, p=True, stdout_as_string=False, **kwargs) - commits = {} - blames = [] - info = None + commits = {} # type: Dict[str, Any] + blames = [] # type: List[List[Union[Optional['Commit'], List[str]]]] + + info = {} # type: Dict[str, Any] # use Any until TypedDict available keepends = True for line in data.splitlines(keepends): @@ -891,7 +936,8 @@ class Repo(object): pass # end handle line contents blames[-1][0] = c - blames[-1][1].append(line) + if blames[-1][1] is not None: + blames[-1][1].append(line) info = {'id': sha} # END if we collected commit info # END distinguish filename,summary,rest @@ -900,7 +946,8 @@ class Repo(object): return blames @classmethod - def init(cls, path=None, mkdir=True, odbt=GitCmdObjectDB, expand_vars=True, **kwargs): + def init(cls, path: PathLike = None, mkdir: bool = True, odbt: Type[GitCmdObjectDB] = GitCmdObjectDB, + expand_vars: bool = True, **kwargs: Any) -> 'Repo': """Initialize a git repository at the given path if specified :param path: @@ -938,9 +985,10 @@ class Repo(object): return cls(path, odbt=odbt) @classmethod - def _clone(cls, git, url, path, odb_default_type, progress, multi_options=None, **kwargs): - if progress is not None: - progress = to_progress_instance(progress) + def _clone(cls, git: 'Git', url: PathLike, path: PathLike, odb_default_type: Type[GitCmdObjectDB], + progress: Optional[Callable], multi_options: Optional[List[str]] = None, **kwargs: Any + ) -> 'Repo': + progress_checked = to_progress_instance(progress) odbt = kwargs.pop('odbt', odb_default_type) @@ -964,9 +1012,10 @@ class Repo(object): if multi_options: multi = ' '.join(multi_options).split(' ') proc = git.clone(multi, Git.polish_url(url), clone_path, with_extended_output=True, as_process=True, - v=True, universal_newlines=True, **add_progress(kwargs, git, progress)) - if progress: - handle_process_output(proc, None, progress.new_message_handler(), finalize_process, decode_streams=False) + v=True, universal_newlines=True, **add_progress(kwargs, git, progress_checked)) + if progress_checked: + handle_process_output(proc, None, progress_checked.new_message_handler(), + finalize_process, decode_streams=False) else: (stdout, stderr) = proc.communicate() cmdline = getattr(proc, 'args', '') @@ -977,8 +1026,8 @@ class Repo(object): # our git command could have a different working dir than our actual # environment, hence we prepend its working dir if required - if not osp.isabs(path) and git.working_dir: - path = osp.join(git._working_dir, path) + if not osp.isabs(path): + path = osp.join(git._working_dir, path) if git._working_dir is not None else path repo = cls(path, odbt=odbt) @@ -996,7 +1045,8 @@ class Repo(object): # END handle remote repo return repo - def clone(self, path, progress=None, multi_options=None, **kwargs): + def clone(self, path: PathLike, progress: Optional[Callable] = None, + multi_options: Optional[List[str]] = None, **kwargs: Any) -> 'Repo': """Create a clone from this repository. :param path: is the full path of the new repo (traditionally ends with ./<name>.git). @@ -1014,7 +1064,9 @@ class Repo(object): return self._clone(self.git, self.common_dir, path, type(self.odb), progress, multi_options, **kwargs) @classmethod - def clone_from(cls, url, to_path, progress=None, env=None, multi_options=None, **kwargs): + def clone_from(cls, url: PathLike, to_path: PathLike, progress: Optional[Callable] = None, + env: Optional[Mapping[str, Any]] = None, + multi_options: Optional[List[str]] = None, **kwargs: Any) -> 'Repo': """Create a clone from the given URL :param url: valid git url, see http://www.kernel.org/pub/software/scm/git/docs/git-clone.html#URLS @@ -1034,7 +1086,8 @@ class Repo(object): git.update_environment(**env) return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs) - def archive(self, ostream, treeish=None, prefix=None, **kwargs): + def archive(self, ostream: Union[TextIO, BinaryIO], treeish: Optional[str] = None, + prefix: Optional[str] = None, **kwargs: Any) -> 'Repo': """Archive the tree at the given revision. :param ostream: file compatible stream object to which the archive will be written as bytes @@ -1055,14 +1108,14 @@ class Repo(object): kwargs['prefix'] = prefix kwargs['output_stream'] = ostream path = kwargs.pop('path', []) + path = cast(Union[PathLike, List[PathLike], Tuple[PathLike, ...]], path) if not isinstance(path, (tuple, list)): path = [path] # end assure paths is list - self.git.archive(treeish, *path, **kwargs) return self - def has_separate_working_tree(self): + def has_separate_working_tree(self) -> bool: """ :return: True if our git_dir is not at the root of our working_tree_dir, but a .git file with a platform agnositic symbolic link. Our git_dir will be wherever the .git file points to @@ -1070,21 +1123,25 @@ class Repo(object): """ if self.bare: return False - return osp.isfile(osp.join(self.working_tree_dir, '.git')) + if self.working_tree_dir: + return osp.isfile(osp.join(self.working_tree_dir, '.git')) + else: + return False # or raise Error? rev_parse = rev_parse - def __repr__(self): + def __repr__(self) -> str: clazz = self.__class__ return '<%s.%s %r>' % (clazz.__module__, clazz.__name__, self.git_dir) - def currently_rebasing_on(self): + def currently_rebasing_on(self) -> Union['SymbolicReference', Commit, 'TagObject', 'Blob', 'Tree', None]: """ :return: The commit which is currently being replayed while rebasing. None if we are not currently rebasing. """ - rebase_head_file = osp.join(self.git_dir, "REBASE_HEAD") + if self.git_dir: + rebase_head_file = osp.join(self.git_dir, "REBASE_HEAD") if not osp.isfile(rebase_head_file): return None return self.commit(open(rebase_head_file, "rt").readline().strip()) |