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:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-20.04, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
python-version: [3.6, 3.7, 3.8, 3.9, "3.10", "3.11"]
|
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
|
|
9
Makefile
9
Makefile
|
@ -1,4 +1,4 @@
|
||||||
.PHONY: dist test install clean twine
|
.PHONY: dist test install clean twine auto-completion
|
||||||
|
|
||||||
install:
|
install:
|
||||||
pip3 install -e .
|
pip3 install -e .
|
||||||
|
@ -10,3 +10,10 @@ twine:
|
||||||
twine upload dist/*
|
twine upload dist/*
|
||||||
clean:
|
clean:
|
||||||
git clean -fdx
|
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:
|
The branch color distinguishes 5 situations between local and remote branches:
|
||||||
|
|
||||||
color | meaning
|
| color | meaning |
|
||||||
---|---
|
| ------ | ---------------------------------------- |
|
||||||
white| local has no remote
|
| white | local has no remote |
|
||||||
green| local is the same as remote
|
| green | local is the same as remote |
|
||||||
red| local has diverged from remote
|
| red | local has diverged from remote |
|
||||||
purple| local is ahead of remote (good for push)
|
| purple | local is ahead of remote (good for push) |
|
||||||
yellow| local is behind remote (good for merge)
|
| yellow | local is behind remote (good for merge) |
|
||||||
|
|
||||||
The choice of purple for ahead and yellow for behind is motivated by
|
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),
|
[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
|
The additional status symbols denote
|
||||||
|
|
||||||
symbol | meaning
|
| symbol | meaning |
|
||||||
---|---
|
| ------ | ----------------------- |
|
||||||
`+`| staged changes
|
| `+` | staged changes |
|
||||||
`*`| unstaged changes
|
| `*` | unstaged changes |
|
||||||
`?`| untracked files/folders
|
| `?` | untracked files/folders |
|
||||||
|
| `$` | stashed contents |
|
||||||
|
|
||||||
The bookkeeping sub-commands are
|
The bookkeeping sub-commands are
|
||||||
|
|
||||||
|
@ -158,11 +159,21 @@ See [this stackoverflow post](https://stackoverflow.com/questions/51680709/color
|
||||||
|
|
||||||
## Auto-completion
|
## Auto-completion
|
||||||
|
|
||||||
Download
|
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.
|
||||||
[.gita-completion.bash](https://github.com/nosarthur/gita/blob/master/.gita-completion.bash)
|
|
||||||
or
|
### Bash
|
||||||
[.gita-completion.zsh](https://github.com/nosarthur/gita/blob/master/.gita-completion.zsh)
|
Download [.gita-completion.bash](https://github.com/nosarthur/gita/blob/master/.gita-completion.bash) and source it in shell.
|
||||||
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
|
## <a name='superman'></a> Superman mode
|
||||||
|
|
||||||
|
@ -380,10 +391,10 @@ their results agree.
|
||||||
|
|
||||||
## Tips
|
## Tips
|
||||||
|
|
||||||
effect | shell command
|
| effect | shell command |
|
||||||
---|---
|
| ------------------------- | ---------------------------------------- |
|
||||||
enter `<repo>` directory|`` cd `gita ls <repo>` ``
|
| enter `<repo>` directory | `` cd `gita ls <repo>` `` |
|
||||||
delete repos in `<group>` | `gita group ll <group> \| xargs gita rm`
|
| delete repos in `<group>` | `gita group ll <group> \| xargs gita rm` |
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
@ -411,4 +422,4 @@ I haven't tried them but I heard good things about them.
|
||||||
|
|
||||||
- [myrepos](https://myrepos.branchable.com/)
|
- [myrepos](https://myrepos.branchable.com/)
|
||||||
- [repo](https://source.android.com/setup/develop/repo)
|
- [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 sys
|
||||||
import csv
|
import csv
|
||||||
import argparse
|
import argparse
|
||||||
|
import argcomplete
|
||||||
import subprocess
|
import subprocess
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import pkg_resources
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import glob
|
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:
|
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:
|
with open(csv_config, "w", newline="") as f:
|
||||||
writer = csv.writer(f)
|
writer = csv.writer(f)
|
||||||
writer.writerow(to_display)
|
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):
|
def f_clone(args: argparse.Namespace):
|
||||||
|
|
||||||
if args.dry_run:
|
if args.dry_run:
|
||||||
if args.from_file:
|
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}")
|
print(f"git clone {url} {abs_path}")
|
||||||
else:
|
else:
|
||||||
print(f"git clone {args.clonee}")
|
print(f"git clone {args.clonee}")
|
||||||
|
@ -172,28 +188,35 @@ def f_clone(args: argparse.Namespace):
|
||||||
f_add(args)
|
f_add(args)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# TODO: add repos to group too
|
||||||
|
repos, groups = io.parse_clone_config(args.clonee)
|
||||||
if args.preserve_path:
|
if args.preserve_path:
|
||||||
utils.exec_async_tasks(
|
utils.exec_async_tasks(
|
||||||
utils.run_async(repo_name, path, ["git", "clone", url, abs_path])
|
utils.run_async(repo_name, path, ["git", "clone", r["url"], r["path"]])
|
||||||
for url, repo_name, abs_path in utils.parse_clone_config(args.clonee)
|
for repo_name, r in repos.items()
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
utils.exec_async_tasks(
|
utils.exec_async_tasks(
|
||||||
utils.run_async(repo_name, path, ["git", "clone", url])
|
utils.run_async(repo_name, path, ["git", "clone", r["url"]])
|
||||||
for url, repo_name, _ in utils.parse_clone_config(args.clonee)
|
for repo_name, r in repos.items()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def f_freeze(args):
|
def f_freeze(args):
|
||||||
repos = utils.get_repos()
|
"""
|
||||||
|
print repo and group information for future cloning
|
||||||
|
"""
|
||||||
ctx = utils.get_context()
|
ctx = utils.get_context()
|
||||||
if args.group is None and ctx:
|
if args.group is None and ctx:
|
||||||
args.group = ctx.stem
|
args.group = ctx.stem
|
||||||
|
repos = utils.get_repos()
|
||||||
|
group_name = args.group
|
||||||
group_repos = None
|
group_repos = None
|
||||||
if args.group: # only display repos in this group
|
if group_name: # only display repos in this group
|
||||||
group_repos = utils.get_groups()[args.group]["repos"]
|
group_repos = utils.get_groups()[group_name]["repos"]
|
||||||
repos = {k: repos[k] for k in group_repos if k in repos}
|
repos = {k: repos[k] for k in group_repos if k in repos}
|
||||||
seen = {""}
|
seen = {""}
|
||||||
|
# print(repos)
|
||||||
for name, prop in repos.items():
|
for name, prop in repos.items():
|
||||||
path = prop["path"]
|
path = prop["path"]
|
||||||
url = ""
|
url = ""
|
||||||
|
@ -211,7 +234,16 @@ def f_freeze(args):
|
||||||
url = parts[1]
|
url = parts[1]
|
||||||
if url not in seen:
|
if url not in seen:
|
||||||
seen.add(url)
|
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):
|
def f_ll(args: argparse.Namespace):
|
||||||
|
@ -435,8 +467,9 @@ def main(argv=None):
|
||||||
title="sub-commands", help="additional help with sub-command -h"
|
title="sub-commands", help="additional help with sub-command -h"
|
||||||
)
|
)
|
||||||
|
|
||||||
version = pkg_resources.require("gita")[0].version
|
p.add_argument(
|
||||||
p.add_argument("-v", "--version", action="version", version=f"%(prog)s {version}")
|
"-v", "--version", action="version", version=f"%(prog)s {get_version()}"
|
||||||
|
)
|
||||||
|
|
||||||
# bookkeeping sub-commands
|
# bookkeeping sub-commands
|
||||||
p_add = subparsers.add_parser("add", description="add repo(s)", help="add repo(s)")
|
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_cmds.add_parser("rm", description="Disable information item.").add_argument(
|
||||||
"info_item", choices=info.ALL_INFO_ITEMS, help="information item to delete"
|
"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:
|
ll_doc = f""" status symbols:
|
||||||
+: staged changes
|
+: staged changes
|
||||||
*: unstaged changes
|
*: unstaged changes
|
||||||
_: untracked files/folders
|
?: untracked files/folders
|
||||||
|
$: stashed changes
|
||||||
|
|
||||||
branch colors:
|
branch colors:
|
||||||
{info.Color.white}white{info.Color.end}: local has no remote
|
{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()
|
cmds = utils.get_cmds_from_files()
|
||||||
for name, data in cmds.items():
|
for name, data in cmds.items():
|
||||||
help = data.get("help")
|
help = data.get("help")
|
||||||
|
repo_help = help
|
||||||
cmd = data["cmd"]
|
cmd = data["cmd"]
|
||||||
if data.get("allow_all"):
|
if data.get("allow_all"):
|
||||||
choices = utils.get_choices()
|
choices = utils.get_choices()
|
||||||
nargs = "*"
|
nargs = "*"
|
||||||
help += " for all repos or"
|
repo_help += " for all repos or"
|
||||||
else:
|
else:
|
||||||
choices = utils.get_repos().keys() | utils.get_groups().keys()
|
choices = utils.get_repos().keys() | utils.get_groups().keys()
|
||||||
nargs = "+"
|
nargs = "+"
|
||||||
help += " for the chosen repo(s) or group(s)"
|
repo_help += " for the chosen repo(s) or group(s)"
|
||||||
sp = subparsers.add_parser(name, description=help)
|
sp = subparsers.add_parser(name, description=help, help=help)
|
||||||
sp.add_argument("repo", nargs=nargs, choices=choices, help=help)
|
sp.add_argument("repo", nargs=nargs, choices=choices, help=repo_help)
|
||||||
is_shell = bool(data.get("shell"))
|
is_shell = bool(data.get("shell"))
|
||||||
sp.add_argument(
|
sp.add_argument(
|
||||||
"-s",
|
"-s",
|
||||||
|
@ -805,6 +845,7 @@ def main(argv=None):
|
||||||
cmd = cmd.split()
|
cmd = cmd.split()
|
||||||
sp.set_defaults(func=f_git_cmd, cmd=cmd)
|
sp.set_defaults(func=f_git_cmd, cmd=cmd)
|
||||||
|
|
||||||
|
argcomplete.autocomplete(p)
|
||||||
args = p.parse_args(argv)
|
args = p.parse_args(argv)
|
||||||
|
|
||||||
args.async_blacklist = {
|
args.async_blacklist = {
|
||||||
|
|
|
@ -22,8 +22,13 @@
|
||||||
"cmd": "git log -1 HEAD",
|
"cmd": "git log -1 HEAD",
|
||||||
"help": "show log information of HEAD"
|
"help": "show log information of HEAD"
|
||||||
},
|
},
|
||||||
"log":
|
"lo":{
|
||||||
{"cmd": "git log",
|
"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,
|
"disable_async": true,
|
||||||
"help": "show logs"
|
"help": "show logs"
|
||||||
},
|
},
|
||||||
|
@ -77,10 +82,12 @@
|
||||||
},
|
},
|
||||||
"stat":{
|
"stat":{
|
||||||
"cmd": "git diff --stat",
|
"cmd": "git diff --stat",
|
||||||
|
"allow_all": true,
|
||||||
"help": "show edit statistics"
|
"help": "show edit statistics"
|
||||||
},
|
},
|
||||||
"st":{
|
"st":{
|
||||||
"cmd": "git status",
|
"cmd": "git status",
|
||||||
|
"allow_all": true,
|
||||||
"help": "show status"
|
"help": "show status"
|
||||||
},
|
},
|
||||||
"tag":{
|
"tag":{
|
||||||
|
|
78
gita/info.py
78
gita/info.py
|
@ -9,6 +9,40 @@ from typing import Tuple, List, Callable, Dict
|
||||||
from . import common
|
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):
|
class Color(Enum):
|
||||||
"""
|
"""
|
||||||
Terminal color
|
Terminal color
|
||||||
|
@ -113,8 +147,8 @@ def get_info_items() -> List[str]:
|
||||||
return display_items
|
return display_items
|
||||||
|
|
||||||
|
|
||||||
def get_path(prop: Dict[str, str]) -> str:
|
def get_path(prop: Dict[str, str], truncator: Truncate) -> str:
|
||||||
return f'{Color.cyan}{prop["path"]}{Color.end}'
|
return f'{Color.cyan}{truncator.truncate("path", prop["path"])}{Color.end}'
|
||||||
|
|
||||||
|
|
||||||
# TODO: do we need to add the flags here too?
|
# 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)
|
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.
|
Return the last commit message.
|
||||||
"""
|
"""
|
||||||
|
@ -177,10 +225,10 @@ def get_commit_msg(prop: Dict[str, str]) -> str:
|
||||||
universal_newlines=True,
|
universal_newlines=True,
|
||||||
cwd=prop["path"],
|
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.
|
Return the last commit time in parenthesis.
|
||||||
"""
|
"""
|
||||||
|
@ -192,13 +240,14 @@ def get_commit_time(prop: Dict[str, str]) -> str:
|
||||||
universal_newlines=True,
|
universal_newlines=True,
|
||||||
cwd=prop["path"],
|
cwd=prop["path"],
|
||||||
)
|
)
|
||||||
return f"({result.stdout.strip()})"
|
return truncator.truncate("commit_time", f"({result.stdout.strip()})")
|
||||||
|
|
||||||
|
|
||||||
default_symbols = {
|
default_symbols = {
|
||||||
"dirty": "*",
|
"dirty": "*",
|
||||||
"staged": "+",
|
"staged": "+",
|
||||||
"untracked": "?",
|
"untracked": "?",
|
||||||
|
"stashed": "$",
|
||||||
"local_ahead": "↑",
|
"local_ahead": "↑",
|
||||||
"remote_ahead": "↓",
|
"remote_ahead": "↓",
|
||||||
"diverged": "⇕",
|
"diverged": "⇕",
|
||||||
|
@ -223,11 +272,11 @@ def get_symbols() -> Dict[str, str]:
|
||||||
return default_symbols
|
return default_symbols
|
||||||
|
|
||||||
|
|
||||||
def get_repo_status(prop: Dict[str, str], no_colors=False) -> str:
|
def get_repo_status(prop: Dict[str, str], truncator: Truncate, no_colors=False) -> str:
|
||||||
branch = get_head(prop["path"])
|
branch = truncator.truncate("branch", get_head(prop["path"]))
|
||||||
dirty, staged, untracked, situ = _get_repo_status(prop)
|
dirty, staged, untracked, stashed, situ = _get_repo_status(prop)
|
||||||
symbols = get_symbols()
|
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:
|
if no_colors:
|
||||||
return f"{info:<18}"
|
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}"
|
return f"{color}{info:<18}{Color.end}"
|
||||||
|
|
||||||
|
|
||||||
def get_repo_branch(prop: Dict[str, str]) -> str:
|
def get_repo_branch(prop: Dict[str, str], truncator: Truncate) -> str:
|
||||||
return get_head(prop["path"])
|
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
|
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 ""
|
dirty = "dirty" if run_quiet_diff(flags, [], path) else ""
|
||||||
staged = "staged" if run_quiet_diff(flags, ["--cached"], path) else ""
|
staged = "staged" if run_quiet_diff(flags, ["--cached"], path) else ""
|
||||||
untracked = "untracked" if has_untracked(flags, 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)
|
diff_returncode = run_quiet_diff(flags, ["@{u}", "@{0}"], path)
|
||||||
if diff_returncode == 128:
|
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"
|
situ = "diverged" if diverged else "remote_ahead"
|
||||||
else: # local is ahead of remote
|
else: # local is ahead of remote
|
||||||
situ = "local_ahead"
|
situ = "local_ahead"
|
||||||
return dirty, staged, untracked, situ
|
return dirty, staged, untracked, stashed, situ
|
||||||
|
|
||||||
|
|
||||||
ALL_INFO_ITEMS = {
|
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
|
import subprocess
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from pathlib import Path
|
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 collections import Counter, defaultdict
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
@ -367,15 +367,6 @@ def auto_group(repos: Dict[str, Dict[str, str]], paths: List[str]) -> Dict[str,
|
||||||
return new_groups
|
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]:
|
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
|
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
|
Return the status of all repos
|
||||||
"""
|
"""
|
||||||
if repos:
|
if repos:
|
||||||
|
truncator = info.Truncate()
|
||||||
name_width = len(max(repos, key=len)) + 1
|
name_width = len(max(repos, key=len)) + 1
|
||||||
funcs = info.get_info_funcs(no_colors=no_colors)
|
funcs = info.get_info_funcs(no_colors=no_colors)
|
||||||
|
|
||||||
num_threads = min(multiprocessing.cpu_count(), len(repos))
|
num_threads = min(multiprocessing.cpu_count(), len(repos))
|
||||||
with ThreadPoolExecutor(max_workers=num_threads) as executor:
|
with ThreadPoolExecutor(max_workers=num_threads) as executor:
|
||||||
for line in executor.map(
|
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),
|
sorted(repos),
|
||||||
):
|
):
|
||||||
yield line
|
yield line
|
||||||
|
|
7
setup.py
7
setup.py
|
@ -7,7 +7,7 @@ with open("README.md", encoding="utf-8") as f:
|
||||||
setup(
|
setup(
|
||||||
name="gita",
|
name="gita",
|
||||||
packages=["gita"],
|
packages=["gita"],
|
||||||
version="0.16.6",
|
version="0.16.7.2",
|
||||||
license="MIT",
|
license="MIT",
|
||||||
description="Manage multiple git repos with sanity",
|
description="Manage multiple git repos with sanity",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
|
@ -19,6 +19,7 @@ setup(
|
||||||
author_email="zhou.dong@gmail.com",
|
author_email="zhou.dong@gmail.com",
|
||||||
entry_points={"console_scripts": ["gita = gita.__main__:main"]},
|
entry_points={"console_scripts": ["gita = gita.__main__:main"]},
|
||||||
python_requires="~=3.6",
|
python_requires="~=3.6",
|
||||||
|
install_requires=["argcomplete"],
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Development Status :: 4 - Beta",
|
"Development Status :: 4 - Beta",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
|
@ -29,10 +30,12 @@ setup(
|
||||||
"Topic :: Software Development :: Version Control :: Git",
|
"Topic :: Software Development :: Version Control :: Git",
|
||||||
"Topic :: Terminals",
|
"Topic :: Terminals",
|
||||||
"Topic :: Utilities",
|
"Topic :: Utilities",
|
||||||
"Programming Language :: Python :: 3.6",
|
|
||||||
"Programming Language :: Python :: 3.7",
|
"Programming Language :: Python :: 3.7",
|
||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.8",
|
||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
],
|
],
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
)
|
)
|
||||||
|
|
|
@ -143,7 +143,7 @@ class TestLsLl:
|
||||||
@patch("gita.info.get_head", return_value="master")
|
@patch("gita.info.get_head", return_value="master")
|
||||||
@patch(
|
@patch(
|
||||||
"gita.info._get_repo_status",
|
"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_msg", return_value="msg")
|
||||||
@patch("gita.info.get_commit_time", return_value="")
|
@patch("gita.info.get_commit_time", return_value="")
|
||||||
|
@ -196,8 +196,11 @@ def test_clone_with_url(mock_run):
|
||||||
|
|
||||||
|
|
||||||
@patch(
|
@patch(
|
||||||
"gita.utils.parse_clone_config",
|
"gita.io.parse_clone_config",
|
||||||
return_value=[["git@github.com:user/repo.git", "repo", "/a/repo"]],
|
return_value=(
|
||||||
|
{"repo": {"url": "git@github.com:user/repo.git", "path": "/a/repo"}},
|
||||||
|
{},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
@patch("gita.utils.run_async", new=async_mock())
|
@patch("gita.utils.run_async", new=async_mock())
|
||||||
@patch("subprocess.run")
|
@patch("subprocess.run")
|
||||||
|
@ -217,8 +220,11 @@ def test_clone_with_config_file(*_):
|
||||||
|
|
||||||
|
|
||||||
@patch(
|
@patch(
|
||||||
"gita.utils.parse_clone_config",
|
"gita.io.parse_clone_config",
|
||||||
return_value=[["git@github.com:user/repo.git", "repo", "/a/repo"]],
|
return_value=(
|
||||||
|
{"repo": {"url": "git@github.com:user/repo.git", "path": "/a/repo"}},
|
||||||
|
{},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
@patch("gita.utils.run_async", new=async_mock())
|
@patch("gita.utils.run_async", new=async_mock())
|
||||||
@patch("subprocess.run")
|
@patch("subprocess.run")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue