diff --git a/CHANGES b/CHANGES index 03ba42592..f25b6c013 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,15 @@ $ pip install --user --upgrade --pre libvcs - {meth}`libvcs.cmd.git.Git.reset` - {meth}`libvcs.cmd.git.Git.checkout` - {meth}`libvcs.cmd.git.Git.status` + - {meth}`libvcs.cmd.git.Git.config` via {issue}`360` + +- **Command**: Now support `-C` (which accepts `.git` dirs, see git's manual) in addition to `cwd` + (subprocess-passthrough), {issue}`360` + +### Bug fixes + +- Fix argument input for git commands, e.g. `git config --get color.diff` would not properly + pass-through to subprocess. {issue}`360` ### Internals diff --git a/libvcs/cmd/git.py b/libvcs/cmd/git.py index 07e0aa695..47ef047c3 100644 --- a/libvcs/cmd/git.py +++ b/libvcs/cmd/git.py @@ -44,6 +44,7 @@ def run( man_path: Optional[bool] = None, info_path: Optional[bool] = None, # Normal flags + C: Optional[Union[StrOrBytesPath, list[StrOrBytesPath]]] = None, cwd: Optional[StrOrBytesPath] = None, git_dir: Optional[StrOrBytesPath] = None, work_tree: Optional[StrOrBytesPath] = None, @@ -71,8 +72,10 @@ def run( Parameters ---------- - cwd : :attr:`libvcs.cmd.types.StrOrBytesPath`, optional - ``-C ``, Defaults to :attr:`~.cwd` + cwd : :attr:`libvcs.cmd.types.StrOrBytesPath`, optional, passed to subprocess's + ``cwd`` the command runs from. Defaults to :attr:`~.cwd`. + C : :attr:`libvcs.cmd.types.StrOrBytesPath`, optional + ``-C `` git_dir : :attr:`libvcs.cmd.types.StrOrBytesPath`, optional ``--git-dir `` work_tree : :attr:`libvcs.cmd.types.StrOrBytesPath`, optional @@ -148,18 +151,21 @@ def run( # # Flags # - if cwd is not None: - cli_args.append(f"-C {cwd}") + if C is not None: + if not isinstance(C, list): + C = [C] + C = [str(c) for c in C] + cli_args.extend(["-C", C]) if git_dir is not None: - cli_args.append(f"--git-dir {git_dir}") + cli_args.extend(["--git-dir", str(git_dir)]) if work_tree is not None: - cli_args.append(f"--work-tree {work_tree}") + cli_args.extend(["--work-tree", str(work_tree)]) if namespace is not None: - cli_args.append(f"--namespace {namespace}") + cli_args.extend(["--namespace", namespace]) if super_prefix is not None: - cli_args.append(f"--super-prefix {super_prefix}") + cli_args.extend(["--super-prefix", super_prefix]) if exec_path is not None: - cli_args.append(f"--exec-path {exec_path}") + cli_args.extend(["--exec-path", exec_path]) if bare is True: cli_args.append("--bare") if no_replace_objects is True: @@ -247,25 +253,25 @@ def clone( if (filter := kwargs.pop("filter", None)) is not None: local_flags.append(f"--filter={filter}") if depth is not None: - local_flags.append(f"--depth {depth}") + local_flags.extend(["--depth", depth]) if branch is not None: - local_flags.append(f"--branch {branch}") + local_flags.extend(["--branch", branch]) if origin is not None: - local_flags.append(f"--origin {origin}") + local_flags.extend(["--origin", origin]) if upload_pack is not None: - local_flags.append(f"--upload-pack {upload_pack}") + local_flags.extend(["--upload-pack", upload_pack]) if shallow_since is not None: local_flags.append(f"--shallow-since={shallow_since}") if shallow_exclude is not None: local_flags.append(f"--shallow-exclude={shallow_exclude}") if reference is not None: - local_flags.append(f"--reference {reference}") + local_flags.extend(["--reference", reference]) if reference_if_able is not None: - local_flags.append(f"--reference {reference_if_able}") + local_flags.extend(["--reference", reference_if_able]) if server_option is not None: local_flags.append(f"--server-option={server_option}") if jobs is not None: - local_flags.append(f"--jobs {jobs}") + local_flags.extend(["--jobs", jobs]) if local is True: local_flags.append("--local") if hardlinks is True: @@ -384,13 +390,13 @@ def fetch( if (filter := kwargs.pop("filter", None)) is not None: local_flags.append(f"--filter={filter}") if depth is not None: - local_flags.append(f"--depth {depth}") + local_flags.extend(["--depth", depth]) if branch is not None: - local_flags.append(f"--branch {branch}") + local_flags.extend(["--branch", branch]) if origin is not None: - local_flags.append(f"--origin {origin}") + local_flags.extend(["--origin", origin]) if upload_pack is not None: - local_flags.append(f"--upload-pack {upload_pack}") + local_flags.extend(["--upload-pack", upload_pack]) if shallow_since is not None: local_flags.append(f"--shallow-since={shallow_since}") if shallow_exclude is not None: @@ -398,7 +404,7 @@ def fetch( if server_option is not None: local_flags.append(f"--server-option={server_option}") if jobs is not None: - local_flags.append(f"--jobs {jobs}") + local_flags.extend(["--jobs", jobs]) if keep: local_flags.append("--keep") if force: @@ -550,12 +556,12 @@ def rebase( if branch: required_flags.insert(0, branch) if onto: - local_flags.append(f"--onto {onto}") + local_flags.extend(["--onto", onto]) if context: - local_flags.append(f"--C{context}") + local_flags.extend(["--C", context]) if exec: - local_flags.append(f"--exec {shlex.quote(exec)}") + local_flags.extend(["--exec", shlex.quote(exec)]) if reschedule_failed_exec: local_flags.append("--reschedule-failed-exec") if no_reschedule_failed_exec: @@ -858,13 +864,13 @@ def pull( if (filter := kwargs.pop("filter", None)) is not None: local_flags.append(f"--filter={filter}") if depth is not None: - local_flags.append(f"--depth {depth}") + local_flags.extend(["--depth", depth]) if branch is not None: - local_flags.append(f"--branch {branch}") + local_flags.extend(["--branch", branch]) if origin is not None: - local_flags.append(f"--origin {origin}") + local_flags.extend(["--origin", origin]) if upload_pack is not None: - local_flags.append(f"--upload-pack {upload_pack}") + local_flags.extend(["--upload-pack", upload_pack]) if shallow_since is not None: local_flags.append(f"--shallow-since={shallow_since}") if shallow_exclude is not None: @@ -872,7 +878,7 @@ def pull( if server_option is not None: local_flags.append(f"--server-option={server_option}") if jobs is not None: - local_flags.append(f"--jobs {jobs}") + local_flags.extend(["--jobs", jobs]) if keep: local_flags.append("--keep") if force: @@ -1000,9 +1006,9 @@ def init( if object_format is not None: local_flags.append(f"--object-format={object_format}") if branch is not None: - local_flags.append(f"--branch {branch}") + local_flags.extend(["--branch", branch]) if initial_branch is not None: - local_flags.append(f"--initial-branch {initial_branch}") + local_flags.extend(["--initial-branch", initial_branch]) if shared is True: local_flags.append("--shared") if quiet is True: @@ -1150,7 +1156,7 @@ def reset( local_flags.append("--no-refresh") if refresh is True: local_flags.append("--refresh") - if pathspec_from_file is True: + if pathspec_from_file is not None: local_flags.append(f"--pathspec_from_file {pathspec_from_file}") # HEAD to commit form @@ -1170,7 +1176,7 @@ def reset( local_flags.append("--keep") if commit is True: - local_flags.append(f"{commit}") + local_flags.append(commit) if recurse_submodules: local_flags.append("--recurse-submodules") @@ -1291,19 +1297,19 @@ def checkout( local_flags.append(f"--conflict={conflict}") if commit is True: - local_flags.append(f"{commit}") + local_flags.append(commit) if branch is True: - local_flags.append(f"{branch}") + local_flags.append(branch) if new_branch is True: - local_flags.append(f"{new_branch}") + local_flags.append(new_branch) if start_point is True: - local_flags.append(f"{start_point}") + local_flags.append(start_point) if treeish is True: - local_flags.append(f"{treeish}") + local_flags.append(treeish) if recurse_submodules: local_flags.append("--recurse-submodules") @@ -1384,6 +1390,9 @@ def status( >>> git.status(porcelain='2') '? new_file.txt' + + >>> git.status(C=git_local_clone.dir / '.git', porcelain='2') + '? new_file.txt' """ local_flags: list[str] = [] @@ -1429,7 +1438,10 @@ def status( local_flags.append("--porcelain") if find_renames is True: - local_flags.append(f"--find-renames={find_renames}") + if isinstance(find_renames, str): + local_flags.append(f"--find-renames={find_renames}") + else: + local_flags.append("--find-renames") if pathspec is not None: if not isinstance(pathspec, list): @@ -1441,3 +1453,188 @@ def status( ["status", *local_flags, *(["--", *pathspec] if len(pathspec) else [])], check_returncode=False, ) + + def config( + self, + replace_all: Optional[bool] = None, + get: Optional[str] = None, + get_all: Optional[bool] = None, + get_regexp: Optional[str] = None, + get_urlmatch: Optional[tuple[str, str]] = None, + system: Optional[bool] = None, + local: Optional[bool] = None, + worktree: Optional[bool] = None, + file: Optional[StrOrBytesPath] = None, + blob: Optional[str] = None, + remove_section: Optional[bool] = None, + rename_section: Optional[bool] = None, + unset: Optional[bool] = None, + unset_all: Optional[bool] = None, + _list: Optional[bool] = None, + fixed_value: Optional[bool] = None, + no_type: Optional[bool] = None, + null: Optional[bool] = None, + name_only: Optional[bool] = None, + show_origin: Optional[bool] = None, + show_scope: Optional[bool] = None, + get_color: Optional[Union[str, bool]] = None, + get_colorbool: Optional[Union[str, bool]] = None, + default: Optional[str] = None, + _type: Optional[ + Literal["bool", "int", "bool-or-int", "path", "expiry-date", "color"] + ] = None, + edit: Optional[bool] = None, + no_includes: Optional[bool] = None, + includes: Optional[bool] = None, + add: Optional[bool] = None, + **kwargs, + ): + """Status of working tree. Wraps + `git status `_. + + `git ls-files` has similar params (e.g. `z`) + + Parameters + ---------- + replace_all : Optional[bool] + get : Optional[bool] + get_all : Optional[bool] + get_regexp : Optional[bool] + get_urlmatch : Optional[tuple[str, str]] + system : Optional[bool] + local : Optional[bool] + worktree : Optional[bool] + file : Optional[StrOrBytesPath] + blob : Optional[str] + remove_section : Optional[bool] + rename_section : Optional[bool] + unset : Optional[bool] + unset_all : Optional[bool] + _list : Optional[bool] + fixed_value : Optional[bool] + no_type : Optional[bool] + null : Optional[bool] + name_only : Optional[bool] + show_origin : Optional[bool] + show_scope : Optional[bool] + get_color : Optional[Union[str, bool]] + get_colorbool : Optional[Union[str, bool]] + default : Optional[str] + _type : "bool", "int", "bool-or-int", "path", "expiry-date", "color" + edit : Optional[bool] + no_includes : Optional[bool] + includes : Optional[bool] + add : Optional[bool] + + Examples + -------- + >>> git = Git(dir=git_local_clone.dir) + + >>> git.config() + 'usage: git config ...' + + >>> git.config(_list=True) + '...user.email=...' + + >>> git.config(get='color.diff') + 'auto' + """ + local_flags: list[str] = [] + + if replace_all is True: + local_flags.append("--replace-all") + + if get is not None and isinstance(get, str): + local_flags.extend(["--get", get]) + + if get_regexp is not None and isinstance(get_regexp, str): + local_flags.extend(["--get-regexp", get_regexp]) + + if get_all is not None and isinstance(get_all, str): + local_flags.extend(["--get-all", get_all]) + + if get_urlmatch is not None and isinstance(get_urlmatch, tuple): + local_flags.extend(["--get-urlmatch=", *get_urlmatch]) + + if unset is not None and isinstance(unset, str): + local_flags.extend(["--unset", unset]) + + if unset_all is not None and isinstance(unset_all, str): + local_flags.extend(["--unset-all", unset_all]) + + if _list is True: + local_flags.append("--list") + + if fixed_value is True: + local_flags.append("--fixed-value") + + if no_type is True: + local_flags.append("--no-type") + + if null is True: + local_flags.append("--null") + + if name_only is True: + local_flags.append("--name-only") + + if show_origin is True: + local_flags.append("--show-origin") + + if show_scope is True: + local_flags.append("--show-scope") + + if edit is True: + local_flags.append("--edit") + + if system is True: + local_flags.append("--system") + + if local is True: + local_flags.append("--local") + + if worktree is True: + local_flags.append("--worktree") + + if remove_section is True: + local_flags.append("--remove-section") + + if rename_section is True: + local_flags.append("--rename-section") + + if _type is not None and isinstance(_type, str): + local_flags.extend(["--type", _type]) + + if blob is not None and isinstance(blob, str): + local_flags.extend(["--blob", blob]) + + if file is not None: + local_flags.extend(["--file", str(file)]) + + if default is True: + local_flags.append("--default") + + if includes is True: + local_flags.append("--includes") + + if no_includes is True: + local_flags.append("--no-includes") + + if add is True: + local_flags.append("--add") + + if get_colorbool is not None: + if isinstance(get_colorbool, str): + local_flags.extend(["--get-colorbool", get_colorbool]) + else: + local_flags.append("--get-colorbool") + + if get_color is not None: + if isinstance(get_color, str): + local_flags.extend(["--get-color", get_color]) + else: + local_flags.append("--get-color") + + return self.run( + ["config", *local_flags], + check_returncode=False, + )