Adding upstream version 0.16.7.2.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
50453fb690
commit
e4b3680961
15 changed files with 737 additions and 81 deletions
9
.github/workflows/nos.yml
vendored
9
.github/workflows/nos.yml
vendored
|
@ -4,13 +4,14 @@ on: [push, pull_request]
|
|||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-latest, windows-latest]
|
||||
python-version: [3.6, 3.7, 3.8, 3.9, "3.10", "3.11"]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
|
|
9
Makefile
9
Makefile
|
@ -1,4 +1,4 @@
|
|||
.PHONY: dist test install clean twine
|
||||
.PHONY: dist test install clean twine auto-completion
|
||||
|
||||
install:
|
||||
pip3 install -e .
|
||||
|
@ -10,3 +10,10 @@ twine:
|
|||
twine upload dist/*
|
||||
clean:
|
||||
git clean -fdx
|
||||
auto-completion:
|
||||
@ mkdir -p auto-completion/bash
|
||||
@ mkdir -p auto-completion/zsh
|
||||
@ mkdir -p auto-completion/fish
|
||||
register-python-argcomplete gita -s bash > auto-completion/bash/.gita-completion.bash
|
||||
register-python-argcomplete gita -s zsh > auto-completion/zsh/_gita
|
||||
register-python-argcomplete gita -s fish > auto-completion/fish/gita.fish
|
||||
|
|
55
README.md
55
README.md
|
@ -41,13 +41,13 @@ I also made a youtube video to demonstrate the common usages
|
|||
|
||||
The branch color distinguishes 5 situations between local and remote branches:
|
||||
|
||||
color | meaning
|
||||
---|---
|
||||
white| local has no remote
|
||||
green| local is the same as remote
|
||||
red| local has diverged from remote
|
||||
purple| local is ahead of remote (good for push)
|
||||
yellow| local is behind remote (good for merge)
|
||||
| color | meaning |
|
||||
| ------ | ---------------------------------------- |
|
||||
| white | local has no remote |
|
||||
| green | local is the same as remote |
|
||||
| red | local has diverged from remote |
|
||||
| purple | local is ahead of remote (good for push) |
|
||||
| yellow | local is behind remote (good for merge) |
|
||||
|
||||
The choice of purple for ahead and yellow for behind is motivated by
|
||||
[blueshift](https://en.wikipedia.org/wiki/Blueshift) and [redshift](https://en.wikipedia.org/wiki/Redshift),
|
||||
|
@ -57,11 +57,12 @@ See the [customization section](#custom).
|
|||
|
||||
The additional status symbols denote
|
||||
|
||||
symbol | meaning
|
||||
---|---
|
||||
`+`| staged changes
|
||||
`*`| unstaged changes
|
||||
`?`| untracked files/folders
|
||||
| symbol | meaning |
|
||||
| ------ | ----------------------- |
|
||||
| `+` | staged changes |
|
||||
| `*` | unstaged changes |
|
||||
| `?` | untracked files/folders |
|
||||
| `$` | stashed contents |
|
||||
|
||||
The bookkeeping sub-commands are
|
||||
|
||||
|
@ -158,11 +159,21 @@ See [this stackoverflow post](https://stackoverflow.com/questions/51680709/color
|
|||
|
||||
## Auto-completion
|
||||
|
||||
Download
|
||||
[.gita-completion.bash](https://github.com/nosarthur/gita/blob/master/.gita-completion.bash)
|
||||
or
|
||||
[.gita-completion.zsh](https://github.com/nosarthur/gita/blob/master/.gita-completion.zsh)
|
||||
and source it in shell.
|
||||
You can download the generated auto-completion file in the following locations for your specific shell. Alternatively, if you have installed `argcomplete` on your system, you can also directly run `eval "$(register-python-argcomplete gita -s SHELL)"` (e.g. `SHELL` as `bash`/`zsh`) in your dotfile.
|
||||
|
||||
### Bash
|
||||
Download [.gita-completion.bash](https://github.com/nosarthur/gita/blob/master/.gita-completion.bash) and source it in shell.
|
||||
|
||||
### Zsh
|
||||
There are 2 options :
|
||||
- [.gita-completion.zsh](https://github.com/nosarthur/gita/blob/master/contrib.completion/zsh/.gita-completion.zsh). Use the help of gita command to display options. It uses the bash completion system for zsh.
|
||||
Add `autoload -U +X bashcompinit && bashcompinit` in .zshrc and source the zsh file
|
||||
- [_gita](https://github.com/nosarthur/gita/blob/master/contrib.completion/zsh/_gita_).
|
||||
Completion more Zsh style. Copy it in a folder and add this folder path in `FPATH` variable. This completion file doesn't take account to command from cmds.json
|
||||
|
||||
### Fish
|
||||
Download [gita.fish](https://github.com/nosarthur/gita/tree/master/auto-completion/fish/gita.fish) and place it in `~/.config/fish/completions/`
|
||||
|
||||
|
||||
## <a name='superman'></a> Superman mode
|
||||
|
||||
|
@ -380,10 +391,10 @@ their results agree.
|
|||
|
||||
## Tips
|
||||
|
||||
effect | shell command
|
||||
---|---
|
||||
enter `<repo>` directory|`` cd `gita ls <repo>` ``
|
||||
delete repos in `<group>` | `gita group ll <group> \| xargs gita rm`
|
||||
| effect | shell command |
|
||||
| ------------------------- | ---------------------------------------- |
|
||||
| enter `<repo>` directory | `` cd `gita ls <repo>` `` |
|
||||
| delete repos in `<group>` | `gita group ll <group> \| xargs gita rm` |
|
||||
|
||||
## Contributing
|
||||
|
||||
|
@ -411,4 +422,4 @@ I haven't tried them but I heard good things about them.
|
|||
|
||||
- [myrepos](https://myrepos.branchable.com/)
|
||||
- [repo](https://source.android.com/setup/develop/repo)
|
||||
|
||||
- [mu-repo](https://github.com/fabioz/mu-repo)
|
||||
|
|
17
auto-completion/fish/gita.fish
Normal file
17
auto-completion/fish/gita.fish
Normal file
|
@ -0,0 +1,17 @@
|
|||
|
||||
function __fish_gita_complete
|
||||
set -x _ARGCOMPLETE 1
|
||||
set -x _ARGCOMPLETE_DFS \t
|
||||
set -x _ARGCOMPLETE_IFS \n
|
||||
set -x _ARGCOMPLETE_SUPPRESS_SPACE 1
|
||||
set -x _ARGCOMPLETE_SHELL fish
|
||||
set -x COMP_LINE (commandline -p)
|
||||
set -x COMP_POINT (string length (commandline -cp))
|
||||
set -x COMP_TYPE
|
||||
if set -q _ARC_DEBUG
|
||||
gita 8>&1 9>&2 1>&9 2>&1
|
||||
else
|
||||
gita 8>&1 9>&2 1>/dev/null 2>&1
|
||||
end
|
||||
end
|
||||
complete --command gita -f -a '(__fish_gita_complete)'
|
473
auto-completion/zsh/_gita
Normal file
473
auto-completion/zsh/_gita
Normal file
|
@ -0,0 +1,473 @@
|
|||
#compdef gita
|
||||
|
||||
__gita_get_repos() {
|
||||
local -a repositories
|
||||
repositories=($(_call_program commands gita ls))
|
||||
_describe -t repositories 'gita repositories' repositories
|
||||
}
|
||||
|
||||
__gita_get_context() {
|
||||
local -a context
|
||||
context=(
|
||||
"auto"
|
||||
"none"
|
||||
)
|
||||
_describe -t context 'gita context' context
|
||||
__gita_get_groups
|
||||
}
|
||||
|
||||
__gita_get_infos() {
|
||||
local -a all_infos infos_in_used infos_unused
|
||||
all_infos=($(_call_program commands gita info ll | cut -d ":" -f2))
|
||||
infos_in_used=($(echo ${all_infos[1]} | tr ',' ' '))
|
||||
infos_unused=($(echo ${all_infos[2]} | tr ',' ' '))
|
||||
_describe -t infos_used 'gita infos in used' infos_in_used
|
||||
_describe -t infos_unused 'gita infos unused' infos_unused
|
||||
}
|
||||
|
||||
__gita_get_groups() {
|
||||
local -a groups
|
||||
|
||||
groups=($(_call_program commands gita group ls))
|
||||
_describe -t groups 'gita groups' groups
|
||||
}
|
||||
|
||||
__gita_commands() {
|
||||
local -a commands
|
||||
commands=(
|
||||
'add:Add repo(s)'
|
||||
'rm:remove repo(s)'
|
||||
'freeze:Print all repo information'
|
||||
'clone:Clone repos'
|
||||
'rename:Rename a repo'
|
||||
'flags:Git flags configuration'
|
||||
'color:Color configuration'
|
||||
'info:Information setting'
|
||||
'll:Display summary of all repos'
|
||||
'context:Set context'
|
||||
'ls:Show repo(s) or repo path'
|
||||
'group:Group repos'
|
||||
'super:Run any git command/alias'
|
||||
'shell:Run any shell command'
|
||||
'clear:Removes all groups and repositories'
|
||||
)
|
||||
_describe -t commands 'gita sub-commands' commands
|
||||
}
|
||||
|
||||
# FUNCTION: _gita_add [[[
|
||||
_gita_add() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' \
|
||||
'(-n --dry-run)'{-n,--dry-run}'[dry run]' \
|
||||
'(-g --group)'{-g=,--group=}'[add repo(s) to the specified group]:Gita groups:__gita_get_groups' \
|
||||
'(-s --skip-modules)'{-s,--skip-modules}'[skip submodule repo(s)]' \
|
||||
'(-r --recursive)'{-r,--recursive}'[recursively add repo(s) in the given path(s)]' \
|
||||
'(-a --auto-group)'{-a,--auto-group}'[recursively add repo(s) in the given path(s) and create hierarchical groups based on folder structure]' \
|
||||
'(-b --bare)'{-b,--bare}'[add bare repo(s)]' \
|
||||
"(-h --help -)*:Directories:_directories"
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_rm [[[
|
||||
_gita_rm() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' \
|
||||
"(-h --help -)*:gita repositories:__gita_get_repos" &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_freeze [[[
|
||||
_gita_freeze() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' \
|
||||
'(-g --group)'{-g=,--group=}'[freeze repos in the specified group]:Gita groups:__gita_get_groups' &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_clone [[[
|
||||
_gita_clone() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' \
|
||||
'(-C --directory)'{-C=,--directory=}'[ Change to DIRECTORY before doing anything]:Directories:_directories' \
|
||||
'(-p --preserve-path)'{-p,--preserve-path}'[clone repo(s) in their original paths]' \
|
||||
'(-n --dry-run)'{-n,--dry-run}'[dry run]' \
|
||||
'(-g --group)'{-g=,--group=}'[If set, add repo to the specified group after cloning, otherwise add to gita without group]:Gita groups:__gita_get_groups' \
|
||||
'(-f --from-file)'{-f=,--from-file=}'[ If set, clone repos in a config file rendered from `gita freeze`]:File:_path_files' &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_rename [[[
|
||||
_gita_rename() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' \
|
||||
"(-h --help -):Gita repositories:__gita_get_repos" &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_flags_commands[[[
|
||||
_gita_flags_commands() {
|
||||
|
||||
local -a subcommands
|
||||
subcommands=(
|
||||
'll:Display repos with custom flags'
|
||||
'set:Set flags for repo'
|
||||
)
|
||||
_describe -t subcommands 'gita flag sub-commands' subcommands
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_flags_ll [[[
|
||||
_gita_flags_ll() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_flags_set [[[
|
||||
_gita_flags_set() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' \
|
||||
"(-h --help -):Gita repositories:__gita_get_repos" &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_flags[[[
|
||||
_gita_flags() {
|
||||
local curcontext="$curcontext" state state_descr line expl
|
||||
local tmp ret=1
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]'
|
||||
|
||||
_arguments -C \
|
||||
"1: :->cmds" \
|
||||
"*::arg:->args"
|
||||
case "$state" in
|
||||
cmds)
|
||||
_gita_flags_commands && return 0
|
||||
;;
|
||||
args)
|
||||
local cmd="${line[1]}"
|
||||
curcontext="${curcontext%:*}-${cmd}:${curcontext##*:}"
|
||||
local completion_func="_gita_flags_${cmd//-/_}"
|
||||
_call_function ret "${completion_func}" && return ret
|
||||
_message "a completion function is not defined for command or alias: ${cmd}"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_color_commands[[[
|
||||
_gita_color_commands() {
|
||||
|
||||
local -a subcommands
|
||||
subcommands=(
|
||||
'll:Display available colors and the current branch coloring in the ll sub-command'
|
||||
'set:Set color for local/remote situation'
|
||||
'reset:Reset color scheme'
|
||||
)
|
||||
_describe -t subcommands 'gita color sub-commands' subcommands
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_color_ll [[[
|
||||
_gita_color_ll() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_color_set [[[
|
||||
_gita_color_set() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_color_reset [[[
|
||||
_gita_color_reset() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_color[[[
|
||||
_gita_color() {
|
||||
local curcontext="$curcontext" state state_descr line expl
|
||||
local tmp ret=1
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]'
|
||||
|
||||
_arguments -C \
|
||||
"1: :->cmds" \
|
||||
"*::arg:->args"
|
||||
case "$state" in
|
||||
cmds)
|
||||
_gita_color_commands && return 0
|
||||
;;
|
||||
args)
|
||||
local cmd="${line[1]}"
|
||||
curcontext="${curcontext%:*}-${cmd}:${curcontext##*:}"
|
||||
local completion_func="_gita_color_${cmd//-/_}"
|
||||
_call_function ret "${completion_func}" && return ret
|
||||
_message "a completion function is not defined for command or alias: ${cmd}"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_info_commands[[[
|
||||
_gita_info_commands() {
|
||||
|
||||
local -a subcommands
|
||||
subcommands=(
|
||||
'll:Show used and unused information items of the ll sub-command'
|
||||
'add:Enable information item'
|
||||
'rm:Disable information item'
|
||||
)
|
||||
_describe -t subcommands 'gita info sub-commands' subcommands
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_info_ll [[[
|
||||
_gita_info_ll() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_info_add [[[
|
||||
_gita_info_add() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' \
|
||||
"(-h --help -):Gita infos:__gita_get_infos" &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_info_rm [[[
|
||||
_gita_info_rm() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' \
|
||||
"(-h --help -):Gita infos:__gita_get_infos" &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_info[[[
|
||||
_gita_info() {
|
||||
local curcontext="$curcontext" state state_descr line expl
|
||||
local tmp ret=1
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]'
|
||||
|
||||
_arguments -C \
|
||||
"1: :->cmds" \
|
||||
"*::arg:->args"
|
||||
case "$state" in
|
||||
cmds)
|
||||
_gita_info_commands && return 0
|
||||
;;
|
||||
args)
|
||||
local cmd="${line[1]}"
|
||||
curcontext="${curcontext%:*}-${cmd}:${curcontext##*:}"
|
||||
local completion_func="_gita_info_${cmd//-/_}"
|
||||
_call_function ret "${completion_func}" && return ret
|
||||
_message "a completion function is not defined for command or alias: ${cmd}"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_ll [[[
|
||||
_gita_ll() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' \
|
||||
'(-C --no-colors)'{-C,--no-colors}'[Disable coloring on the branch names]' \
|
||||
'(-g)'-g'[Show repo summaries by group]' \
|
||||
"(-h --help -):Groups name:__gita_get_groups" &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_context [[[
|
||||
_gita_context() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' \
|
||||
"(-h --help -):Gita context:__gita_get_context" &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_ls [[[
|
||||
_gita_ls() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' \
|
||||
"(-h --help -):Gita repositories:__gita_get_repos" &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_group_commands[[[
|
||||
_gita_group_commands() {
|
||||
|
||||
local -a subcommands
|
||||
subcommands=(
|
||||
'll:List all groups with repos.'
|
||||
'ls:List all group names'
|
||||
'add:Add repo(s) to a group'
|
||||
'rmrepo:remove repo(s) from a group'
|
||||
'rename:Change group name'
|
||||
'rm:Remove group(s)'
|
||||
)
|
||||
_describe -t subcommands 'gita group sub-commands' subcommands
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_group_ll [[[
|
||||
_gita_group_ll() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' \
|
||||
"(-h --help -):Groups name:__gita_get_groups" &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_group_ls [[[
|
||||
_gita_group_ls() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_group_add [[[
|
||||
_gita_group_add() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' \
|
||||
'(-n --name)'{-n=,--name=}'[group-name,]:Groups name:__gita_get_groups' \
|
||||
'(-p --path)'{-p=,--path=}'[group-path]:Group path:_directories' \
|
||||
"(-h --help -)*:Gita repositories:__gita_get_repos" &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_group_rmrepo [[[
|
||||
_gita_group_rmrepo() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' \
|
||||
'(-n --name)'{-n=,--name=}'[group-name,]:Groups name:__gita_get_groups' \
|
||||
"(-h --help -)*:Gita repositories:__gita_get_repos" &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_group_rename [[[
|
||||
_gita_group_rename() {
|
||||
_arguments -A \
|
||||
'(-h --help -)'{-h,--help}'[show this help message and exit]' &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_group_rm [[[
|
||||
_gita_group_rm() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' \
|
||||
"(-h --help -)*:Groups name:__gita_get_groups" &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_group[[[
|
||||
_gita_group() {
|
||||
local curcontext="$curcontext" state state_descr line expl
|
||||
local tmp ret=1
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]'
|
||||
|
||||
_arguments -C \
|
||||
"1: :->cmds" \
|
||||
"*::arg:->args"
|
||||
case "$state" in
|
||||
cmds)
|
||||
_gita_group_commands && return 0
|
||||
;;
|
||||
args)
|
||||
local cmd="${line[1]}"
|
||||
curcontext="${curcontext%:*}-${cmd}:${curcontext##*:}"
|
||||
local completion_func="_gita_group_${cmd//-/_}"
|
||||
_call_function ret "${completion_func}" && return ret
|
||||
_message "a completion function is not defined for command or alias: ${cmd}"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_super [[[
|
||||
_gita_super() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' \
|
||||
'(-q --quote-mode)'{-q,--quote-mode}'[use quote mode]' &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_shell [[[
|
||||
_gita_shell() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' \
|
||||
'(-q --quote-mode)'{-q,--quote-mode}'[use quote mode]' &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita_clear [[[
|
||||
_gita_clear() {
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' &&
|
||||
ret=0
|
||||
}
|
||||
#]]]
|
||||
|
||||
# FUNCTION: _gita [[[
|
||||
_gita() {
|
||||
local curcontext="$curcontext" state state_descr line expl
|
||||
local tmp ret=1
|
||||
_arguments -A \
|
||||
'(-h --help)'{-h,--help}'[show this help message and exit]' \
|
||||
'(-v --version)'{-v,--version}'[show program'\''s version number and exit]'
|
||||
|
||||
_arguments -C \
|
||||
"1: :->cmds" \
|
||||
"*::arg:->args"
|
||||
case "$state" in
|
||||
cmds)
|
||||
__gita_commands && return 0
|
||||
;;
|
||||
args)
|
||||
local cmd="${line[1]}"
|
||||
curcontext="${curcontext%:*}-${cmd}:${curcontext##*:}"
|
||||
local completion_func="_gita_${cmd//-/_}"
|
||||
_call_function ret "${completion_func}" && return ret
|
||||
_message "a completion function is not defined for command or alias: ${cmd}"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
} # ]]]
|
||||
|
||||
_gita "$@"
|
|
@ -1,3 +1,18 @@
|
|||
import pkg_resources
|
||||
import sys
|
||||
|
||||
__version__ = pkg_resources.get_distribution("gita").version
|
||||
|
||||
def get_version() -> str:
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
try:
|
||||
from importlib.metadata import version
|
||||
except ImportError:
|
||||
print("cannot determine version", sys.version_info)
|
||||
else:
|
||||
return version("gita")
|
||||
else:
|
||||
return pkg_resources.get_distribution("gita").version
|
||||
|
||||
|
||||
__version__ = get_version()
|
||||
|
|
|
@ -18,14 +18,15 @@ import os
|
|||
import sys
|
||||
import csv
|
||||
import argparse
|
||||
import argcomplete
|
||||
import subprocess
|
||||
from functools import partial
|
||||
import pkg_resources
|
||||
from itertools import chain
|
||||
from pathlib import Path
|
||||
import glob
|
||||
from typing import Dict, Optional
|
||||
|
||||
from . import utils, info, common
|
||||
from . import utils, info, common, io, get_version
|
||||
|
||||
|
||||
def _group_name(name: str, exclude_old_names=True) -> str:
|
||||
|
@ -146,13 +147,28 @@ def f_info(args: argparse.Namespace):
|
|||
with open(csv_config, "w", newline="") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(to_display)
|
||||
elif cmd == "set-length":
|
||||
csv_config = common.get_config_fname("layout.csv")
|
||||
print(f"Settings are in {csv_config}")
|
||||
defaults = {
|
||||
"branch": 19,
|
||||
"symbols": 5,
|
||||
"branch_name": 27,
|
||||
"commit_msg": 0,
|
||||
"commit_time": 0, # 0 means no limit
|
||||
"path": 30,
|
||||
}
|
||||
with open(csv_config, "w", newline="") as f:
|
||||
writer = csv.DictWriter(f, fieldnames=defaults)
|
||||
writer.writeheader()
|
||||
writer.writerow(defaults)
|
||||
|
||||
|
||||
def f_clone(args: argparse.Namespace):
|
||||
|
||||
if args.dry_run:
|
||||
if args.from_file:
|
||||
for url, repo_name, abs_path in utils.parse_clone_config(args.clonee):
|
||||
for url, repo_name, abs_path in io.parse_clone_config(args.clonee):
|
||||
print(f"git clone {url} {abs_path}")
|
||||
else:
|
||||
print(f"git clone {args.clonee}")
|
||||
|
@ -172,28 +188,35 @@ def f_clone(args: argparse.Namespace):
|
|||
f_add(args)
|
||||
return
|
||||
|
||||
# TODO: add repos to group too
|
||||
repos, groups = io.parse_clone_config(args.clonee)
|
||||
if args.preserve_path:
|
||||
utils.exec_async_tasks(
|
||||
utils.run_async(repo_name, path, ["git", "clone", url, abs_path])
|
||||
for url, repo_name, abs_path in utils.parse_clone_config(args.clonee)
|
||||
utils.run_async(repo_name, path, ["git", "clone", r["url"], r["path"]])
|
||||
for repo_name, r in repos.items()
|
||||
)
|
||||
else:
|
||||
utils.exec_async_tasks(
|
||||
utils.run_async(repo_name, path, ["git", "clone", url])
|
||||
for url, repo_name, _ in utils.parse_clone_config(args.clonee)
|
||||
utils.run_async(repo_name, path, ["git", "clone", r["url"]])
|
||||
for repo_name, r in repos.items()
|
||||
)
|
||||
|
||||
|
||||
def f_freeze(args):
|
||||
repos = utils.get_repos()
|
||||
"""
|
||||
print repo and group information for future cloning
|
||||
"""
|
||||
ctx = utils.get_context()
|
||||
if args.group is None and ctx:
|
||||
args.group = ctx.stem
|
||||
repos = utils.get_repos()
|
||||
group_name = args.group
|
||||
group_repos = None
|
||||
if args.group: # only display repos in this group
|
||||
group_repos = utils.get_groups()[args.group]["repos"]
|
||||
if group_name: # only display repos in this group
|
||||
group_repos = utils.get_groups()[group_name]["repos"]
|
||||
repos = {k: repos[k] for k in group_repos if k in repos}
|
||||
seen = {""}
|
||||
# print(repos)
|
||||
for name, prop in repos.items():
|
||||
path = prop["path"]
|
||||
url = ""
|
||||
|
@ -211,7 +234,16 @@ def f_freeze(args):
|
|||
url = parts[1]
|
||||
if url not in seen:
|
||||
seen.add(url)
|
||||
print(f"{url},{name},{path}")
|
||||
# TODO: add another field to distinguish regular repo or worktree or submodule
|
||||
print(f"{url},{name},{path},")
|
||||
# group information: these lines don't have URL
|
||||
if group_name:
|
||||
group_path = utils.get_groups()[group_name]["path"]
|
||||
print(f",{group_name},{group_path},{'|'.join(group_repos)}")
|
||||
else: # show all groups
|
||||
for gname, g in utils.get_groups().items():
|
||||
group_repos = "|".join(g["repos"])
|
||||
print(f",{gname},{g['path']},{group_repos}")
|
||||
|
||||
|
||||
def f_ll(args: argparse.Namespace):
|
||||
|
@ -435,8 +467,9 @@ def main(argv=None):
|
|||
title="sub-commands", help="additional help with sub-command -h"
|
||||
)
|
||||
|
||||
version = pkg_resources.require("gita")[0].version
|
||||
p.add_argument("-v", "--version", action="version", version=f"%(prog)s {version}")
|
||||
p.add_argument(
|
||||
"-v", "--version", action="version", version=f"%(prog)s {get_version()}"
|
||||
)
|
||||
|
||||
# bookkeeping sub-commands
|
||||
p_add = subparsers.add_parser("add", description="add repo(s)", help="add repo(s)")
|
||||
|
@ -598,11 +631,17 @@ def main(argv=None):
|
|||
info_cmds.add_parser("rm", description="Disable information item.").add_argument(
|
||||
"info_item", choices=info.ALL_INFO_ITEMS, help="information item to delete"
|
||||
)
|
||||
info_cmds.add_parser(
|
||||
"set-length",
|
||||
description="Set default column widths for information items. "
|
||||
"The settings are in layout.csv",
|
||||
)
|
||||
|
||||
ll_doc = f""" status symbols:
|
||||
+: staged changes
|
||||
*: unstaged changes
|
||||
_: untracked files/folders
|
||||
?: untracked files/folders
|
||||
$: stashed changes
|
||||
|
||||
branch colors:
|
||||
{info.Color.white}white{info.Color.end}: local has no remote
|
||||
|
@ -780,17 +819,18 @@ def main(argv=None):
|
|||
cmds = utils.get_cmds_from_files()
|
||||
for name, data in cmds.items():
|
||||
help = data.get("help")
|
||||
repo_help = help
|
||||
cmd = data["cmd"]
|
||||
if data.get("allow_all"):
|
||||
choices = utils.get_choices()
|
||||
nargs = "*"
|
||||
help += " for all repos or"
|
||||
repo_help += " for all repos or"
|
||||
else:
|
||||
choices = utils.get_repos().keys() | utils.get_groups().keys()
|
||||
nargs = "+"
|
||||
help += " for the chosen repo(s) or group(s)"
|
||||
sp = subparsers.add_parser(name, description=help)
|
||||
sp.add_argument("repo", nargs=nargs, choices=choices, help=help)
|
||||
repo_help += " for the chosen repo(s) or group(s)"
|
||||
sp = subparsers.add_parser(name, description=help, help=help)
|
||||
sp.add_argument("repo", nargs=nargs, choices=choices, help=repo_help)
|
||||
is_shell = bool(data.get("shell"))
|
||||
sp.add_argument(
|
||||
"-s",
|
||||
|
@ -805,6 +845,7 @@ def main(argv=None):
|
|||
cmd = cmd.split()
|
||||
sp.set_defaults(func=f_git_cmd, cmd=cmd)
|
||||
|
||||
argcomplete.autocomplete(p)
|
||||
args = p.parse_args(argv)
|
||||
|
||||
args.async_blacklist = {
|
||||
|
|
|
@ -22,8 +22,13 @@
|
|||
"cmd": "git log -1 HEAD",
|
||||
"help": "show log information of HEAD"
|
||||
},
|
||||
"log":
|
||||
{"cmd": "git log",
|
||||
"lo":{
|
||||
"cmd": "git log --oneline -7",
|
||||
"allow_all": true,
|
||||
"help": "show one-line log for the latest 7 commits"
|
||||
},
|
||||
"log":{
|
||||
"cmd": "git log",
|
||||
"disable_async": true,
|
||||
"help": "show logs"
|
||||
},
|
||||
|
@ -77,10 +82,12 @@
|
|||
},
|
||||
"stat":{
|
||||
"cmd": "git diff --stat",
|
||||
"allow_all": true,
|
||||
"help": "show edit statistics"
|
||||
},
|
||||
"st":{
|
||||
"cmd": "git status",
|
||||
"allow_all": true,
|
||||
"help": "show status"
|
||||
},
|
||||
"tag":{
|
||||
|
|
78
gita/info.py
78
gita/info.py
|
@ -9,6 +9,40 @@ from typing import Tuple, List, Callable, Dict
|
|||
from . import common
|
||||
|
||||
|
||||
class Truncate:
|
||||
"""
|
||||
Reads in user layout.csv file and uses the values there
|
||||
to truncate the string passed in. If the file doesn't
|
||||
exist or the requested field doesn't exist then don't
|
||||
truncate
|
||||
"""
|
||||
|
||||
widths = {}
|
||||
|
||||
def __init__(self):
|
||||
csv_config = Path(common.get_config_fname("layout.csv"))
|
||||
if csv_config.is_file():
|
||||
with open(csv_config, "r") as f:
|
||||
reader = csv.DictReader(f)
|
||||
self.widths = next(reader)
|
||||
|
||||
# Ensure the Dict type is Dict[str, int] to reduce casting elsewhere
|
||||
for e, width in self.widths.items():
|
||||
self.widths[e] = int(width)
|
||||
|
||||
def truncate(self, field: str, message: str):
|
||||
# 0 means no width limit applied
|
||||
if not self.widths.get(field):
|
||||
return message
|
||||
|
||||
length = 3 if self.widths[field] < 3 else self.widths[field]
|
||||
return (
|
||||
message[: length - 3] + "..."
|
||||
if len(message) > length
|
||||
else message.ljust(length)
|
||||
)
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
"""
|
||||
Terminal color
|
||||
|
@ -113,8 +147,8 @@ def get_info_items() -> List[str]:
|
|||
return display_items
|
||||
|
||||
|
||||
def get_path(prop: Dict[str, str]) -> str:
|
||||
return f'{Color.cyan}{prop["path"]}{Color.end}'
|
||||
def get_path(prop: Dict[str, str], truncator: Truncate) -> str:
|
||||
return f'{Color.cyan}{truncator.truncate("path", prop["path"])}{Color.end}'
|
||||
|
||||
|
||||
# TODO: do we need to add the flags here too?
|
||||
|
@ -164,7 +198,21 @@ def has_untracked(flags: List[str], path) -> bool:
|
|||
return bool(result.stdout)
|
||||
|
||||
|
||||
def get_commit_msg(prop: Dict[str, str]) -> str:
|
||||
def has_stashed(flags: List[str], path) -> bool:
|
||||
"""
|
||||
Return True if stashed content exists
|
||||
"""
|
||||
# FIXME: this doesn't work for repos like worktrees, bare, etc
|
||||
p = Path(path) / ".git" / "logs" / "refs" / "stash"
|
||||
got = False
|
||||
try:
|
||||
got = p.is_file()
|
||||
except Exception:
|
||||
pass
|
||||
return got
|
||||
|
||||
|
||||
def get_commit_msg(prop: Dict[str, str], truncator: Truncate) -> str:
|
||||
"""
|
||||
Return the last commit message.
|
||||
"""
|
||||
|
@ -177,10 +225,10 @@ def get_commit_msg(prop: Dict[str, str]) -> str:
|
|||
universal_newlines=True,
|
||||
cwd=prop["path"],
|
||||
)
|
||||
return result.stdout.strip()
|
||||
return truncator.truncate("commit_msg", result.stdout.strip())
|
||||
|
||||
|
||||
def get_commit_time(prop: Dict[str, str]) -> str:
|
||||
def get_commit_time(prop: Dict[str, str], truncator: Truncate) -> str:
|
||||
"""
|
||||
Return the last commit time in parenthesis.
|
||||
"""
|
||||
|
@ -192,13 +240,14 @@ def get_commit_time(prop: Dict[str, str]) -> str:
|
|||
universal_newlines=True,
|
||||
cwd=prop["path"],
|
||||
)
|
||||
return f"({result.stdout.strip()})"
|
||||
return truncator.truncate("commit_time", f"({result.stdout.strip()})")
|
||||
|
||||
|
||||
default_symbols = {
|
||||
"dirty": "*",
|
||||
"staged": "+",
|
||||
"untracked": "?",
|
||||
"stashed": "$",
|
||||
"local_ahead": "↑",
|
||||
"remote_ahead": "↓",
|
||||
"diverged": "⇕",
|
||||
|
@ -223,11 +272,11 @@ def get_symbols() -> Dict[str, str]:
|
|||
return default_symbols
|
||||
|
||||
|
||||
def get_repo_status(prop: Dict[str, str], no_colors=False) -> str:
|
||||
branch = get_head(prop["path"])
|
||||
dirty, staged, untracked, situ = _get_repo_status(prop)
|
||||
def get_repo_status(prop: Dict[str, str], truncator: Truncate, no_colors=False) -> str:
|
||||
branch = truncator.truncate("branch", get_head(prop["path"]))
|
||||
dirty, staged, untracked, stashed, situ = _get_repo_status(prop)
|
||||
symbols = get_symbols()
|
||||
info = f"{branch:<10} [{symbols[dirty]+symbols[staged]+symbols[untracked]+symbols[situ]}]"
|
||||
info = f"{branch:<10} {truncator.truncate('symbols', f'[{symbols[dirty]}{symbols[staged]}{symbols[stashed]}{symbols[untracked]}{symbols[situ]}]')}"
|
||||
|
||||
if no_colors:
|
||||
return f"{info:<18}"
|
||||
|
@ -236,11 +285,11 @@ def get_repo_status(prop: Dict[str, str], no_colors=False) -> str:
|
|||
return f"{color}{info:<18}{Color.end}"
|
||||
|
||||
|
||||
def get_repo_branch(prop: Dict[str, str]) -> str:
|
||||
return get_head(prop["path"])
|
||||
def get_repo_branch(prop: Dict[str, str], truncator: Truncate) -> str:
|
||||
return truncator.truncate("branch_name", get_head(prop["path"]))
|
||||
|
||||
|
||||
def _get_repo_status(prop: Dict[str, str]) -> Tuple[str, str, str, str]:
|
||||
def _get_repo_status(prop: Dict[str, str]) -> Tuple[str, str, str, str, str]:
|
||||
"""
|
||||
Return the status of one repo
|
||||
"""
|
||||
|
@ -249,6 +298,7 @@ def _get_repo_status(prop: Dict[str, str]) -> Tuple[str, str, str, str]:
|
|||
dirty = "dirty" if run_quiet_diff(flags, [], path) else ""
|
||||
staged = "staged" if run_quiet_diff(flags, ["--cached"], path) else ""
|
||||
untracked = "untracked" if has_untracked(flags, path) else ""
|
||||
stashed = "stashed" if has_stashed(flags, path) else ""
|
||||
|
||||
diff_returncode = run_quiet_diff(flags, ["@{u}", "@{0}"], path)
|
||||
if diff_returncode == 128:
|
||||
|
@ -263,7 +313,7 @@ def _get_repo_status(prop: Dict[str, str]) -> Tuple[str, str, str, str]:
|
|||
situ = "diverged" if diverged else "remote_ahead"
|
||||
else: # local is ahead of remote
|
||||
situ = "local_ahead"
|
||||
return dirty, staged, untracked, situ
|
||||
return dirty, staged, untracked, stashed, situ
|
||||
|
||||
|
||||
ALL_INFO_ITEMS = {
|
||||
|
|
33
gita/io.py
Normal file
33
gita/io.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
import os
|
||||
import csv
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
def parse_clone_config(fname: str) -> Tuple:
|
||||
"""
|
||||
Return the repo information (url, name, path, type) and group information
|
||||
(, name, path, repos) saved in `fname`.
|
||||
"""
|
||||
repos = {}
|
||||
groups = {}
|
||||
if os.path.isfile(fname) and os.stat(fname).st_size > 0:
|
||||
with open(fname) as f:
|
||||
rows = csv.DictReader(
|
||||
f, ["url", "name", "path", "type", "flags"], restval=""
|
||||
) # it's actually a reader
|
||||
for r in rows:
|
||||
if r["url"]:
|
||||
repos[r["name"]] = {
|
||||
"path": r["path"],
|
||||
"type": r["type"],
|
||||
"flags": r["flags"].split(),
|
||||
"url": r["url"],
|
||||
}
|
||||
else:
|
||||
groups[r["name"]] = {
|
||||
"path": r["path"],
|
||||
"repos": [
|
||||
repo for repo in r["type"].split("|") if repo in repos
|
||||
],
|
||||
}
|
||||
return repos, groups
|
|
@ -7,7 +7,7 @@ import platform
|
|||
import subprocess
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Coroutine, Union, Iterator, Tuple
|
||||
from typing import List, Dict, Coroutine, Union, Tuple
|
||||
from collections import Counter, defaultdict
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import multiprocessing
|
||||
|
@ -367,15 +367,6 @@ def auto_group(repos: Dict[str, Dict[str, str]], paths: List[str]) -> Dict[str,
|
|||
return new_groups
|
||||
|
||||
|
||||
def parse_clone_config(fname: str) -> Iterator[List[str]]:
|
||||
"""
|
||||
Return the url, name, and path of all repos in `fname`.
|
||||
"""
|
||||
with open(fname) as f:
|
||||
for line in f:
|
||||
yield line.strip().split(",")
|
||||
|
||||
|
||||
async def run_async(repo_name: str, path: str, cmds: List[str]) -> Union[None, str]:
|
||||
"""
|
||||
Run `cmds` asynchronously in `path` directory. Return the `path` if
|
||||
|
@ -430,13 +421,14 @@ def describe(repos: Dict[str, Dict[str, str]], no_colors: bool = False) -> str:
|
|||
Return the status of all repos
|
||||
"""
|
||||
if repos:
|
||||
truncator = info.Truncate()
|
||||
name_width = len(max(repos, key=len)) + 1
|
||||
funcs = info.get_info_funcs(no_colors=no_colors)
|
||||
|
||||
num_threads = min(multiprocessing.cpu_count(), len(repos))
|
||||
with ThreadPoolExecutor(max_workers=num_threads) as executor:
|
||||
for line in executor.map(
|
||||
lambda name: f'{name:<{name_width}}{" ".join(f(repos[name]) for f in funcs)}',
|
||||
lambda name: f'{name:<{name_width}}{" ".join(f(repos[name], truncator) for f in funcs)}',
|
||||
sorted(repos),
|
||||
):
|
||||
yield line
|
||||
|
|
7
setup.py
7
setup.py
|
@ -7,7 +7,7 @@ with open("README.md", encoding="utf-8") as f:
|
|||
setup(
|
||||
name="gita",
|
||||
packages=["gita"],
|
||||
version="0.16.6",
|
||||
version="0.16.7.2",
|
||||
license="MIT",
|
||||
description="Manage multiple git repos with sanity",
|
||||
long_description=long_description,
|
||||
|
@ -19,6 +19,7 @@ setup(
|
|||
author_email="zhou.dong@gmail.com",
|
||||
entry_points={"console_scripts": ["gita = gita.__main__:main"]},
|
||||
python_requires="~=3.6",
|
||||
install_requires=["argcomplete"],
|
||||
classifiers=[
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
|
@ -29,10 +30,12 @@ setup(
|
|||
"Topic :: Software Development :: Version Control :: Git",
|
||||
"Topic :: Terminals",
|
||||
"Topic :: Utilities",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
],
|
||||
include_package_data=True,
|
||||
)
|
||||
|
|
|
@ -143,7 +143,7 @@ class TestLsLl:
|
|||
@patch("gita.info.get_head", return_value="master")
|
||||
@patch(
|
||||
"gita.info._get_repo_status",
|
||||
return_value=("dirty", "staged", "untracked", "diverged"),
|
||||
return_value=("dirty", "staged", "untracked", "", "diverged"),
|
||||
)
|
||||
@patch("gita.info.get_commit_msg", return_value="msg")
|
||||
@patch("gita.info.get_commit_time", return_value="")
|
||||
|
@ -196,8 +196,11 @@ def test_clone_with_url(mock_run):
|
|||
|
||||
|
||||
@patch(
|
||||
"gita.utils.parse_clone_config",
|
||||
return_value=[["git@github.com:user/repo.git", "repo", "/a/repo"]],
|
||||
"gita.io.parse_clone_config",
|
||||
return_value=(
|
||||
{"repo": {"url": "git@github.com:user/repo.git", "path": "/a/repo"}},
|
||||
{},
|
||||
),
|
||||
)
|
||||
@patch("gita.utils.run_async", new=async_mock())
|
||||
@patch("subprocess.run")
|
||||
|
@ -217,8 +220,11 @@ def test_clone_with_config_file(*_):
|
|||
|
||||
|
||||
@patch(
|
||||
"gita.utils.parse_clone_config",
|
||||
return_value=[["git@github.com:user/repo.git", "repo", "/a/repo"]],
|
||||
"gita.io.parse_clone_config",
|
||||
return_value=(
|
||||
{"repo": {"url": "git@github.com:user/repo.git", "path": "/a/repo"}},
|
||||
{},
|
||||
),
|
||||
)
|
||||
@patch("gita.utils.run_async", new=async_mock())
|
||||
@patch("subprocess.run")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue