Adding upstream version 0.15.1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
d01d7be95b
commit
f2586667ea
22 changed files with 1805 additions and 372 deletions
|
@ -7,30 +7,36 @@ _gita_completions()
|
||||||
cur=${COMP_WORDS[COMP_CWORD]}
|
cur=${COMP_WORDS[COMP_CWORD]}
|
||||||
cmd=${COMP_WORDS[1]}
|
cmd=${COMP_WORDS[1]}
|
||||||
|
|
||||||
# FIXME: this is somewhat slow
|
|
||||||
commands=`gita -h | sed '2q;d' |sed 's/[{}.,]/ /g'`
|
|
||||||
|
|
||||||
repos=`gita ls`
|
|
||||||
# this doesn't work for two repos with the same basename
|
# this doesn't work for two repos with the same basename
|
||||||
#gita_path=${XDG_CONFIG_HOME:-$HOME/.config}/gita/repo_path
|
#gita_path=${XDG_CONFIG_HOME:-$HOME/.config}/gita/repo_path
|
||||||
#repos=`awk '{split($0, paths, ":")} END {for (i in paths) {n=split(paths[i],b, /\//); print b[n]}}' ${gita_path}`
|
#repos=`awk '{split($0, paths, ":")} END {for (i in paths) {n=split(paths[i],b, /\//); print b[n]}}' ${gita_path}`
|
||||||
|
|
||||||
if [ $COMP_CWORD -eq 1 ]; then
|
if [ $COMP_CWORD -eq 1 ]; then
|
||||||
|
# FIXME: this is somewhat slow
|
||||||
|
commands=`gita -h | sed '2q;d' |sed 's/[{}.,]/ /g'`
|
||||||
COMPREPLY=($(compgen -W "${commands}" ${cur}))
|
COMPREPLY=($(compgen -W "${commands}" ${cur}))
|
||||||
elif [ $COMP_CWORD -gt 1 ]; then
|
elif [ $COMP_CWORD -gt 1 ]; then
|
||||||
case $cmd in
|
case $cmd in
|
||||||
add)
|
add)
|
||||||
COMPREPLY=($(compgen -d ${cur}))
|
COMPREPLY=($(compgen -d ${cur}))
|
||||||
;;
|
;;
|
||||||
ll)
|
clone)
|
||||||
|
COMPREPLY=($(compgen -f ${cur}))
|
||||||
|
;;
|
||||||
|
color | flags)
|
||||||
|
COMPREPLY=($(compgen -W "ll set" ${cur}))
|
||||||
|
;;
|
||||||
|
ll | context)
|
||||||
|
groups=`gita group ls`
|
||||||
|
COMPREPLY=($(compgen -W "${groups}" ${cur}))
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
|
repos=`gita ls`
|
||||||
COMPREPLY=($(compgen -W "${repos}" ${cur}))
|
COMPREPLY=($(compgen -W "${repos}" ${cur}))
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
complete -F _gita_completions gita
|
complete -F _gita_completions gita
|
||||||
|
|
7
.github/dependabot.yml
vendored
Normal file
7
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: pip
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
open-pull-requests-limit: 10
|
|
@ -1 +1 @@
|
||||||
include gita/cmds.yml
|
include gita/cmds.json
|
||||||
|
|
210
README.md
210
README.md
|
@ -14,7 +14,7 @@
|
||||||
| | ____ | | | | | ___ |
|
| | ____ | | | | | ___ |
|
||||||
| | \_ ) | | | | | ( ) |
|
| | \_ ) | | | | | ( ) |
|
||||||
| (___) |__) (___ | | | ) ( |
|
| (___) |__) (___ | | | ) ( |
|
||||||
(_______)_______/ )_( |/ \| v0.12
|
(_______)_______/ )_( |/ \| v0.15
|
||||||
```
|
```
|
||||||
|
|
||||||
# Gita: a command-line tool to manage multiple git repos
|
# Gita: a command-line tool to manage multiple git repos
|
||||||
|
@ -29,11 +29,13 @@ I also hate to change directories to execute git commands.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
In the screenshot, the `gita remote nowhub` command translates to `git remote -v`
|
In this screenshot, the `gita ll` command displays the status of all repos.
|
||||||
for the `nowhub` repo, even though we are at the `blog` repo.
|
The `gita remote dotfiles` command translates to `git remote -v`
|
||||||
To see the pre-defined sub-commands, run `gita -h` or take a look at
|
for the `dotfiles` repo, even though we are not in the repo.
|
||||||
[cmds.yml](https://github.com/nosarthur/gita/blob/master/gita/cmds.yml).
|
The `gita fetch` command fetches from all repos and two of them have updates.
|
||||||
To add your own sub-commands, see the [customization section](#custom).
|
To see the pre-defined commands, run `gita -h` or take a look at
|
||||||
|
[cmds.json](https://github.com/nosarthur/gita/blob/master/gita/cmds.json).
|
||||||
|
To add your own commands, see the [customization section](#custom).
|
||||||
To run arbitrary `git` command, see the [superman mode section](#superman).
|
To run arbitrary `git` command, see the [superman mode section](#superman).
|
||||||
To run arbitrary shell command, see the [shell mode section](#shell).
|
To run arbitrary shell command, see the [shell mode section](#shell).
|
||||||
|
|
||||||
|
@ -48,7 +50,7 @@ The branch color distinguishes 5 situations between local and remote branches:
|
||||||
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),
|
||||||
using green as baseline.
|
using green as baseline.
|
||||||
You can change the color scheme using the `gita color` sub-command.
|
You can change the color scheme using the `gita color` command.
|
||||||
See the [customization section](#custom).
|
See the [customization section](#custom).
|
||||||
|
|
||||||
The additional status symbols denote
|
The additional status symbols denote
|
||||||
|
@ -60,7 +62,13 @@ The additional status symbols denote
|
||||||
The bookkeeping sub-commands are
|
The bookkeeping sub-commands are
|
||||||
|
|
||||||
- `gita add <repo-path(s)>`: add repo(s) to `gita`
|
- `gita add <repo-path(s)>`: add repo(s) to `gita`
|
||||||
|
- `gita add -a <repo-parent-path(s)>`: add repo(s) in <repo-parent-path(s)> recursively
|
||||||
|
and automatically generate hierarchical groups. See the [customization section](#custom) for more details.
|
||||||
|
- `gita add -b <bare-repo-path(s)>`: add bare repo(s) to `gita`. See the [customization section](#custom) for more details on setting custom worktree.
|
||||||
|
- `gita add -m <main-repo-path(s)>`: add main repo(s) to `gita`. See the [customization section](#custom) for more details.
|
||||||
|
- `gita add -r <repo-parent-path(s)>`: add repo(s) in <repo-parent-path(s)> recursively
|
||||||
- `gita clone <config-file>`: clone repos in `config-file` (generated by `gita freeze`) to current directory.
|
- `gita clone <config-file>`: clone repos in `config-file` (generated by `gita freeze`) to current directory.
|
||||||
|
- `gita clone -p <config-file>`: clone repos in `config-file` to prescribed paths.
|
||||||
- `gita context`: context sub-command
|
- `gita context`: context sub-command
|
||||||
- `gita context`: show current context
|
- `gita context`: show current context
|
||||||
- `gita context none`: remove context
|
- `gita context none`: remove context
|
||||||
|
@ -68,9 +76,12 @@ The bookkeeping sub-commands are
|
||||||
- `gita color`: color sub-command
|
- `gita color`: color sub-command
|
||||||
- `gita color [ll]`: Show available colors and the current coloring scheme
|
- `gita color [ll]`: Show available colors and the current coloring scheme
|
||||||
- `gita color set <situation> <color>`: Use the specified color for the local-remote situation
|
- `gita color set <situation> <color>`: Use the specified color for the local-remote situation
|
||||||
|
- `gita flags`: flags sub-command
|
||||||
|
- `gita flags set <repo-name> <flags>`: add custom `flags` to repo
|
||||||
|
- `gita flags [ll]`: display repos with custom flags
|
||||||
- `gita freeze`: print information of all repos such as URL, name, and path.
|
- `gita freeze`: print information of all repos such as URL, name, and path.
|
||||||
- `gita group`: group sub-command
|
- `gita group`: group sub-command
|
||||||
- `gita group add <repo-name(s)> -n <group-name>`: add repo(s) to a new group or existing group
|
- `gita group add <repo-name(s)> -n <group-name>`: add repo(s) to a new or existing group
|
||||||
- `gita group [ll]`: display existing groups with repos
|
- `gita group [ll]`: display existing groups with repos
|
||||||
- `gita group ls`: display existing group names
|
- `gita group ls`: display existing group names
|
||||||
- `gita group rename <group-name> <new-name>`: change group name
|
- `gita group rename <group-name> <new-name>`: change group name
|
||||||
|
@ -85,7 +96,7 @@ The bookkeeping sub-commands are
|
||||||
- `gita ls`: display the names of all repos
|
- `gita ls`: display the names of all repos
|
||||||
- `gita ls <repo-name>`: display the absolute path of one repo
|
- `gita ls <repo-name>`: display the absolute path of one repo
|
||||||
- `gita rename <repo-name> <new-name>`: rename a repo
|
- `gita rename <repo-name> <new-name>`: rename a repo
|
||||||
- `gita rm <repo-name(s)>`: remove repo(s) from `gita` (won't remove files from disk)
|
- `gita rm <repo-name(s)>`: remove repo(s) from `gita` (won't remove files on disk)
|
||||||
- `gita -v`: display gita version
|
- `gita -v`: display gita version
|
||||||
|
|
||||||
The `git` delegating sub-commands are of two formats
|
The `git` delegating sub-commands are of two formats
|
||||||
|
@ -99,7 +110,7 @@ They translate to `git <sub-command>` for the corresponding repos.
|
||||||
By default, only `fetch` and `pull` take optional input. In other words,
|
By default, only `fetch` and `pull` take optional input. In other words,
|
||||||
`gita fetch` and `gita pull` apply to all repos.
|
`gita fetch` and `gita pull` apply to all repos.
|
||||||
To see the pre-defined sub-commands, run `gita -h` or take a look at
|
To see the pre-defined sub-commands, run `gita -h` or take a look at
|
||||||
[cmds.yml](https://github.com/nosarthur/gita/blob/master/gita/cmds.yml).
|
[cmds.json](https://github.com/nosarthur/gita/blob/master/gita/cmds.json).
|
||||||
To add your own sub-commands or override the default behaviors, see the [customization section](#custom).
|
To add your own sub-commands or override the default behaviors, see the [customization section](#custom).
|
||||||
To run arbitrary `git` command, see the [superman mode section](#superman).
|
To run arbitrary `git` command, see the [superman mode section](#superman).
|
||||||
|
|
||||||
|
@ -107,7 +118,8 @@ If more than one repos are specified, the `git` command runs asynchronously,
|
||||||
with the exception of `log`, `difftool` and `mergetool`,
|
with the exception of `log`, `difftool` and `mergetool`,
|
||||||
which require non-trivial user input.
|
which require non-trivial user input.
|
||||||
|
|
||||||
Repo paths are saved in `$XDG_CONFIG_HOME/gita/repo_path` (most likely `~/.config/gita/repo_path`).
|
Repo configuration is saved in `$XDG_CONFIG_HOME/gita/repos.csv`
|
||||||
|
(most likely `~/.config/gita/repos.csv`).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -124,7 +136,7 @@ pip3 install -e <gita-source-folder>
|
||||||
```
|
```
|
||||||
|
|
||||||
In either case, calling `gita` in terminal may not work,
|
In either case, calling `gita` in terminal may not work,
|
||||||
then you can put the following line in the `.bashrc` file.
|
then put the following line in the `.bashrc` file.
|
||||||
|
|
||||||
```
|
```
|
||||||
alias gita="python3 -m gita"
|
alias gita="python3 -m gita"
|
||||||
|
@ -140,7 +152,7 @@ Download
|
||||||
[.gita-completion.bash](https://github.com/nosarthur/gita/blob/master/.gita-completion.bash)
|
[.gita-completion.bash](https://github.com/nosarthur/gita/blob/master/.gita-completion.bash)
|
||||||
or
|
or
|
||||||
[.gita-completion.zsh](https://github.com/nosarthur/gita/blob/master/.gita-completion.zsh)
|
[.gita-completion.zsh](https://github.com/nosarthur/gita/blob/master/.gita-completion.zsh)
|
||||||
and source it in the corresponding rc file.
|
and source it in shell.
|
||||||
|
|
||||||
## <a name='superman'></a> Superman mode
|
## <a name='superman'></a> Superman mode
|
||||||
|
|
||||||
|
@ -171,65 +183,178 @@ Here `repo-name(s)` or `group-name(s)` are optional, and their absence means all
|
||||||
For example,
|
For example,
|
||||||
|
|
||||||
- `gita shell ll` lists contents for all repos
|
- `gita shell ll` lists contents for all repos
|
||||||
- `gita shell repo1 mkdir docs` create a new directory `docs` in repo1
|
- `gita shell repo1 repo2 mkdir docs` create a new directory `docs` in `repo1` and `repo2`
|
||||||
|
- `gita shell "git describe --abbrev=0 --tags | xargs git checkout"`: check out the latest tag for all repos
|
||||||
|
|
||||||
## <a name='custom'></a> Customization
|
## <a name='custom'></a> Customization
|
||||||
|
|
||||||
### user-defined sub-command using yaml file
|
### define repo group and context
|
||||||
|
|
||||||
Custom delegating sub-commands can be defined in `$XDG_CONFIG_HOME/gita/cmds.yml`
|
When the project contains several independent but related repos,
|
||||||
(most likely `~/.config/gita/cmds.yml`).
|
we can define a group and execute `gita` command on this group.
|
||||||
|
For example,
|
||||||
|
|
||||||
|
```
|
||||||
|
gita group add repo1 repo2 -n my-group
|
||||||
|
gita ll my-group
|
||||||
|
gita pull my-group
|
||||||
|
```
|
||||||
|
|
||||||
|
To save more typing, one can set a group as context, then any `gita` command
|
||||||
|
is scoped to the group
|
||||||
|
|
||||||
|
```
|
||||||
|
gita context my-group
|
||||||
|
gita ll
|
||||||
|
gita pull
|
||||||
|
```
|
||||||
|
|
||||||
|
It is also possible to recursively add repos within a directory and
|
||||||
|
generate hierarchical groups automatically. For example, running
|
||||||
|
|
||||||
|
```
|
||||||
|
gita add -a src
|
||||||
|
```
|
||||||
|
on the following folder structure
|
||||||
|
```
|
||||||
|
src
|
||||||
|
├── project1
|
||||||
|
│ ├── repo1
|
||||||
|
│ └── repo2
|
||||||
|
├── repo3
|
||||||
|
├── project2
|
||||||
|
│ ├── repo4
|
||||||
|
│ └── repo5
|
||||||
|
└── repo6
|
||||||
|
```
|
||||||
|
gives rise to
|
||||||
|
```
|
||||||
|
src:repo1,repo2,repo3,repo4,repo5,repo6
|
||||||
|
src-project1:repo1,repo2
|
||||||
|
src-project2:repo4,repo5
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### define main repos and shadow the global configuration setting with local setting
|
||||||
|
|
||||||
|
The so-called main repos contain `.gita` folder for local configurations.
|
||||||
|
It works best for the repos-within-repo project structure, for example,
|
||||||
|
|
||||||
|
```
|
||||||
|
main-repo
|
||||||
|
├── sub-repo1
|
||||||
|
│ └── sub-sub-repo
|
||||||
|
├── sub-repo2
|
||||||
|
└── sub-repo3
|
||||||
|
```
|
||||||
|
|
||||||
|
When executing `gita` commands within/relative to a main repo, local configurations
|
||||||
|
are used. And only repos within the current main repos are in the scope.
|
||||||
|
|
||||||
|
To add a main repo, run
|
||||||
|
|
||||||
|
```
|
||||||
|
gita add -m main-repo-path
|
||||||
|
```
|
||||||
|
|
||||||
|
Subordinate repos are added recursively to the local configuration.
|
||||||
|
Only the main repo is saved to the global configuration.
|
||||||
|
|
||||||
|
In the `gita ll` display, the main repos are underlined.
|
||||||
|
|
||||||
|
### add user-defined sub-command using json file
|
||||||
|
|
||||||
|
Custom delegating sub-commands can be defined in `$XDG_CONFIG_HOME/gita/cmds.json`
|
||||||
|
(most likely `~/.config/gita/cmds.json`)
|
||||||
And they shadow the default ones if name collisions exist.
|
And they shadow the default ones if name collisions exist.
|
||||||
|
|
||||||
Default delegating sub-commands are defined in
|
Default delegating sub-commands are defined in
|
||||||
[cmds.yml](https://github.com/nosarthur/gita/blob/master/gita/cmds.yml).
|
[cmds.json](https://github.com/nosarthur/gita/blob/master/gita/cmds.json).
|
||||||
For example, `gita stat <repo-name(s)>` is registered as
|
For example, `gita stat <repo-name(s)>` is registered as
|
||||||
|
|
||||||
```yaml
|
```json
|
||||||
stat:
|
"stat":{
|
||||||
cmd: diff --stat
|
"cmd": "git diff --stat",
|
||||||
help: show edit statistics
|
"help": "show edit statistics"
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
which executes `git diff --stat` for the specified repo(s).
|
which executes `git diff --stat` for the specified repo(s).
|
||||||
|
|
||||||
If the delegated `git` command is a single word, the `cmd` tag can be omitted.
|
To disable asynchronous execution, set `disable_async` to be `true`.
|
||||||
See `push` for an example.
|
See the `difftool` example:
|
||||||
To disable asynchronous execution, set the `disable_async` tag to be `true`.
|
|
||||||
See `difftool` for an example.
|
|
||||||
|
|
||||||
If you want a custom command to behave like `gita fetch`, i.e., to apply the
|
```json
|
||||||
command to all repos when no repo is specified,
|
"difftool":{
|
||||||
set the `allow_all` option to be `true`.
|
"cmd": "git difftool",
|
||||||
|
"disable_async": true,
|
||||||
|
"help": "show differences using a tool"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want a custom command to behave like `gita fetch`, i.e., to apply to all
|
||||||
|
repos when no repo is specified, set `allow_all` to be `true`.
|
||||||
For example, the following snippet creates a new command
|
For example, the following snippet creates a new command
|
||||||
`gita comaster [repo-name(s)]` with optional repo name input.
|
`gita comaster [repo-name(s)]` with optional repo name input.
|
||||||
|
|
||||||
```yaml
|
```json
|
||||||
comaster:
|
"comaster":{
|
||||||
cmd: checkout master
|
"cmd": "checkout master",
|
||||||
allow_all: true
|
"allow_all": true,
|
||||||
help: checkout the master branch
|
"help": "checkout the master branch"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Any command that runs in the [superman mode](#superman) mode or the
|
||||||
|
[shell mode](#shell) can be defined in this json format.
|
||||||
|
For example, the following command runs in shell mode and fetches only the
|
||||||
|
current branch from upstream.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"fetchcrt":{
|
||||||
|
"cmd": "git rev-parse --abbrev-ref HEAD | xargs git fetch --prune upstream",
|
||||||
|
"allow_all": true,
|
||||||
|
"shell": true,
|
||||||
|
"help": "fetch current branch only"
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### customize the local/remote relationship coloring displayed by the `gita ll` command
|
### customize the local/remote relationship coloring displayed by the `gita ll` command
|
||||||
|
|
||||||
You can see the default color scheme and the available colors via `gita color`.
|
You can see the default color scheme and the available colors via `gita color`.
|
||||||
To change the color coding, use `gita color set <situation> <color>`.
|
To change the color coding, use `gita color set <situation> <color>`.
|
||||||
The configuration is saved in `$XDG_CONFIG_HOME/gita/color.yml`.
|
The configuration is saved in `$XDG_CONFIG_HOME/gita/color.csv`.
|
||||||
|
|
||||||
### customize information displayed by the `gita ll` command
|
### customize information displayed by the `gita ll` command
|
||||||
|
|
||||||
You can customize the information displayed by `gita ll`.
|
You can customize the information displayed by `gita ll`.
|
||||||
The used and unused information items are shown with `gita info`, and the
|
The used and unused information items are shown with `gita info`, and the
|
||||||
configuration is saved in `$XDG_CONFIG_HOME/gita/info.yml`.
|
configuration is saved in `$XDG_CONFIG_HOME/gita/info.csv`.
|
||||||
|
|
||||||
For example, the default information items setting corresponds to
|
For example, the default setting corresponds to
|
||||||
|
|
||||||
```yaml
|
```csv
|
||||||
- branch
|
branch,commit_msg,commit_time
|
||||||
- commit_msg
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### customize git command flags
|
||||||
|
|
||||||
|
One can set custom flags to run `git` commands. For example
|
||||||
|
|
||||||
|
```
|
||||||
|
gita flags set my-repo --git-dir=$HOME/somefolder --work-tree=$HOME
|
||||||
|
```
|
||||||
|
|
||||||
|
Then any `git` command/alias triggered from `gita` on `my-repo` will use these flags.
|
||||||
|
Note that the flags are applied immediately after `git`. For example,
|
||||||
|
`gita st my-repo` translates to
|
||||||
|
|
||||||
|
```
|
||||||
|
git --git-dir=$HOME/somefolder --work-tree=$HOME status
|
||||||
|
```
|
||||||
|
|
||||||
|
running from the `my-repo` directory.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
Gita requires Python 3.6 or higher, due to the use of
|
Gita requires Python 3.6 or higher, due to the use of
|
||||||
|
@ -249,9 +374,12 @@ To contribute, you can
|
||||||
- request/implement features
|
- request/implement features
|
||||||
- star/recommend this project
|
- star/recommend this project
|
||||||
|
|
||||||
|
Read [this article](https://www.dataschool.io/how-to-contribute-on-github/) if you have never contribute code to open source project before.
|
||||||
|
|
||||||
Chat room is available on [](https://gitter.im/nosarthur/gita?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
Chat room is available on [](https://gitter.im/nosarthur/gita?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
To run tests locally, simply `pytest`.
|
To run tests locally, simply `pytest` in the source code folder.
|
||||||
|
Note that context should be set as `none`.
|
||||||
More implementation details are in
|
More implementation details are in
|
||||||
[design.md](https://github.com/nosarthur/gita/blob/master/doc/design.md).
|
[design.md](https://github.com/nosarthur/gita/blob/master/doc/design.md).
|
||||||
A step-by-step guide to reproduce this project is [here](https://nosarthur.github.io/side%20project/2019/05/27/gita-breakdown.html).
|
A step-by-step guide to reproduce this project is [here](https://nosarthur.github.io/side%20project/2019/05/27/gita-breakdown.html).
|
||||||
|
|
|
@ -14,12 +14,12 @@
|
||||||
| | ____ | | | | | ___ |
|
| | ____ | | | | | ___ |
|
||||||
| | \_ ) | | | | | ( ) |
|
| | \_ ) | | | | | ( ) |
|
||||||
| (___) |__) (___ | | | ) ( |
|
| (___) |__) (___ | | | ) ( |
|
||||||
(_______)_______/ )_( |/ \| v0.12
|
(_______)_______/ )_( |/ \| v0.15
|
||||||
```
|
```
|
||||||
|
|
||||||
# Gita:一个管理多个 git 库的命令行工具
|
# Gita:一个管理多个 git 库的命令行工具
|
||||||
|
|
||||||
这个工具有两个作用:
|
这个工具有两个功能:
|
||||||
|
|
||||||
- 并排显示多个库的状态信息,比如分支名,编辑状态,提交信息等
|
- 并排显示多个库的状态信息,比如分支名,编辑状态,提交信息等
|
||||||
- 在任何目录下(批处理)代理执行 git 指令
|
- 在任何目录下(批处理)代理执行 git 指令
|
||||||
|
@ -46,17 +46,34 @@
|
||||||
基础指令:
|
基础指令:
|
||||||
|
|
||||||
- `gita add <repo-path(s)>`: 添加库
|
- `gita add <repo-path(s)>`: 添加库
|
||||||
|
- `gita add -a <repo-parent-path(s)>`:
|
||||||
|
- `gita add -b <bare-repo-path(s)>`:
|
||||||
|
- `gita add -m <main-repo-path(s)>`:
|
||||||
|
- `gita add -r <repo-parent-path(s)>`:
|
||||||
|
- `gita clone <config-file>`:
|
||||||
|
- `gita clone -p <config-file>`:
|
||||||
- `gita context`: 情境命令
|
- `gita context`: 情境命令
|
||||||
- `gita context`: 显示当前的情境
|
- `gita context`: 显示当前的情境
|
||||||
- `gita context none`: 去除情境
|
- `gita context none`: 去除情境
|
||||||
- `gita context <group-name>`: 把情境设置成`group-name`, 之后所有的操作只作用到这个组里的库
|
- `gita context <group-name>`: 把情境设置成`group-name`, 之后所有的操作只作用到这个组里的库
|
||||||
|
- `gita color`:
|
||||||
|
- `gita color [ll]`:
|
||||||
|
- `gita color set <situation> <color>`:
|
||||||
|
- `gita flags`:
|
||||||
|
- `gita flags set <repo-name> <flags>`:
|
||||||
|
- `gita flags [ll]`:
|
||||||
|
- `gita freeze`:
|
||||||
- `gita group`: 组群命令
|
- `gita group`: 组群命令
|
||||||
- `gita group add <repo-name(s)>`: 把库加入新的或者已经存在的组
|
- `gita group add <repo-name(s)>`: 把库加入新的或者已经存在的组
|
||||||
- `gita group [ll]`: 显示已有的组和它们的库
|
- `gita group [ll]`: 显示已有的组和它们的库
|
||||||
- `gita group ls`: 显示已有的组名
|
- `gita group ls`: 显示已有的组名
|
||||||
- `gita group rename <group-name> <new-name>`: 改组名
|
- `gita group rename <group-name> <new-name>`: 改组名
|
||||||
- `gita group rm group(s): 删除组
|
- `gita group rm group(s): 删除组
|
||||||
|
- `gita group rmrepo -n <group-name>:
|
||||||
- `gita info`: 显示已用的和未用的信息项
|
- `gita info`: 显示已用的和未用的信息项
|
||||||
|
- `gita info [ll]`
|
||||||
|
- `gita info add <info-item>`
|
||||||
|
- `gita info rm <info-item>`
|
||||||
- `gita ll`: 显示所有库的状态信息
|
- `gita ll`: 显示所有库的状态信息
|
||||||
- `gita ll <group-name>`: 显示一个组群中库的状态信息
|
- `gita ll <group-name>`: 显示一个组群中库的状态信息
|
||||||
- `gita ls`: 显示所有库的名字
|
- `gita ls`: 显示所有库的名字
|
||||||
|
@ -65,7 +82,7 @@
|
||||||
- `gita rm <repo-name(s)>`: 移除库(不会删除文件)
|
- `gita rm <repo-name(s)>`: 移除库(不会删除文件)
|
||||||
- `gita -v`: 显示版本号
|
- `gita -v`: 显示版本号
|
||||||
|
|
||||||
库的路径存在`$XDG_CONFIG_HOME/gita/repo_path` (多半是`~/.config/gita/repo_path`)。
|
库的路径存在`$XDG_CONFIG_HOME/gita/repos.csv` (多半是`~/.config/gita/repos.csv`)。
|
||||||
|
|
||||||
代理执行的子命令有两种格式:
|
代理执行的子命令有两种格式:
|
||||||
|
|
||||||
|
@ -150,11 +167,10 @@ comaster:
|
||||||
help: checkout the master branch
|
help: checkout the master branch
|
||||||
```
|
```
|
||||||
另一个自定义功能是针对`gita ll`展示的信息项。
|
另一个自定义功能是针对`gita ll`展示的信息项。
|
||||||
`gita info`可以展示所有用到的和没用到的信息项,并且可以通过修改`$XDG_CONFIG_HOME/gita/info.yml`支持自定义。举个栗子,默认的信息项显示配置相当于是:
|
`gita info`可以展示所有用到的和没用到的信息项,并且可以通过修改`$XDG_CONFIG_HOME/gita/info.csv`支持自定义。举个栗子,默认的信息项显示配置相当于是:
|
||||||
|
|
||||||
```yaml
|
```csv
|
||||||
- branch
|
branch,commit_msg,commit_time
|
||||||
- commit_msg
|
|
||||||
```
|
```
|
||||||
为了创建自己的信息项,命名一个目录为`extra_info_items`。
|
为了创建自己的信息项,命名一个目录为`extra_info_items`。
|
||||||
在`$XDG_CONFIG_HOME/gita/extra_repo_info.py`中,要把信息项的名字作为字符串映射到方法中,该方法将库的路径作为输入参数。举个栗子:
|
在`$XDG_CONFIG_HOME/gita/extra_repo_info.py`中,要把信息项的名字作为字符串映射到方法中,该方法将库的路径作为输入参数。举个栗子:
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 220 KiB After Width: | Height: | Size: 257 KiB |
235
gita/__main__.py
235
gita/__main__.py
|
@ -16,22 +16,52 @@ https://github.com/nosarthur/gita/blob/master/.gita-completion.bash
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import yaml
|
import csv
|
||||||
import argparse
|
import argparse
|
||||||
import subprocess
|
import subprocess
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import glob
|
||||||
|
|
||||||
from . import utils, info, common
|
from . import utils, info, common
|
||||||
|
|
||||||
|
|
||||||
|
def _group_name(name: str) -> str:
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
repos = utils.get_repos()
|
||||||
|
if name in repos:
|
||||||
|
print(f"Cannot use group name {name} since it's a repo name.")
|
||||||
|
sys.exit(1)
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
def f_add(args: argparse.Namespace):
|
def f_add(args: argparse.Namespace):
|
||||||
repos = utils.get_repos()
|
repos = utils.get_repos()
|
||||||
paths = args.paths
|
paths = args.paths
|
||||||
if args.recursive:
|
if args.main:
|
||||||
paths = chain.from_iterable(Path(p).glob('**') for p in args.paths)
|
# add to global and tag as main
|
||||||
utils.add_repos(repos, paths)
|
main_repos = utils.add_repos(repos, paths, repo_type='m')
|
||||||
|
# add sub-repo recursively and save to local config
|
||||||
|
for name, prop in main_repos.items():
|
||||||
|
main_path = prop['path']
|
||||||
|
print('Inside main repo:', name)
|
||||||
|
#sub_paths = Path(main_path).glob('**')
|
||||||
|
sub_paths = glob.glob(os.path.join(main_path,'**/'), recursive=True)
|
||||||
|
utils.add_repos({}, sub_paths, root=main_path)
|
||||||
|
else:
|
||||||
|
if args.recursive or args.auto_group:
|
||||||
|
paths = chain.from_iterable(
|
||||||
|
glob.glob(os.path.join(p, '**/'), recursive=True)
|
||||||
|
for p in args.paths)
|
||||||
|
new_repos = utils.add_repos(repos, paths, is_bare=args.bare)
|
||||||
|
if args.auto_group:
|
||||||
|
new_groups = utils.auto_group(new_repos, args.paths)
|
||||||
|
if new_groups:
|
||||||
|
print(f'Created {len(new_groups)} new group(s).')
|
||||||
|
utils.write_to_groups_file(new_groups, 'a+')
|
||||||
|
|
||||||
|
|
||||||
def f_rename(args: argparse.Namespace):
|
def f_rename(args: argparse.Namespace):
|
||||||
|
@ -39,16 +69,32 @@ def f_rename(args: argparse.Namespace):
|
||||||
utils.rename_repo(repos, args.repo[0], args.new_name)
|
utils.rename_repo(repos, args.repo[0], args.new_name)
|
||||||
|
|
||||||
|
|
||||||
|
def f_flags(args: argparse.Namespace):
|
||||||
|
cmd = args.flags_cmd or 'll'
|
||||||
|
repos = utils.get_repos()
|
||||||
|
if cmd == 'll':
|
||||||
|
for r, prop in repos.items():
|
||||||
|
if prop['flags']:
|
||||||
|
print(f"{r}: {prop['flags']}")
|
||||||
|
elif cmd == 'set':
|
||||||
|
# when in memory, flags are List[str], when on disk, they are space
|
||||||
|
# delimited str
|
||||||
|
repos[args.repo]['flags'] = args.flags
|
||||||
|
utils.write_to_repo_file(repos, 'w')
|
||||||
|
|
||||||
|
|
||||||
def f_color(args: argparse.Namespace):
|
def f_color(args: argparse.Namespace):
|
||||||
cmd = args.color_cmd or 'll'
|
cmd = args.color_cmd or 'll'
|
||||||
if cmd == 'll': # pragma: no cover
|
if cmd == 'll': # pragma: no cover
|
||||||
info.show_colors()
|
info.show_colors()
|
||||||
elif cmd == 'set':
|
elif cmd == 'set':
|
||||||
colors = info.get_color_encoding()
|
colors = info.get_color_encoding()
|
||||||
colors[args.situation] = info.Color[args.color].value
|
colors[args.situation] = args.color
|
||||||
yml_config = common.get_config_fname('color.yml')
|
csv_config = common.get_config_fname('color.csv')
|
||||||
with open(yml_config, 'w') as f:
|
with open(csv_config, 'w', newline='') as f:
|
||||||
yaml.dump(colors, f, default_flow_style=None)
|
writer = csv.DictWriter(f, fieldnames=colors)
|
||||||
|
writer.writeheader()
|
||||||
|
writer.writerow(colors)
|
||||||
|
|
||||||
|
|
||||||
def f_info(args: argparse.Namespace):
|
def f_info(args: argparse.Namespace):
|
||||||
|
@ -56,37 +102,53 @@ def f_info(args: argparse.Namespace):
|
||||||
cmd = args.info_cmd or 'll'
|
cmd = args.info_cmd or 'll'
|
||||||
if cmd == 'll':
|
if cmd == 'll':
|
||||||
print('In use:', ','.join(to_display))
|
print('In use:', ','.join(to_display))
|
||||||
unused = set(info.ALL_INFO_ITEMS) - set(to_display)
|
unused = sorted(list(set(info.ALL_INFO_ITEMS) - set(to_display)))
|
||||||
if unused:
|
if unused:
|
||||||
print('Unused:', ' '.join(unused))
|
print('Unused:', ','.join(unused))
|
||||||
return
|
return
|
||||||
if cmd == 'add' and args.info_item not in to_display:
|
if cmd == 'add' and args.info_item not in to_display:
|
||||||
to_display.append(args.info_item)
|
to_display.append(args.info_item)
|
||||||
yml_config = common.get_config_fname('info.yml')
|
csv_config = common.get_config_fname('info.csv')
|
||||||
with open(yml_config, 'w') as f:
|
with open(csv_config, 'w', newline='') as f:
|
||||||
yaml.dump(to_display, f, default_flow_style=None)
|
writer = csv.writer(f)
|
||||||
|
writer.writerow(to_display)
|
||||||
elif cmd == 'rm' and args.info_item in to_display:
|
elif cmd == 'rm' and args.info_item in to_display:
|
||||||
to_display.remove(args.info_item)
|
to_display.remove(args.info_item)
|
||||||
yml_config = common.get_config_fname('info.yml')
|
csv_config = common.get_config_fname('info.csv')
|
||||||
with open(yml_config, 'w') as f:
|
with open(csv_config, 'w', newline='') as f:
|
||||||
yaml.dump(to_display, f, default_flow_style=None)
|
writer = csv.writer(f)
|
||||||
|
writer.writerow(to_display)
|
||||||
|
|
||||||
|
|
||||||
def f_clone(args: argparse.Namespace):
|
def f_clone(args: argparse.Namespace):
|
||||||
path = Path.cwd()
|
path = Path.cwd()
|
||||||
errors = utils.exec_async_tasks(
|
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.fname))
|
||||||
|
else:
|
||||||
|
utils.exec_async_tasks(
|
||||||
utils.run_async(repo_name, path, ['git', 'clone', url])
|
utils.run_async(repo_name, path, ['git', 'clone', url])
|
||||||
for url, repo_name, _ in utils.parse_clone_config(args.fname))
|
for url, repo_name, _ in utils.parse_clone_config(args.fname))
|
||||||
|
|
||||||
|
|
||||||
def f_freeze(_):
|
def f_freeze(_):
|
||||||
repos = utils.get_repos()
|
repos = utils.get_repos()
|
||||||
for name, path in repos.items():
|
seen = {''}
|
||||||
|
for name, prop in repos.items():
|
||||||
|
path = prop['path']
|
||||||
|
# TODO: What do we do with main repos? Maybe give an option to print
|
||||||
|
# their sub-repos too.
|
||||||
url = ''
|
url = ''
|
||||||
cp = subprocess.run(['git', 'remote', '-v'], cwd=path, capture_output=True)
|
cp = subprocess.run(['git', 'remote', '-v'], cwd=path, capture_output=True)
|
||||||
if cp.returncode == 0:
|
lines = cp.stdout.decode('utf-8').split('\n')
|
||||||
url = cp.stdout.decode('utf-8').split('\n')[0].split()[1]
|
if cp.returncode == 0 and len(lines) > 0:
|
||||||
print(f'{url},{name},{path}')
|
parts = lines[0].split()
|
||||||
|
if len(parts)>1:
|
||||||
|
url = parts[1]
|
||||||
|
if url not in seen:
|
||||||
|
seen.add(url)
|
||||||
|
print(f'{url},{name},{path}')
|
||||||
|
|
||||||
|
|
||||||
def f_ll(args: argparse.Namespace):
|
def f_ll(args: argparse.Namespace):
|
||||||
|
@ -107,7 +169,7 @@ def f_ll(args: argparse.Namespace):
|
||||||
def f_ls(args: argparse.Namespace):
|
def f_ls(args: argparse.Namespace):
|
||||||
repos = utils.get_repos()
|
repos = utils.get_repos()
|
||||||
if args.repo: # one repo, show its path
|
if args.repo: # one repo, show its path
|
||||||
print(repos[args.repo])
|
print(repos[args.repo]['path'])
|
||||||
else: # show names of all repos
|
else: # show names of all repos
|
||||||
print(' '.join(repos))
|
print(' '.join(repos))
|
||||||
|
|
||||||
|
@ -128,6 +190,11 @@ def f_group(args: argparse.Namespace):
|
||||||
groups[new_name] = groups[gname]
|
groups[new_name] = groups[gname]
|
||||||
del groups[gname]
|
del groups[gname]
|
||||||
utils.write_to_groups_file(groups, 'w')
|
utils.write_to_groups_file(groups, 'w')
|
||||||
|
# change context
|
||||||
|
ctx = utils.get_context()
|
||||||
|
if ctx and str(ctx.stem) == gname:
|
||||||
|
# ctx.rename(ctx.with_stem(new_name)) # only works in py3.9
|
||||||
|
ctx.rename(ctx.with_name(f'{new_name}.context'))
|
||||||
elif cmd == 'rm':
|
elif cmd == 'rm':
|
||||||
ctx = utils.get_context()
|
ctx = utils.get_context()
|
||||||
for name in args.to_ungroup:
|
for name in args.to_ungroup:
|
||||||
|
@ -178,12 +245,22 @@ def f_rm(args: argparse.Namespace):
|
||||||
"""
|
"""
|
||||||
Unregister repo(s) from gita
|
Unregister repo(s) from gita
|
||||||
"""
|
"""
|
||||||
path_file = common.get_config_fname('repo_path')
|
path_file = common.get_config_fname('repos.csv')
|
||||||
if os.path.isfile(path_file):
|
if os.path.isfile(path_file):
|
||||||
repos = utils.get_repos()
|
repos = utils.get_repos()
|
||||||
|
main_paths = [prop['path'] for prop in repos.values() if prop['type'] == 'm']
|
||||||
|
# TODO: add test case to delete main repo from main repo
|
||||||
|
# only local setting should be affected instead of the global one
|
||||||
for repo in args.repo:
|
for repo in args.repo:
|
||||||
del repos[repo]
|
del repos[repo]
|
||||||
utils.write_to_repo_file(repos, 'w')
|
# If cwd is relative to any main repo, write to local config
|
||||||
|
cwd = os.getcwd()
|
||||||
|
for p in main_paths:
|
||||||
|
if utils.is_relative_to(cwd, p):
|
||||||
|
utils.write_to_repo_file(repos, 'w', p)
|
||||||
|
break
|
||||||
|
else: # global config
|
||||||
|
utils.write_to_repo_file(repos, 'w')
|
||||||
|
|
||||||
|
|
||||||
def f_git_cmd(args: argparse.Namespace):
|
def f_git_cmd(args: argparse.Namespace):
|
||||||
|
@ -205,21 +282,33 @@ def f_git_cmd(args: argparse.Namespace):
|
||||||
for r in groups[k]:
|
for r in groups[k]:
|
||||||
chosen[r] = repos[r]
|
chosen[r] = repos[r]
|
||||||
repos = chosen
|
repos = chosen
|
||||||
cmds = ['git'] + args.cmd
|
per_repo_cmds = []
|
||||||
if len(repos) == 1 or cmds[1] in args.async_blacklist:
|
for prop in repos.values():
|
||||||
for path in repos.values():
|
cmds = args.cmd.copy()
|
||||||
|
if cmds[0] == 'git' and prop['flags']:
|
||||||
|
cmds[1:1] = prop['flags']
|
||||||
|
per_repo_cmds.append(cmds)
|
||||||
|
|
||||||
|
# This async blacklist mechanism is broken if the git command name does
|
||||||
|
# not match with the gita command name.
|
||||||
|
if len(repos) == 1 or args.cmd[1] in args.async_blacklist:
|
||||||
|
for prop, cmds in zip(repos.values(), per_repo_cmds):
|
||||||
|
path = prop['path']
|
||||||
print(path)
|
print(path)
|
||||||
subprocess.run(cmds, cwd=path)
|
subprocess.run(cmds, cwd=path, shell=args.shell)
|
||||||
else: # run concurrent subprocesses
|
else: # run concurrent subprocesses
|
||||||
# Async execution cannot deal with multiple repos' user name/password.
|
# Async execution cannot deal with multiple repos' user name/password.
|
||||||
# Here we shut off any user input in the async execution, and re-run
|
# Here we shut off any user input in the async execution, and re-run
|
||||||
# the failed ones synchronously.
|
# the failed ones synchronously.
|
||||||
errors = utils.exec_async_tasks(
|
errors = utils.exec_async_tasks(
|
||||||
utils.run_async(repo_name, path, cmds) for repo_name, path in repos.items())
|
utils.run_async(repo_name, prop['path'], cmds)
|
||||||
|
for cmds, (repo_name, prop) in zip(per_repo_cmds, repos.items()))
|
||||||
for path in errors:
|
for path in errors:
|
||||||
if path:
|
if path:
|
||||||
print(path)
|
print(path)
|
||||||
subprocess.run(cmds, cwd=path)
|
# FIXME: This is broken, flags are missing. But probably few
|
||||||
|
# people will use `gita flags`
|
||||||
|
subprocess.run(args.cmd, cwd=path)
|
||||||
|
|
||||||
|
|
||||||
def f_shell(args):
|
def f_shell(args):
|
||||||
|
@ -249,10 +338,10 @@ def f_shell(args):
|
||||||
for r in groups[k]:
|
for r in groups[k]:
|
||||||
chosen[r] = repos[r]
|
chosen[r] = repos[r]
|
||||||
repos = chosen
|
repos = chosen
|
||||||
cmds = args.man[i:]
|
cmds = ' '.join(args.man[i:]) # join the shell command into a single string
|
||||||
for name, path in repos.items():
|
for name, prop in repos.items():
|
||||||
# TODO: pull this out as a function
|
# TODO: pull this out as a function
|
||||||
got = subprocess.run(cmds, cwd=path, check=True,
|
got = subprocess.run(cmds, cwd=prop['path'], check=True, shell=True,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.STDOUT)
|
stderr=subprocess.STDOUT)
|
||||||
print(utils.format_output(got.stdout.decode(), name))
|
print(utils.format_output(got.stdout.decode(), name))
|
||||||
|
@ -271,8 +360,9 @@ def f_super(args):
|
||||||
names.append(word)
|
names.append(word)
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
args.cmd = args.man[i:]
|
args.cmd = ['git'] + args.man[i:]
|
||||||
args.repo = names
|
args.repo = names
|
||||||
|
args.shell = False
|
||||||
f_git_cmd(args)
|
f_git_cmd(args)
|
||||||
|
|
||||||
|
|
||||||
|
@ -292,9 +382,17 @@ def main(argv=None):
|
||||||
# bookkeeping sub-commands
|
# bookkeeping sub-commands
|
||||||
p_add = subparsers.add_parser('add', description='add repo(s)',
|
p_add = subparsers.add_parser('add', description='add repo(s)',
|
||||||
help='add repo(s)')
|
help='add repo(s)')
|
||||||
p_add.add_argument('paths', nargs='+', help="repo(s) to add")
|
p_add.add_argument('paths', nargs='+', type=os.path.abspath, help="repo(s) to add")
|
||||||
p_add.add_argument('-r', dest='recursive', action='store_true',
|
xgroup = p_add.add_mutually_exclusive_group()
|
||||||
help="recursively add repo(s) in the given path.")
|
xgroup.add_argument('-r', '--recursive', action='store_true',
|
||||||
|
help="recursively add repo(s) in the given path(s).")
|
||||||
|
xgroup.add_argument('-m', '--main', action='store_true',
|
||||||
|
help="make main repo(s), sub-repos are recursively added.")
|
||||||
|
xgroup.add_argument('-a', '--auto-group', action='store_true',
|
||||||
|
help="recursively add repo(s) in the given path(s) "
|
||||||
|
"and create hierarchical groups based on folder structure.")
|
||||||
|
xgroup.add_argument('-b', '--bare', action='store_true',
|
||||||
|
help="add bare repo(s)")
|
||||||
p_add.set_defaults(func=f_add)
|
p_add.set_defaults(func=f_add)
|
||||||
|
|
||||||
p_rm = subparsers.add_parser('rm', description='remove repo(s)',
|
p_rm = subparsers.add_parser('rm', description='remove repo(s)',
|
||||||
|
@ -305,15 +403,22 @@ def main(argv=None):
|
||||||
help="remove the chosen repo(s)")
|
help="remove the chosen repo(s)")
|
||||||
p_rm.set_defaults(func=f_rm)
|
p_rm.set_defaults(func=f_rm)
|
||||||
|
|
||||||
p_freeze = subparsers.add_parser('freeze', description='print all repo information')
|
p_freeze = subparsers.add_parser('freeze',
|
||||||
|
description='print all repo information',
|
||||||
|
help='print all repo information')
|
||||||
p_freeze.set_defaults(func=f_freeze)
|
p_freeze.set_defaults(func=f_freeze)
|
||||||
|
|
||||||
p_clone = subparsers.add_parser('clone', description='clone repos from config file')
|
p_clone = subparsers.add_parser('clone',
|
||||||
|
description='clone repos from config file',
|
||||||
|
help='clone repos from config file')
|
||||||
p_clone.add_argument('fname',
|
p_clone.add_argument('fname',
|
||||||
help='config file. Its content should be the output of `gita freeze`.')
|
help='config file. Its content should be the output of `gita freeze`.')
|
||||||
|
p_clone.add_argument('-p', '--preserve-path', dest='preserve_path', action='store_true',
|
||||||
|
help="clone repo(s) in their original paths")
|
||||||
p_clone.set_defaults(func=f_clone)
|
p_clone.set_defaults(func=f_clone)
|
||||||
|
|
||||||
p_rename = subparsers.add_parser('rename', description='rename a repo')
|
p_rename = subparsers.add_parser('rename', description='rename a repo',
|
||||||
|
help='rename a repo')
|
||||||
p_rename.add_argument(
|
p_rename.add_argument(
|
||||||
'repo',
|
'repo',
|
||||||
nargs=1,
|
nargs=1,
|
||||||
|
@ -322,8 +427,25 @@ def main(argv=None):
|
||||||
p_rename.add_argument('new_name', help="new name")
|
p_rename.add_argument('new_name', help="new name")
|
||||||
p_rename.set_defaults(func=f_rename)
|
p_rename.set_defaults(func=f_rename)
|
||||||
|
|
||||||
|
p_flags = subparsers.add_parser('flags',
|
||||||
|
description='Set custom git flags for repo.',
|
||||||
|
help='git flags configuration')
|
||||||
|
p_flags.set_defaults(func=f_flags)
|
||||||
|
flags_cmds = p_flags.add_subparsers(dest='flags_cmd',
|
||||||
|
help='additional help with sub-command -h')
|
||||||
|
flags_cmds.add_parser('ll',
|
||||||
|
description='display repos with custom flags')
|
||||||
|
pf_set = flags_cmds.add_parser('set',
|
||||||
|
description='Set flags for repo.')
|
||||||
|
pf_set.add_argument('repo', choices=utils.get_repos(),
|
||||||
|
help="repo name")
|
||||||
|
pf_set.add_argument('flags',
|
||||||
|
nargs=argparse.REMAINDER,
|
||||||
|
help="custom flags, use quotes")
|
||||||
|
|
||||||
p_color = subparsers.add_parser('color',
|
p_color = subparsers.add_parser('color',
|
||||||
description='display and modify branch coloring of the ll sub-command.')
|
description='display and modify branch coloring of the ll sub-command.',
|
||||||
|
help='color configuration')
|
||||||
p_color.set_defaults(func=f_color)
|
p_color.set_defaults(func=f_color)
|
||||||
color_cmds = p_color.add_subparsers(dest='color_cmd',
|
color_cmds = p_color.add_subparsers(dest='color_cmd',
|
||||||
help='additional help with sub-command -h')
|
help='additional help with sub-command -h')
|
||||||
|
@ -339,7 +461,8 @@ def main(argv=None):
|
||||||
help="available colors")
|
help="available colors")
|
||||||
|
|
||||||
p_info = subparsers.add_parser('info',
|
p_info = subparsers.add_parser('info',
|
||||||
description='list, add, or remove information items of the ll sub-command.')
|
description='list, add, or remove information items of the ll sub-command.',
|
||||||
|
help='information setting')
|
||||||
p_info.set_defaults(func=f_info)
|
p_info.set_defaults(func=f_info)
|
||||||
info_cmds = p_info.add_subparsers(dest='info_cmd',
|
info_cmds = p_info.add_subparsers(dest='info_cmd',
|
||||||
help='additional help with sub-command -h')
|
help='additional help with sub-command -h')
|
||||||
|
@ -347,11 +470,11 @@ def main(argv=None):
|
||||||
description='show used and unused information items of the ll sub-command')
|
description='show used and unused information items of the ll sub-command')
|
||||||
info_cmds.add_parser('add', description='Enable information item.'
|
info_cmds.add_parser('add', description='Enable information item.'
|
||||||
).add_argument('info_item',
|
).add_argument('info_item',
|
||||||
choices=('branch', 'commit_msg', 'path'),
|
choices=info.ALL_INFO_ITEMS,
|
||||||
help="information item to add")
|
help="information item to add")
|
||||||
info_cmds.add_parser('rm', description='Disable information item.'
|
info_cmds.add_parser('rm', description='Disable information item.'
|
||||||
).add_argument('info_item',
|
).add_argument('info_item',
|
||||||
choices=('branch', 'commit_msg', 'path'),
|
choices=info.ALL_INFO_ITEMS,
|
||||||
help="information item to delete")
|
help="information item to delete")
|
||||||
|
|
||||||
|
|
||||||
|
@ -379,6 +502,7 @@ def main(argv=None):
|
||||||
p_ll.set_defaults(func=f_ll)
|
p_ll.set_defaults(func=f_ll)
|
||||||
|
|
||||||
p_context = subparsers.add_parser('context',
|
p_context = subparsers.add_parser('context',
|
||||||
|
help='set context',
|
||||||
description='Set and remove context. A context is a group.'
|
description='Set and remove context. A context is a group.'
|
||||||
' When set, all operations apply only to repos in that group.')
|
' When set, all operations apply only to repos in that group.')
|
||||||
p_context.add_argument('choice',
|
p_context.add_argument('choice',
|
||||||
|
@ -388,7 +512,8 @@ def main(argv=None):
|
||||||
p_context.set_defaults(func=f_context)
|
p_context.set_defaults(func=f_context)
|
||||||
|
|
||||||
p_ls = subparsers.add_parser(
|
p_ls = subparsers.add_parser(
|
||||||
'ls', description='display names of all repos, or path of a chosen repo')
|
'ls', help='show repo(s) or repo path',
|
||||||
|
description='display names of all repos, or path of a chosen repo')
|
||||||
p_ls.add_argument('repo',
|
p_ls.add_argument('repo',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
choices=utils.get_repos(),
|
choices=utils.get_repos(),
|
||||||
|
@ -396,7 +521,8 @@ def main(argv=None):
|
||||||
p_ls.set_defaults(func=f_ls)
|
p_ls.set_defaults(func=f_ls)
|
||||||
|
|
||||||
p_group = subparsers.add_parser(
|
p_group = subparsers.add_parser(
|
||||||
'group', description='list, add, or remove repo group(s)')
|
'group', description='list, add, or remove repo group(s)',
|
||||||
|
help='group repos')
|
||||||
p_group.set_defaults(func=f_group)
|
p_group.set_defaults(func=f_group)
|
||||||
group_cmds = p_group.add_subparsers(dest='group_cmd',
|
group_cmds = p_group.add_subparsers(dest='group_cmd',
|
||||||
help='additional help with sub-command -h')
|
help='additional help with sub-command -h')
|
||||||
|
@ -410,6 +536,7 @@ def main(argv=None):
|
||||||
help="repo(s) to be grouped")
|
help="repo(s) to be grouped")
|
||||||
pg_add.add_argument('-n', '--name',
|
pg_add.add_argument('-n', '--name',
|
||||||
dest='gname',
|
dest='gname',
|
||||||
|
type=_group_name,
|
||||||
metavar='group-name',
|
metavar='group-name',
|
||||||
required=True,
|
required=True,
|
||||||
help="group name")
|
help="group name")
|
||||||
|
@ -429,6 +556,7 @@ def main(argv=None):
|
||||||
choices=utils.get_groups(),
|
choices=utils.get_groups(),
|
||||||
help="existing group to rename")
|
help="existing group to rename")
|
||||||
pg_rename.add_argument('new_name', metavar='new-name',
|
pg_rename.add_argument('new_name', metavar='new-name',
|
||||||
|
type=_group_name,
|
||||||
help="new group name")
|
help="new group name")
|
||||||
group_cmds.add_parser('rm',
|
group_cmds.add_parser('rm',
|
||||||
description='Remove group(s).').add_argument('to_ungroup',
|
description='Remove group(s).').add_argument('to_ungroup',
|
||||||
|
@ -439,6 +567,7 @@ def main(argv=None):
|
||||||
# superman mode
|
# superman mode
|
||||||
p_super = subparsers.add_parser(
|
p_super = subparsers.add_parser(
|
||||||
'super',
|
'super',
|
||||||
|
help='run any git command/alias',
|
||||||
description='Superman mode: delegate any git command/alias in specified or '
|
description='Superman mode: delegate any git command/alias in specified or '
|
||||||
'all repo(s).\n'
|
'all repo(s).\n'
|
||||||
'Examples:\n \t gita super myrepo1 commit -am "fix a bug"\n'
|
'Examples:\n \t gita super myrepo1 commit -am "fix a bug"\n'
|
||||||
|
@ -446,7 +575,7 @@ def main(argv=None):
|
||||||
p_super.add_argument(
|
p_super.add_argument(
|
||||||
'man',
|
'man',
|
||||||
nargs=argparse.REMAINDER,
|
nargs=argparse.REMAINDER,
|
||||||
help="execute arbitrary git command/alias for specified or all repos "
|
help="execute arbitrary git command/alias for specified or all repos\n"
|
||||||
"Example: gita super myrepo1 diff --name-only --staged "
|
"Example: gita super myrepo1 diff --name-only --staged "
|
||||||
"Another: gita super checkout master ")
|
"Another: gita super checkout master ")
|
||||||
p_super.set_defaults(func=f_super)
|
p_super.set_defaults(func=f_super)
|
||||||
|
@ -454,6 +583,7 @@ def main(argv=None):
|
||||||
# shell mode
|
# shell mode
|
||||||
p_shell = subparsers.add_parser(
|
p_shell = subparsers.add_parser(
|
||||||
'shell',
|
'shell',
|
||||||
|
help='run any shell command',
|
||||||
description='shell mode: delegate any shell command in specified or '
|
description='shell mode: delegate any shell command in specified or '
|
||||||
'all repo(s).\n'
|
'all repo(s).\n'
|
||||||
'Examples:\n \t gita shell pwd\n'
|
'Examples:\n \t gita shell pwd\n'
|
||||||
|
@ -470,7 +600,7 @@ 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')
|
||||||
cmd = data.get('cmd') or name
|
cmd = data['cmd']
|
||||||
if data.get('allow_all'):
|
if data.get('allow_all'):
|
||||||
choices = utils.get_choices()
|
choices = utils.get_choices()
|
||||||
nargs = '*'
|
nargs = '*'
|
||||||
|
@ -481,7 +611,14 @@ def main(argv=None):
|
||||||
help += ' for the chosen repo(s) or group(s)'
|
help += ' for the chosen repo(s) or group(s)'
|
||||||
sp = subparsers.add_parser(name, description=help)
|
sp = subparsers.add_parser(name, description=help)
|
||||||
sp.add_argument('repo', nargs=nargs, choices=choices, help=help)
|
sp.add_argument('repo', nargs=nargs, choices=choices, help=help)
|
||||||
sp.set_defaults(func=f_git_cmd, cmd=cmd.split())
|
is_shell = bool(data.get('shell'))
|
||||||
|
sp.add_argument('-s', '--shell', default=is_shell, type=bool,
|
||||||
|
help='If set, run in shell mode')
|
||||||
|
if is_shell:
|
||||||
|
cmd = [cmd]
|
||||||
|
else:
|
||||||
|
cmd = cmd.split()
|
||||||
|
sp.set_defaults(func=f_git_cmd, cmd=cmd)
|
||||||
|
|
||||||
args = p.parse_args(argv)
|
args = p.parse_args(argv)
|
||||||
|
|
||||||
|
|
89
gita/cmds.json
Normal file
89
gita/cmds.json
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
{
|
||||||
|
"br":{
|
||||||
|
"cmd": "git branch -vv",
|
||||||
|
"help":"show local branches"},
|
||||||
|
"clean":{
|
||||||
|
"cmd": "git clean -dfx",
|
||||||
|
"help": "remove all untracked files/folders"},
|
||||||
|
"diff":{
|
||||||
|
"cmd": "git diff",
|
||||||
|
"help": "git show differences"},
|
||||||
|
"difftool":{
|
||||||
|
"cmd": "git difftool",
|
||||||
|
"disable_async": true,
|
||||||
|
"help": "show differences using a tool"
|
||||||
|
},
|
||||||
|
"fetch":{
|
||||||
|
"cmd": "git fetch",
|
||||||
|
"allow_all": true,
|
||||||
|
"help": "fetch remote update"
|
||||||
|
},
|
||||||
|
"last":{
|
||||||
|
"cmd": "git log -1 HEAD",
|
||||||
|
"help": "show log information of HEAD"
|
||||||
|
},
|
||||||
|
"log":
|
||||||
|
{"cmd": "git log",
|
||||||
|
"disable_async": true,
|
||||||
|
"help": "show logs"
|
||||||
|
},
|
||||||
|
"merge":{
|
||||||
|
"cmd": "git merge @{u}",
|
||||||
|
"help": "merge remote updates"
|
||||||
|
},
|
||||||
|
"mergetool":{
|
||||||
|
"cmd": "git mergetool",
|
||||||
|
"disable_async": true,
|
||||||
|
"help": "merge updates with a tool"
|
||||||
|
},
|
||||||
|
"patch":{
|
||||||
|
"cmd": "git format-patch HEAD~",
|
||||||
|
"help": "make a patch"
|
||||||
|
},
|
||||||
|
"pull":{
|
||||||
|
"cmd": "git pull",
|
||||||
|
"allow_all": true,
|
||||||
|
"help": "pull remote updates"
|
||||||
|
},
|
||||||
|
"push":{
|
||||||
|
"cmd": "git push",
|
||||||
|
"help": "push the local updates"
|
||||||
|
},
|
||||||
|
"rebase":{
|
||||||
|
"cmd": "git rebase",
|
||||||
|
"help": "rebase from master"
|
||||||
|
},
|
||||||
|
"reflog":{
|
||||||
|
"cmd": "git reflog",
|
||||||
|
"help": "show ref logs"
|
||||||
|
},
|
||||||
|
"remote":{
|
||||||
|
"cmd": "git remote -v",
|
||||||
|
"help": "show remote settings"
|
||||||
|
},
|
||||||
|
"reset":{
|
||||||
|
"cmd": "git reset",
|
||||||
|
"help": "reset repo(s)"
|
||||||
|
},
|
||||||
|
"show":{
|
||||||
|
"cmd": "git show",
|
||||||
|
"disable_async": true,
|
||||||
|
"help": "show detailed commit information"
|
||||||
|
},
|
||||||
|
"stash":{
|
||||||
|
"cmd": "git stash",
|
||||||
|
"help": "store uncommited changes"
|
||||||
|
},
|
||||||
|
"stat":{
|
||||||
|
"cmd": "git diff --stat",
|
||||||
|
"help": "show edit statistics"
|
||||||
|
},
|
||||||
|
"st":{
|
||||||
|
"cmd": "git status",
|
||||||
|
"help": "show status"
|
||||||
|
},
|
||||||
|
"tag":{
|
||||||
|
"cmd": "git tag -n",
|
||||||
|
"help": "show tags"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,65 +0,0 @@
|
||||||
br:
|
|
||||||
cmd: branch -vv
|
|
||||||
help: show local branches
|
|
||||||
clean:
|
|
||||||
cmd: clean -dfx
|
|
||||||
help: remove all untracked files/folders
|
|
||||||
diff:
|
|
||||||
help: show differences
|
|
||||||
difftool:
|
|
||||||
disable_async: true
|
|
||||||
help: show differences using a tool
|
|
||||||
fetch:
|
|
||||||
allow_all: true
|
|
||||||
help: fetch remote update
|
|
||||||
last:
|
|
||||||
cmd: log -1 HEAD
|
|
||||||
help: show log information of HEAD
|
|
||||||
log:
|
|
||||||
disable_async: true
|
|
||||||
help: show logs
|
|
||||||
merge:
|
|
||||||
cmd: merge @{u}
|
|
||||||
help: merge remote updates
|
|
||||||
mergetool:
|
|
||||||
disable_async: true
|
|
||||||
help: merge updates with a tool
|
|
||||||
patch:
|
|
||||||
cmd: format-patch HEAD~
|
|
||||||
help: make a patch
|
|
||||||
pull:
|
|
||||||
allow_all: true
|
|
||||||
help: pull remote updates
|
|
||||||
push:
|
|
||||||
help: push the local updates
|
|
||||||
rebase:
|
|
||||||
help: rebase from master
|
|
||||||
reflog:
|
|
||||||
help: show ref logs
|
|
||||||
remote:
|
|
||||||
cmd: remote -v
|
|
||||||
help: show remote settings
|
|
||||||
reset:
|
|
||||||
help: reset repo(s)
|
|
||||||
shortlog:
|
|
||||||
disable_async: true
|
|
||||||
help: show short log
|
|
||||||
show:
|
|
||||||
disable_async: true
|
|
||||||
help: show detailed commit information
|
|
||||||
show-branch:
|
|
||||||
disable_async: true
|
|
||||||
help: show detailed branch information
|
|
||||||
stash:
|
|
||||||
help: store uncommited changes
|
|
||||||
stat:
|
|
||||||
cmd: diff --stat
|
|
||||||
help: show edit statistics
|
|
||||||
st:
|
|
||||||
help: show status
|
|
||||||
tag:
|
|
||||||
cmd: tag -n
|
|
||||||
help: show tags
|
|
||||||
whatchanged:
|
|
||||||
disable_async: true
|
|
||||||
help: show detailed log
|
|
|
@ -1,16 +1,17 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
def get_config_dir() -> str:
|
def get_config_dir(root=None) -> str:
|
||||||
parent = os.environ.get('XDG_CONFIG_HOME') or os.path.join(
|
if root is None:
|
||||||
os.path.expanduser('~'), '.config')
|
root = os.environ.get('XDG_CONFIG_HOME') or os.path.join(
|
||||||
root = os.path.join(parent, "gita")
|
os.path.expanduser('~'), '.config')
|
||||||
return root
|
return os.path.join(root, "gita")
|
||||||
|
else:
|
||||||
|
return os.path.join(root, ".gita")
|
||||||
|
|
||||||
|
|
||||||
def get_config_fname(fname: str) -> str:
|
def get_config_fname(fname: str, root=None) -> str:
|
||||||
"""
|
"""
|
||||||
Return the file name that stores the repo locations.
|
Return the file name that stores the repo locations.
|
||||||
"""
|
"""
|
||||||
root = get_config_dir()
|
return os.path.join(get_config_dir(root), fname)
|
||||||
return os.path.join(root, fname)
|
|
||||||
|
|
103
gita/info.py
103
gita/info.py
|
@ -1,5 +1,5 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import csv
|
||||||
import yaml
|
import yaml
|
||||||
import subprocess
|
import subprocess
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
@ -31,40 +31,42 @@ class Color(str, Enum):
|
||||||
b_purple = '\x1b[35;1m'
|
b_purple = '\x1b[35;1m'
|
||||||
b_cyan = '\x1b[36;1m'
|
b_cyan = '\x1b[36;1m'
|
||||||
b_white = '\x1b[37;1m'
|
b_white = '\x1b[37;1m'
|
||||||
|
underline = '\x1B[4m'
|
||||||
|
|
||||||
|
|
||||||
def show_colors(): # pragma: no cover
|
def show_colors(): # pragma: no cover
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
names = {c.value: c.name for c in Color}
|
|
||||||
for i, c in enumerate(Color, start=1):
|
for i, c in enumerate(Color, start=1):
|
||||||
if c != Color.end:
|
if c != Color.end and c != Color.underline:
|
||||||
print(f'{c.value}{c.name:<8} ', end='')
|
print(f'{c.value}{c.name:<8} ', end='')
|
||||||
if i % 9 == 0:
|
if i % 9 == 0:
|
||||||
print()
|
print()
|
||||||
print(f'{Color.end}')
|
print(f'{Color.end}')
|
||||||
for situation, c in sorted(get_color_encoding().items()):
|
for situation, c in sorted(get_color_encoding().items()):
|
||||||
print(f'{situation:<12}: {c}{names[c]:<8}{Color.end} ')
|
print(f'{situation:<12}: {Color[c].value}{c:<8}{Color.end} ')
|
||||||
|
|
||||||
|
|
||||||
@lru_cache()
|
@lru_cache()
|
||||||
def get_color_encoding() -> Dict[str, str]:
|
def get_color_encoding() -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Return color scheme for different local/remote situations.
|
Return color scheme for different local/remote situations.
|
||||||
|
In the format of {situation: color name}
|
||||||
"""
|
"""
|
||||||
# custom settings
|
# custom settings
|
||||||
yml_config = Path(common.get_config_fname('color.yml'))
|
csv_config = Path(common.get_config_fname('color.csv'))
|
||||||
if yml_config.is_file():
|
if csv_config.is_file():
|
||||||
with open(yml_config, 'r') as stream:
|
with open(csv_config, 'r') as f:
|
||||||
colors = yaml.load(stream, Loader=yaml.FullLoader)
|
reader = csv.DictReader(f)
|
||||||
|
colors = next(reader)
|
||||||
else:
|
else:
|
||||||
colors = {
|
colors = {
|
||||||
'no-remote': Color.white.value,
|
'no-remote': Color.white.name,
|
||||||
'in-sync': Color.green.value,
|
'in-sync': Color.green.name,
|
||||||
'diverged': Color.red.value,
|
'diverged': Color.red.name,
|
||||||
'local-ahead': Color.purple.value,
|
'local-ahead': Color.purple.name,
|
||||||
'remote-ahead': Color.yellow.value,
|
'remote-ahead': Color.yellow.name,
|
||||||
}
|
}
|
||||||
return colors
|
return colors
|
||||||
|
|
||||||
|
@ -80,6 +82,7 @@ def get_info_funcs() -> List[Callable[[str], str]]:
|
||||||
all_info_items = {
|
all_info_items = {
|
||||||
'branch': get_repo_status,
|
'branch': get_repo_status,
|
||||||
'commit_msg': get_commit_msg,
|
'commit_msg': get_commit_msg,
|
||||||
|
'commit_time': get_commit_time,
|
||||||
'path': get_path,
|
'path': get_path,
|
||||||
}
|
}
|
||||||
return [all_info_items[k] for k in to_display]
|
return [all_info_items[k] for k in to_display]
|
||||||
|
@ -90,23 +93,26 @@ def get_info_items() -> List[str]:
|
||||||
Return the information items to be displayed in the `gita ll` command.
|
Return the information items to be displayed in the `gita ll` command.
|
||||||
"""
|
"""
|
||||||
# custom settings
|
# custom settings
|
||||||
yml_config = Path(common.get_config_fname('info.yml'))
|
csv_config = Path(common.get_config_fname('info.csv'))
|
||||||
if yml_config.is_file():
|
if csv_config.is_file():
|
||||||
with open(yml_config, 'r') as stream:
|
with open(csv_config, 'r') as f:
|
||||||
display_items = yaml.load(stream, Loader=yaml.FullLoader)
|
reader = csv.reader(f)
|
||||||
|
display_items = next(reader)
|
||||||
display_items = [x for x in display_items if x in ALL_INFO_ITEMS]
|
display_items = [x for x in display_items if x in ALL_INFO_ITEMS]
|
||||||
else:
|
else:
|
||||||
# default settings
|
# default settings
|
||||||
display_items = ['branch', 'commit_msg']
|
display_items = ['branch', 'commit_msg', 'commit_time']
|
||||||
return display_items
|
return display_items
|
||||||
|
|
||||||
|
|
||||||
def get_path(path):
|
def get_path(prop: Dict[str, str]) -> str:
|
||||||
return f'{Color.cyan}{path}{Color.end}'
|
return f'{Color.cyan}{prop["path"]}{Color.end}'
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: do we need to add the flags here too?
|
||||||
def get_head(path: str) -> str:
|
def get_head(path: str) -> str:
|
||||||
result = subprocess.run('git rev-parse --abbrev-ref HEAD'.split(),
|
result = subprocess.run('git symbolic-ref -q --short HEAD || git describe --tags --exact-match',
|
||||||
|
shell=True,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
universal_newlines=True,
|
universal_newlines=True,
|
||||||
|
@ -114,12 +120,12 @@ def get_head(path: str) -> str:
|
||||||
return result.stdout.strip()
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
def run_quiet_diff(args: List[str]) -> bool:
|
def run_quiet_diff(flags: List[str], args: List[str]) -> int:
|
||||||
"""
|
"""
|
||||||
Return the return code of git diff `args` in quiet mode
|
Return the return code of git diff `args` in quiet mode
|
||||||
"""
|
"""
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['git', 'diff', '--quiet'] + args,
|
['git'] + flags + ['diff', '--quiet'] + args,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
return result.returncode
|
return result.returncode
|
||||||
|
@ -135,50 +141,68 @@ def get_common_commit() -> str:
|
||||||
return result.stdout.strip()
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
def has_untracked() -> bool:
|
def has_untracked(flags: List[str]) -> bool:
|
||||||
"""
|
"""
|
||||||
Return True if untracked file/folder exists
|
Return True if untracked file/folder exists
|
||||||
"""
|
"""
|
||||||
result = subprocess.run('git ls-files -zo --exclude-standard'.split(),
|
cmd = ['git'] + flags + 'ls-files -zo --exclude-standard'.split()
|
||||||
|
result = subprocess.run(cmd,
|
||||||
stdout=subprocess.PIPE)
|
stdout=subprocess.PIPE)
|
||||||
return bool(result.stdout)
|
return bool(result.stdout)
|
||||||
|
|
||||||
|
|
||||||
def get_commit_msg(path: str) -> str:
|
def get_commit_msg(prop: Dict[str, str]) -> str:
|
||||||
"""
|
"""
|
||||||
Return the last commit message.
|
Return the last commit message.
|
||||||
"""
|
"""
|
||||||
# `git show-branch --no-name HEAD` is faster than `git show -s --format=%s`
|
# `git show-branch --no-name HEAD` is faster than `git show -s --format=%s`
|
||||||
result = subprocess.run('git show-branch --no-name HEAD'.split(),
|
cmd = ['git'] + prop['flags'] + 'show-branch --no-name HEAD'.split()
|
||||||
|
result = subprocess.run(cmd,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
universal_newlines=True,
|
universal_newlines=True,
|
||||||
cwd=path)
|
cwd=prop['path'])
|
||||||
return result.stdout.strip()
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
def get_repo_status(path: str, no_colors=False) -> str:
|
def get_commit_time(prop: Dict[str, str]) -> str:
|
||||||
head = get_head(path)
|
"""
|
||||||
dirty, staged, untracked, color = _get_repo_status(path, no_colors)
|
Return the last commit time in parenthesis.
|
||||||
|
"""
|
||||||
|
cmd = ['git'] + prop['flags'] + 'log -1 --format=%cd --date=relative'.split()
|
||||||
|
result = subprocess.run(cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
universal_newlines=True,
|
||||||
|
cwd=prop['path'])
|
||||||
|
return f"({result.stdout.strip()})"
|
||||||
|
|
||||||
|
|
||||||
|
def get_repo_status(prop: Dict[str, str], no_colors=False) -> str:
|
||||||
|
head = get_head(prop['path'])
|
||||||
|
dirty, staged, untracked, color = _get_repo_status(prop, no_colors)
|
||||||
if color:
|
if color:
|
||||||
return f'{color}{head+" "+dirty+staged+untracked:<10}{Color.end}'
|
return f'{color}{head+" "+dirty+staged+untracked:<10}{Color.end}'
|
||||||
return f'{head+" "+dirty+staged+untracked:<10}'
|
return f'{head+" "+dirty+staged+untracked:<10}'
|
||||||
|
|
||||||
|
|
||||||
def _get_repo_status(path: str, no_colors: bool) -> Tuple[str]:
|
def _get_repo_status(prop: Dict[str, str], no_colors: bool) -> Tuple[str]:
|
||||||
"""
|
"""
|
||||||
Return the status of one repo
|
Return the status of one repo
|
||||||
"""
|
"""
|
||||||
|
path = prop['path']
|
||||||
|
flags = prop['flags']
|
||||||
os.chdir(path)
|
os.chdir(path)
|
||||||
dirty = '*' if run_quiet_diff([]) else ''
|
dirty = '*' if run_quiet_diff(flags, []) else ''
|
||||||
staged = '+' if run_quiet_diff(['--cached']) else ''
|
staged = '+' if run_quiet_diff(flags, ['--cached']) else ''
|
||||||
untracked = '_' if has_untracked() else ''
|
untracked = '_' if has_untracked(flags) else ''
|
||||||
|
|
||||||
if no_colors:
|
if no_colors:
|
||||||
return dirty, staged, untracked, ''
|
return dirty, staged, untracked, ''
|
||||||
|
|
||||||
colors = get_color_encoding()
|
colors = {situ: Color[name].value
|
||||||
diff_returncode = run_quiet_diff(['@{u}', '@{0}'])
|
for situ, name in get_color_encoding().items()}
|
||||||
|
diff_returncode = run_quiet_diff(flags, ['@{u}', '@{0}'])
|
||||||
has_no_remote = diff_returncode == 128
|
has_no_remote = diff_returncode == 128
|
||||||
has_no_diff = diff_returncode == 0
|
has_no_diff = diff_returncode == 0
|
||||||
if has_no_remote:
|
if has_no_remote:
|
||||||
|
@ -187,9 +211,9 @@ def _get_repo_status(path: str, no_colors: bool) -> Tuple[str]:
|
||||||
color = colors['in-sync']
|
color = colors['in-sync']
|
||||||
else:
|
else:
|
||||||
common_commit = get_common_commit()
|
common_commit = get_common_commit()
|
||||||
outdated = run_quiet_diff(['@{u}', common_commit])
|
outdated = run_quiet_diff(flags, ['@{u}', common_commit])
|
||||||
if outdated:
|
if outdated:
|
||||||
diverged = run_quiet_diff(['@{0}', common_commit])
|
diverged = run_quiet_diff(flags, ['@{0}', common_commit])
|
||||||
color = colors['diverged'] if diverged else colors['remote-ahead']
|
color = colors['diverged'] if diverged else colors['remote-ahead']
|
||||||
else: # local is ahead of remote
|
else: # local is ahead of remote
|
||||||
color = colors['local-ahead']
|
color = colors['local-ahead']
|
||||||
|
@ -199,5 +223,6 @@ def _get_repo_status(path: str, no_colors: bool) -> Tuple[str]:
|
||||||
ALL_INFO_ITEMS = {
|
ALL_INFO_ITEMS = {
|
||||||
'branch': get_repo_status,
|
'branch': get_repo_status,
|
||||||
'commit_msg': get_commit_msg,
|
'commit_msg': get_commit_msg,
|
||||||
|
'commit_time': get_commit_time,
|
||||||
'path': get_path,
|
'path': get_path,
|
||||||
}
|
}
|
||||||
|
|
259
gita/utils.py
259
gita/utils.py
|
@ -1,15 +1,53 @@
|
||||||
import os
|
import os
|
||||||
import yaml
|
import json
|
||||||
|
import csv
|
||||||
import asyncio
|
import asyncio
|
||||||
import platform
|
import platform
|
||||||
|
import subprocess
|
||||||
from functools import lru_cache, partial
|
from functools import lru_cache, partial
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Dict, Coroutine, Union, Iterator
|
from typing import List, Dict, Coroutine, Union, Iterator, Tuple
|
||||||
|
from collections import Counter, defaultdict
|
||||||
|
|
||||||
from . import info
|
from . import info
|
||||||
from . import common
|
from . import common
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: python3.9 pathlib has is_relative_to() function
|
||||||
|
def is_relative_to(kid: str, parent: str) -> bool:
|
||||||
|
"""
|
||||||
|
Both the `kid` and `parent` should be absolute path
|
||||||
|
"""
|
||||||
|
return parent == os.path.commonpath((kid, parent))
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache()
|
||||||
|
def get_repos(root=None) -> Dict[str, Dict[str, str]]:
|
||||||
|
"""
|
||||||
|
Return a `dict` of repo name to repo absolute path and repo type
|
||||||
|
|
||||||
|
@param root: Use local config if set. If None, use either global or local
|
||||||
|
config depending on cwd.
|
||||||
|
"""
|
||||||
|
path_file = common.get_config_fname('repos.csv', root)
|
||||||
|
repos = {}
|
||||||
|
if os.path.isfile(path_file) and os.stat(path_file).st_size > 0:
|
||||||
|
with open(path_file) as f:
|
||||||
|
rows = csv.DictReader(f, ['path', 'name', 'type', 'flags'],
|
||||||
|
restval='') # it's actually a reader
|
||||||
|
repos = {r['name']:
|
||||||
|
{'path': r['path'], 'type': r['type'],
|
||||||
|
'flags': r['flags'].split()}
|
||||||
|
for r in rows if is_git(r['path'], is_bare=True)}
|
||||||
|
if root is None: # detect if inside a main path
|
||||||
|
cwd = os.getcwd()
|
||||||
|
for prop in repos.values():
|
||||||
|
path = prop['path']
|
||||||
|
if prop['type'] == 'm' and is_relative_to(cwd, path):
|
||||||
|
return get_repos(path)
|
||||||
|
return repos
|
||||||
|
|
||||||
|
|
||||||
@lru_cache()
|
@lru_cache()
|
||||||
def get_context() -> Union[Path, None]:
|
def get_context() -> Union[Path, None]:
|
||||||
"""
|
"""
|
||||||
|
@ -21,42 +59,18 @@ def get_context() -> Union[Path, None]:
|
||||||
return matches[0] if matches else None
|
return matches[0] if matches else None
|
||||||
|
|
||||||
|
|
||||||
@lru_cache()
|
|
||||||
def get_repos() -> Dict[str, str]:
|
|
||||||
"""
|
|
||||||
Return a `dict` of repo name to repo absolute path
|
|
||||||
"""
|
|
||||||
path_file = common.get_config_fname('repo_path')
|
|
||||||
repos = {}
|
|
||||||
# Each line is a repo path and repo name separated by ,
|
|
||||||
if os.path.isfile(path_file) and os.stat(path_file).st_size > 0:
|
|
||||||
with open(path_file) as f:
|
|
||||||
for line in f:
|
|
||||||
line = line.rstrip()
|
|
||||||
if not line: # blank line
|
|
||||||
continue
|
|
||||||
path, name = line.split(',')
|
|
||||||
if not is_git(path):
|
|
||||||
continue
|
|
||||||
if name not in repos:
|
|
||||||
repos[name] = path
|
|
||||||
else: # repo name collision for different paths: include parent path name
|
|
||||||
par_name = os.path.basename(os.path.dirname(path))
|
|
||||||
repos[os.path.join(par_name, name)] = path
|
|
||||||
return repos
|
|
||||||
|
|
||||||
|
|
||||||
@lru_cache()
|
@lru_cache()
|
||||||
def get_groups() -> Dict[str, List[str]]:
|
def get_groups() -> Dict[str, List[str]]:
|
||||||
"""
|
"""
|
||||||
Return a `dict` of group name to repo names.
|
Return a `dict` of group name to repo names.
|
||||||
"""
|
"""
|
||||||
fname = common.get_config_fname('groups.yml')
|
fname = common.get_config_fname('groups.csv')
|
||||||
groups = {}
|
groups = {}
|
||||||
# Each line is a repo path and repo name separated by ,
|
# Each line is a repo path and repo name separated by ,
|
||||||
if os.path.isfile(fname) and os.stat(fname).st_size > 0:
|
if os.path.isfile(fname) and os.stat(fname).st_size > 0:
|
||||||
with open(fname, 'r') as f:
|
with open(fname, 'r') as f:
|
||||||
groups = yaml.load(f, Loader=yaml.FullLoader)
|
rows = csv.reader(f, delimiter=':')
|
||||||
|
groups = {r[0]: r[1].split() for r in rows}
|
||||||
return groups
|
return groups
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,10 +89,12 @@ def get_choices() -> List[Union[str, None]]:
|
||||||
return choices
|
return choices
|
||||||
|
|
||||||
|
|
||||||
def is_git(path: str) -> bool:
|
def is_git(path: str, is_bare=False) -> bool:
|
||||||
"""
|
"""
|
||||||
Return True if the path is a git repo.
|
Return True if the path is a git repo.
|
||||||
"""
|
"""
|
||||||
|
if not os.path.exists(path):
|
||||||
|
return False
|
||||||
# An alternative is to call `git rev-parse --is-inside-work-tree`
|
# An alternative is to call `git rev-parse --is-inside-work-tree`
|
||||||
# I don't see why that one is better yet.
|
# I don't see why that one is better yet.
|
||||||
# For a regular git repo, .git is a folder, for a worktree repo, .git is a file.
|
# For a regular git repo, .git is a folder, for a worktree repo, .git is a file.
|
||||||
|
@ -88,59 +104,172 @@ def is_git(path: str) -> bool:
|
||||||
# `git rev-parse --git-common-dir`
|
# `git rev-parse --git-common-dir`
|
||||||
loc = os.path.join(path, '.git')
|
loc = os.path.join(path, '.git')
|
||||||
# TODO: we can display the worktree repos in a different font.
|
# TODO: we can display the worktree repos in a different font.
|
||||||
return os.path.exists(loc)
|
if os.path.exists(loc):
|
||||||
|
return True
|
||||||
|
if not is_bare:
|
||||||
|
return False
|
||||||
|
# detect bare repo
|
||||||
|
got = subprocess.run('git rev-parse --is-bare-repository'.split(),
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
|
||||||
|
cwd=path
|
||||||
|
)
|
||||||
|
if got.returncode == 0 and got.stdout == b'true\n':
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def rename_repo(repos: Dict[str, Dict[str, str]], repo: str, new_name: str):
|
||||||
def rename_repo(repos: Dict[str, str], repo: str, new_name: str):
|
|
||||||
"""
|
"""
|
||||||
Write new repo name to file
|
Write new repo name to file
|
||||||
"""
|
"""
|
||||||
path = repos[repo]
|
if new_name in repos:
|
||||||
|
print(f"{new_name} is already in use!")
|
||||||
|
return
|
||||||
|
prop = repos[repo]
|
||||||
del repos[repo]
|
del repos[repo]
|
||||||
repos[new_name] = path
|
repos[new_name] = prop
|
||||||
write_to_repo_file(repos, 'w')
|
# write to local config if inside a main path
|
||||||
|
main_paths = (prop['path'] for prop in repos.values() if prop['type'] == 'm')
|
||||||
|
cwd = os.getcwd()
|
||||||
|
is_local_config = True
|
||||||
|
for p in main_paths:
|
||||||
|
if is_relative_to(cwd, p):
|
||||||
|
write_to_repo_file(repos, 'w', p)
|
||||||
|
break
|
||||||
|
else: # global config
|
||||||
|
write_to_repo_file(repos, 'w')
|
||||||
|
is_local_config = False
|
||||||
|
# update groups only when outside any main repos
|
||||||
|
if is_local_config:
|
||||||
|
return
|
||||||
|
groups = get_groups()
|
||||||
|
for g, members in groups.items():
|
||||||
|
if repo in members:
|
||||||
|
members.remove(repo)
|
||||||
|
members.append(new_name)
|
||||||
|
groups[g] = sorted(members)
|
||||||
|
write_to_groups_file(groups, 'w')
|
||||||
|
|
||||||
|
|
||||||
def write_to_repo_file(repos: Dict[str, str], mode: str):
|
def write_to_repo_file(repos: Dict[str, Dict[str, str]], mode: str, root=None):
|
||||||
"""
|
"""
|
||||||
|
@param repos: each repo is {name: {properties}}
|
||||||
"""
|
"""
|
||||||
data = ''.join(f'{path},{name}\n' for name, path in repos.items())
|
data = [(prop['path'], name, prop['type'], ' '.join(prop['flags']))
|
||||||
fname = common.get_config_fname('repo_path')
|
for name, prop in repos.items()]
|
||||||
|
fname = common.get_config_fname('repos.csv', root)
|
||||||
os.makedirs(os.path.dirname(fname), exist_ok=True)
|
os.makedirs(os.path.dirname(fname), exist_ok=True)
|
||||||
with open(fname, mode) as f:
|
with open(fname, mode, newline='') as f:
|
||||||
f.write(data)
|
writer = csv.writer(f, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
|
||||||
|
writer.writerows(data)
|
||||||
|
|
||||||
|
|
||||||
def write_to_groups_file(groups: Dict[str, List[str]], mode: str):
|
def write_to_groups_file(groups: Dict[str, List[str]], mode: str):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
fname = common.get_config_fname('groups.yml')
|
fname = common.get_config_fname('groups.csv')
|
||||||
os.makedirs(os.path.dirname(fname), exist_ok=True)
|
os.makedirs(os.path.dirname(fname), exist_ok=True)
|
||||||
if not groups: # all groups are deleted
|
if not groups: # all groups are deleted
|
||||||
open(fname, 'w').close()
|
open(fname, 'w').close()
|
||||||
else:
|
else:
|
||||||
with open(fname, mode) as f:
|
with open(fname, mode, newline='') as f:
|
||||||
yaml.dump(groups, f, default_flow_style=None)
|
data = [
|
||||||
|
(group, ' '.join(repos))
|
||||||
|
for group, repos in groups.items()
|
||||||
|
]
|
||||||
|
writer = csv.writer(f, delimiter=':', quotechar='"', quoting=csv.QUOTE_MINIMAL)
|
||||||
|
writer.writerows(data)
|
||||||
|
|
||||||
|
|
||||||
def add_repos(repos: Dict[str, str], new_paths: List[str]):
|
def _make_name(path: str, repos: Dict[str, Dict[str, str]],
|
||||||
|
name_counts: Counter) -> str:
|
||||||
"""
|
"""
|
||||||
Write new repo paths to file
|
Given a new repo `path`, create a repo name. By default, basename is used.
|
||||||
|
If name collision exists, further include parent path name.
|
||||||
|
|
||||||
|
@param path: It should not be in `repos` and is absolute
|
||||||
|
"""
|
||||||
|
name = os.path.basename(os.path.normpath(path))
|
||||||
|
if name in repos or name_counts[name] > 1:
|
||||||
|
par_name = os.path.basename(os.path.dirname(path))
|
||||||
|
return os.path.join(par_name, name)
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
def _get_repo_type(path, repo_type, root) -> str:
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
if repo_type != '': # explicitly set
|
||||||
|
return repo_type
|
||||||
|
if root is not None and os.path.normpath(root) == os.path.normpath(path):
|
||||||
|
return 'm'
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
def add_repos(repos: Dict[str, Dict[str, str]], new_paths: List[str],
|
||||||
|
repo_type='', root=None, is_bare=False) -> Dict[str, Dict[str, str]]:
|
||||||
|
"""
|
||||||
|
Write new repo paths to file; return the added repos.
|
||||||
|
|
||||||
@param repos: name -> path
|
@param repos: name -> path
|
||||||
"""
|
"""
|
||||||
existing_paths = set(repos.values())
|
existing_paths = {prop['path'] for prop in repos.values()}
|
||||||
new_paths = set(os.path.abspath(p) for p in new_paths if is_git(p))
|
new_paths = {p for p in new_paths if is_git(p, is_bare)}
|
||||||
new_paths = new_paths - existing_paths
|
new_paths = new_paths - existing_paths
|
||||||
|
new_repos = {}
|
||||||
if new_paths:
|
if new_paths:
|
||||||
print(f"Found {len(new_paths)} new repo(s).")
|
print(f"Found {len(new_paths)} new repo(s).")
|
||||||
new_repos = {
|
name_counts = Counter(
|
||||||
os.path.basename(os.path.normpath(path)): path
|
os.path.basename(os.path.normpath(p)) for p in new_paths
|
||||||
for path in new_paths}
|
)
|
||||||
write_to_repo_file(new_repos, 'a+')
|
new_repos = {_make_name(path, repos, name_counts): {
|
||||||
|
'path': path,
|
||||||
|
'type': _get_repo_type(path, repo_type, root),
|
||||||
|
'flags': '',
|
||||||
|
} for path in new_paths}
|
||||||
|
# When root is not None, we could optionally set its type to 'm', i.e.,
|
||||||
|
# main repo.
|
||||||
|
write_to_repo_file(new_repos, 'a+', root)
|
||||||
else:
|
else:
|
||||||
print('No new repos found!')
|
print('No new repos found!')
|
||||||
|
return new_repos
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_dir_hash(repo_path: str, paths: List[str]) -> Tuple[str, ...]:
|
||||||
|
"""
|
||||||
|
Return relative parent strings
|
||||||
|
|
||||||
|
For example, if `repo_path` is /a/b/c/d/here, and one of `paths` is /a/b/
|
||||||
|
then return (b, c, d)
|
||||||
|
"""
|
||||||
|
for p in paths:
|
||||||
|
if is_relative_to(repo_path, p):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return ()
|
||||||
|
return (os.path.basename(p),
|
||||||
|
*os.path.normpath(os.path.relpath(repo_path, p)).split(os.sep)[:-1])
|
||||||
|
|
||||||
|
|
||||||
|
def auto_group(repos: Dict[str, Dict[str, str]], paths: List[str]
|
||||||
|
) -> Dict[str, List[str]]:
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
# FIXME: the upstream code should make sure that paths are all independent
|
||||||
|
# i.e., each repo should be contained in one and only one path
|
||||||
|
new_groups = defaultdict(list)
|
||||||
|
for repo_name, prop in repos.items():
|
||||||
|
hash = _generate_dir_hash(prop['path'], paths)
|
||||||
|
if not hash:
|
||||||
|
continue
|
||||||
|
for i in range(1, len(hash)+1):
|
||||||
|
group_name = '-'.join(hash[:i])
|
||||||
|
new_groups[group_name].append(repo_name)
|
||||||
|
# FIXME: need to make sure the new group names don't clash with old ones
|
||||||
|
# or repo names
|
||||||
|
return new_groups
|
||||||
|
|
||||||
|
|
||||||
def parse_clone_config(fname: str) -> Iterator[List[str]]:
|
def parse_clone_config(fname: str) -> Iterator[List[str]]:
|
||||||
|
@ -157,6 +286,7 @@ async def run_async(repo_name: str, path: str, cmds: List[str]) -> Union[None, s
|
||||||
Run `cmds` asynchronously in `path` directory. Return the `path` if
|
Run `cmds` asynchronously in `path` directory. Return the `path` if
|
||||||
execution fails.
|
execution fails.
|
||||||
"""
|
"""
|
||||||
|
# TODO: deprecated since 3.8, will be removed in 3.10
|
||||||
process = await asyncio.create_subprocess_exec(
|
process = await asyncio.create_subprocess_exec(
|
||||||
*cmds,
|
*cmds,
|
||||||
stdin=asyncio.subprocess.DEVNULL,
|
stdin=asyncio.subprocess.DEVNULL,
|
||||||
|
@ -199,7 +329,7 @@ def exec_async_tasks(tasks: List[Coroutine]) -> List[Union[None, str]]:
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
def describe(repos: Dict[str, str], no_colors: bool=False) -> str:
|
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
|
||||||
"""
|
"""
|
||||||
|
@ -213,9 +343,14 @@ def describe(repos: Dict[str, str], no_colors: bool=False) -> str:
|
||||||
funcs[idx] = partial(get_repo_status, no_colors=True)
|
funcs[idx] = partial(get_repo_status, no_colors=True)
|
||||||
|
|
||||||
for name in sorted(repos):
|
for name in sorted(repos):
|
||||||
path = repos[name]
|
info_items = ' '.join(f(repos[name]) for f in funcs)
|
||||||
info_items = ' '.join(f(path) for f in funcs)
|
if repos[name]['type'] == 'm':
|
||||||
yield f'{name:<{name_width}}{info_items}'
|
# ANSI color code also takes length in Python
|
||||||
|
name = f'{info.Color.underline}{name}{info.Color.end}'
|
||||||
|
width = name_width + 8
|
||||||
|
yield f'{name:<{width}}{info_items}'
|
||||||
|
else:
|
||||||
|
yield f'{name:<{name_width}}{info_items}'
|
||||||
|
|
||||||
|
|
||||||
def get_cmds_from_files() -> Dict[str, Dict[str, str]]:
|
def get_cmds_from_files() -> Dict[str, Dict[str, str]]:
|
||||||
|
@ -231,17 +366,17 @@ def get_cmds_from_files() -> Dict[str, Dict[str, str]]:
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
# default config file
|
# default config file
|
||||||
fname = os.path.join(os.path.dirname(__file__), "cmds.yml")
|
fname = os.path.join(os.path.dirname(__file__), "cmds.json")
|
||||||
with open(fname, 'r') as stream:
|
with open(fname, 'r') as f:
|
||||||
cmds = yaml.load(stream, Loader=yaml.FullLoader)
|
cmds = json.load(f)
|
||||||
|
|
||||||
# custom config file
|
# custom config file
|
||||||
root = common.get_config_dir()
|
root = common.get_config_dir()
|
||||||
fname = os.path.join(root, 'cmds.yml')
|
fname = os.path.join(root, 'cmds.json')
|
||||||
custom_cmds = {}
|
custom_cmds = {}
|
||||||
if os.path.isfile(fname) and os.path.getsize(fname):
|
if os.path.isfile(fname) and os.path.getsize(fname):
|
||||||
with open(fname, 'r') as stream:
|
with open(fname, 'r') as f:
|
||||||
custom_cmds = yaml.load(stream, Loader=yaml.FullLoader)
|
custom_cmds = json.load(f)
|
||||||
|
|
||||||
# custom commands shadow default ones
|
# custom commands shadow default ones
|
||||||
cmds.update(custom_cmds)
|
cmds.update(custom_cmds)
|
||||||
|
|
3
setup.py
3
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.12.7',
|
version='0.15.1',
|
||||||
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,
|
||||||
|
@ -18,7 +18,6 @@ setup(
|
||||||
author='Dong Zhou',
|
author='Dong Zhou',
|
||||||
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']},
|
||||||
install_requires=['pyyaml>=5.1'],
|
|
||||||
python_requires='~=3.6',
|
python_requires='~=3.6',
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Development Status :: 4 - Beta",
|
"Development Status :: 4 - Beta",
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
/a/bcd/repo1,repo1
|
/a/bcd/repo1,repo1,
|
||||||
/e/fgh/repo2,repo2
|
/e/fgh/repo2,repo2,,--haha --pp
|
||||||
/root/x/repo1,repo1
|
/root/x/repo1,repo1
|
||||||
|
|
|
@ -11,6 +11,7 @@ def fullpath(fname: str):
|
||||||
PATH_FNAME = fullpath('mock_path_file')
|
PATH_FNAME = fullpath('mock_path_file')
|
||||||
PATH_FNAME_EMPTY = fullpath('empty_path_file')
|
PATH_FNAME_EMPTY = fullpath('empty_path_file')
|
||||||
PATH_FNAME_CLASH = fullpath('clash_path_file')
|
PATH_FNAME_CLASH = fullpath('clash_path_file')
|
||||||
|
PATH_FNAME_MAIN = fullpath('main_path_file')
|
||||||
GROUP_FNAME = fullpath('mock_group_file')
|
GROUP_FNAME = fullpath('mock_group_file')
|
||||||
|
|
||||||
def async_mock():
|
def async_mock():
|
||||||
|
|
2
tests/main_path_file
Normal file
2
tests/main_path_file
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/path/to/main/,main1,m
|
||||||
|
/xxx/xx,xx,
|
|
@ -1,2 +1,2 @@
|
||||||
xx: [a, b]
|
xx:a b
|
||||||
yy: [a, c, d]
|
yy:a c d
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/a/bcd/repo1,repo1
|
/a/bcd/repo1,repo1
|
||||||
/a/b/c/repo3,xxx
|
/a/b/c/repo3,xxx,,
|
||||||
/e/fgh/repo2,repo2
|
/e/fgh/repo2,repo2
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,9 @@ from gita import info
|
||||||
def test_run_quiet_diff(mock_run):
|
def test_run_quiet_diff(mock_run):
|
||||||
mock_return = MagicMock()
|
mock_return = MagicMock()
|
||||||
mock_run.return_value = mock_return
|
mock_run.return_value = mock_return
|
||||||
got = info.run_quiet_diff(['my', 'args'])
|
got = info.run_quiet_diff(['--flags'], ['my', 'args'])
|
||||||
mock_run.assert_called_once_with(
|
mock_run.assert_called_once_with(
|
||||||
['git', 'diff', '--quiet', 'my', 'args'],
|
['git', '--flags', 'diff', '--quiet', 'my', 'args'],
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
assert got == mock_return.returncode
|
assert got == mock_return.returncode
|
||||||
|
|
|
@ -1,28 +1,102 @@
|
||||||
|
import os
|
||||||
import pytest
|
import pytest
|
||||||
from unittest.mock import patch, mock_open
|
from unittest.mock import patch
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import argparse
|
import argparse
|
||||||
|
import asyncio
|
||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
from gita import __main__
|
from gita import __main__
|
||||||
from gita import utils, info
|
from gita import utils, info, common
|
||||||
from conftest import (
|
from conftest import (
|
||||||
PATH_FNAME, PATH_FNAME_EMPTY, PATH_FNAME_CLASH, GROUP_FNAME,
|
PATH_FNAME, PATH_FNAME_EMPTY, PATH_FNAME_CLASH, GROUP_FNAME, PATH_FNAME_MAIN,
|
||||||
async_mock, TEST_DIR,
|
async_mock, TEST_DIR,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@patch('gita.utils.get_repos', return_value={'aa'})
|
||||||
|
def test_group_name(_):
|
||||||
|
got = __main__._group_name('xx')
|
||||||
|
assert got == 'xx'
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
__main__._group_name('aa')
|
||||||
|
|
||||||
|
|
||||||
|
class TestAdd:
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('input, expected', [
|
||||||
|
(['add', '.'], ''),
|
||||||
|
(['add', '-m', '.'], 'm'),
|
||||||
|
])
|
||||||
|
@patch('gita.common.get_config_fname')
|
||||||
|
def test_add(self, mock_path_fname, tmp_path, input, expected):
|
||||||
|
def side_effect(input, _=None):
|
||||||
|
return tmp_path / f'{input}.txt'
|
||||||
|
mock_path_fname.side_effect = side_effect
|
||||||
|
utils.get_repos.cache_clear()
|
||||||
|
__main__.main(input)
|
||||||
|
utils.get_repos.cache_clear()
|
||||||
|
got = utils.get_repos()
|
||||||
|
assert len(got) == 1
|
||||||
|
assert got['gita']['type'] == expected
|
||||||
|
|
||||||
|
@patch('gita.utils.is_git', return_value=True)
|
||||||
|
def test_add_main(self, _, tmp_path, monkeypatch, tmpdir):
|
||||||
|
def side_effect(root=None):
|
||||||
|
if root is None:
|
||||||
|
return os.path.join(tmp_path, "gita")
|
||||||
|
else:
|
||||||
|
return os.path.join(root, ".gita")
|
||||||
|
|
||||||
|
def desc(repos, **_):
|
||||||
|
print(len(repos), repos.keys())
|
||||||
|
assert len(repos) > 0
|
||||||
|
for r, prop in repos.items():
|
||||||
|
if prop['type'] == 'm':
|
||||||
|
assert 'test_add_main' in r
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
assert 0, 'no main repo found'
|
||||||
|
return ''
|
||||||
|
|
||||||
|
monkeypatch.setattr(common, 'get_config_dir', side_effect)
|
||||||
|
monkeypatch.setattr(utils, 'describe', desc)
|
||||||
|
|
||||||
|
utils.get_repos.cache_clear()
|
||||||
|
|
||||||
|
with tmpdir.as_cwd():
|
||||||
|
__main__.main(['add', '-m', '.'])
|
||||||
|
utils.get_repos.cache_clear()
|
||||||
|
__main__.main(['ll'])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('path_fname, expected', [
|
||||||
|
(PATH_FNAME, ''),
|
||||||
|
(PATH_FNAME_CLASH, "repo2: ['--haha', '--pp']\n"),
|
||||||
|
])
|
||||||
|
@patch('gita.utils.is_git', return_value=True)
|
||||||
|
@patch('gita.utils.get_groups', return_value={})
|
||||||
|
@patch('gita.common.get_config_fname')
|
||||||
|
def test_flags(mock_path_fname, _, __, path_fname, expected, capfd):
|
||||||
|
mock_path_fname.return_value = path_fname
|
||||||
|
utils.get_repos.cache_clear()
|
||||||
|
__main__.main(['flags'])
|
||||||
|
out, err = capfd.readouterr()
|
||||||
|
assert err == ''
|
||||||
|
assert out == expected
|
||||||
|
|
||||||
|
|
||||||
class TestLsLl:
|
class TestLsLl:
|
||||||
@patch('gita.common.get_config_fname')
|
@patch('gita.common.get_config_fname')
|
||||||
def testLl(self, mock_path_fname, capfd, tmp_path):
|
def test_ll(self, mock_path_fname, capfd, tmp_path):
|
||||||
"""
|
"""
|
||||||
functional test
|
functional test
|
||||||
"""
|
"""
|
||||||
# avoid modifying the local configuration
|
# avoid modifying the local configuration
|
||||||
def side_effect(input):
|
def side_effect(input, _=None):
|
||||||
return tmp_path / f'{input}.txt'
|
return tmp_path / f'{input}.txt'
|
||||||
#mock_path_fname.return_value = tmp_path / 'path_config.txt'
|
|
||||||
mock_path_fname.side_effect = side_effect
|
mock_path_fname.side_effect = side_effect
|
||||||
|
utils.get_repos.cache_clear()
|
||||||
__main__.main(['add', '.'])
|
__main__.main(['add', '.'])
|
||||||
out, err = capfd.readouterr()
|
out, err = capfd.readouterr()
|
||||||
assert err == ''
|
assert err == ''
|
||||||
|
@ -52,11 +126,11 @@ class TestLsLl:
|
||||||
__main__.main(['ls', 'gita'])
|
__main__.main(['ls', 'gita'])
|
||||||
out, err = capfd.readouterr()
|
out, err = capfd.readouterr()
|
||||||
assert err == ''
|
assert err == ''
|
||||||
assert out.strip() == utils.get_repos()['gita']
|
assert out.strip() == utils.get_repos()['gita']['path']
|
||||||
|
|
||||||
def testLs(self, monkeypatch, capfd):
|
def test_ls(self, monkeypatch, capfd):
|
||||||
monkeypatch.setattr(utils, 'get_repos',
|
monkeypatch.setattr(utils, 'get_repos',
|
||||||
lambda: {'repo1': '/a/', 'repo2': '/b/'})
|
lambda: {'repo1': {'path': '/a/'}, 'repo2': {'path': '/b/'}})
|
||||||
monkeypatch.setattr(utils, 'describe', lambda x: x)
|
monkeypatch.setattr(utils, 'describe', lambda x: x)
|
||||||
__main__.main(['ls'])
|
__main__.main(['ls'])
|
||||||
out, err = capfd.readouterr()
|
out, err = capfd.readouterr()
|
||||||
|
@ -69,21 +143,24 @@ class TestLsLl:
|
||||||
|
|
||||||
@pytest.mark.parametrize('path_fname, expected', [
|
@pytest.mark.parametrize('path_fname, expected', [
|
||||||
(PATH_FNAME,
|
(PATH_FNAME,
|
||||||
"repo1 cmaster dsu\x1b[0m msg\nrepo2 cmaster dsu\x1b[0m msg\nxxx cmaster dsu\x1b[0m msg\n"),
|
"repo1 cmaster dsu\x1b[0m msg \nrepo2 cmaster dsu\x1b[0m msg \nxxx cmaster dsu\x1b[0m msg \n"),
|
||||||
(PATH_FNAME_EMPTY, ""),
|
(PATH_FNAME_EMPTY, ""),
|
||||||
|
(PATH_FNAME_MAIN,
|
||||||
|
'\x1b[4mmain1\x1b[0m cmaster dsu\x1b[0m msg \nxx cmaster dsu\x1b[0m msg \n'),
|
||||||
(PATH_FNAME_CLASH,
|
(PATH_FNAME_CLASH,
|
||||||
"repo1 cmaster dsu\x1b[0m msg\nrepo2 cmaster dsu\x1b[0m msg\nx/repo1 cmaster dsu\x1b[0m msg\n"
|
"repo1 cmaster dsu\x1b[0m msg \nrepo2 cmaster dsu\x1b[0m msg \n"
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
@patch('gita.utils.is_git', return_value=True)
|
@patch('gita.utils.is_git', return_value=True)
|
||||||
@patch('gita.info.get_head', return_value="master")
|
@patch('gita.info.get_head', return_value="master")
|
||||||
@patch('gita.info._get_repo_status', return_value=("d", "s", "u", "c"))
|
@patch('gita.info._get_repo_status', return_value=("d", "s", "u", "c"))
|
||||||
@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.common.get_config_fname')
|
@patch('gita.common.get_config_fname')
|
||||||
def testWithPathFiles(self, mock_path_fname, _0, _1, _2, _3, path_fname,
|
def test_with_path_files(self, mock_path_fname, _0, _1, _2, _3, _4, path_fname,
|
||||||
expected, capfd):
|
expected, capfd):
|
||||||
def side_effect(input):
|
def side_effect(input, _=None):
|
||||||
if input == 'repo_path':
|
if input == 'repos.csv':
|
||||||
return path_fname
|
return path_fname
|
||||||
return f'/{input}'
|
return f'/{input}'
|
||||||
mock_path_fname.side_effect = side_effect
|
mock_path_fname.side_effect = side_effect
|
||||||
|
@ -95,25 +172,63 @@ class TestLsLl:
|
||||||
assert out == expected
|
assert out == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('input, expected', [
|
||||||
|
({'repo1': {'path': '/a/'}, 'repo2': {'path': '/b/'}}, ''),
|
||||||
|
])
|
||||||
@patch('subprocess.run')
|
@patch('subprocess.run')
|
||||||
@patch('gita.utils.get_repos', return_value={'repo1': '/a/', 'repo2': '/b/'})
|
@patch('gita.utils.get_repos')
|
||||||
def test_freeze(_, mock_run, capfd):
|
def test_freeze(mock_repos, mock_run, input, expected, capfd):
|
||||||
|
mock_repos.return_value = input
|
||||||
__main__.main(['freeze'])
|
__main__.main(['freeze'])
|
||||||
assert mock_run.call_count == 2
|
assert mock_run.call_count == 2
|
||||||
out, err = capfd.readouterr()
|
out, err = capfd.readouterr()
|
||||||
assert err == ''
|
assert err == ''
|
||||||
assert out == ',repo1,/a/\n,repo2,/b/\n'
|
assert out == expected
|
||||||
|
|
||||||
|
|
||||||
|
@patch('gita.utils.parse_clone_config', return_value=[
|
||||||
|
['git@github.com:user/repo.git', 'repo', '/a/repo']])
|
||||||
|
@patch('gita.utils.run_async', new=async_mock())
|
||||||
|
@patch('subprocess.run')
|
||||||
|
def test_clone(*_):
|
||||||
|
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||||
|
args = argparse.Namespace()
|
||||||
|
args.fname = ['freeze_filename']
|
||||||
|
args.preserve_path = None
|
||||||
|
__main__.f_clone(args)
|
||||||
|
mock_run = utils.run_async.mock
|
||||||
|
assert mock_run.call_count == 1
|
||||||
|
cmds = ['git', 'clone', 'git@github.com:user/repo.git']
|
||||||
|
mock_run.assert_called_once_with('repo', Path.cwd(), cmds)
|
||||||
|
|
||||||
|
|
||||||
|
@patch('gita.utils.parse_clone_config', return_value=[
|
||||||
|
['git@github.com:user/repo.git', 'repo', '/a/repo']])
|
||||||
|
@patch('gita.utils.run_async', new=async_mock())
|
||||||
|
@patch('subprocess.run')
|
||||||
|
def test_clone_with_preserve_path(*_):
|
||||||
|
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||||
|
args = argparse.Namespace()
|
||||||
|
args.fname = ['freeze_filename']
|
||||||
|
args.preserve_path = True
|
||||||
|
__main__.f_clone(args)
|
||||||
|
mock_run = utils.run_async.mock
|
||||||
|
assert mock_run.call_count == 1
|
||||||
|
cmds = ['git', 'clone', 'git@github.com:user/repo.git', '/a/repo']
|
||||||
|
mock_run.assert_called_once_with('repo', Path.cwd(), cmds)
|
||||||
|
|
||||||
|
|
||||||
@patch('os.path.isfile', return_value=True)
|
@patch('os.path.isfile', return_value=True)
|
||||||
@patch('gita.common.get_config_fname', return_value='some path')
|
@patch('gita.common.get_config_fname', return_value='some path')
|
||||||
@patch('gita.utils.get_repos', return_value={'repo1': '/a/', 'repo2': '/b/'})
|
@patch('gita.utils.get_repos', return_value={'repo1': {'path': '/a/', 'type': ''},
|
||||||
|
'repo2': {'path': '/b/', 'type': ''}})
|
||||||
@patch('gita.utils.write_to_repo_file')
|
@patch('gita.utils.write_to_repo_file')
|
||||||
def test_rm(mock_write, *_):
|
def test_rm(mock_write, *_):
|
||||||
args = argparse.Namespace()
|
args = argparse.Namespace()
|
||||||
args.repo = ['repo1']
|
args.repo = ['repo1']
|
||||||
__main__.f_rm(args)
|
__main__.f_rm(args)
|
||||||
mock_write.assert_called_once_with({'repo2': '/b/'}, 'w')
|
mock_write.assert_called_once_with(
|
||||||
|
{'repo2': {'path': '/b/', 'type': ''}}, 'w')
|
||||||
|
|
||||||
|
|
||||||
def test_not_add():
|
def test_not_add():
|
||||||
|
@ -121,17 +236,19 @@ def test_not_add():
|
||||||
__main__.main(['add', '/home/some/repo/'])
|
__main__.main(['add', '/home/some/repo/'])
|
||||||
|
|
||||||
|
|
||||||
@patch('gita.utils.get_repos', return_value={'repo2': '/d/efg'})
|
@patch('gita.utils.get_repos', return_value={'repo2': {'path': '/d/efg',
|
||||||
|
'flags': []}})
|
||||||
@patch('subprocess.run')
|
@patch('subprocess.run')
|
||||||
def test_fetch(mock_run, *_):
|
def test_fetch(mock_run, *_):
|
||||||
|
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||||
__main__.main(['fetch'])
|
__main__.main(['fetch'])
|
||||||
mock_run.assert_called_once_with(['git', 'fetch'], cwd='/d/efg')
|
mock_run.assert_called_once_with(['git', 'fetch'], cwd='/d/efg', shell=False)
|
||||||
|
|
||||||
|
|
||||||
@patch(
|
@patch(
|
||||||
'gita.utils.get_repos', return_value={
|
'gita.utils.get_repos', return_value={
|
||||||
'repo1': '/a/bc',
|
'repo1': {'path': '/a/bc', 'flags': []},
|
||||||
'repo2': '/d/efg'
|
'repo2': {'path': '/d/efg', 'flags': []}
|
||||||
})
|
})
|
||||||
@patch('gita.utils.run_async', new=async_mock())
|
@patch('gita.utils.run_async', new=async_mock())
|
||||||
@patch('subprocess.run')
|
@patch('subprocess.run')
|
||||||
|
@ -149,28 +266,28 @@ def test_async_fetch(*_):
|
||||||
'diff --name-only --staged',
|
'diff --name-only --staged',
|
||||||
"commit -am 'lala kaka'",
|
"commit -am 'lala kaka'",
|
||||||
])
|
])
|
||||||
@patch('gita.utils.get_repos', return_value={'repo7': 'path7'})
|
@patch('gita.utils.get_repos', return_value={'repo7': {'path': 'path7', 'flags': []}})
|
||||||
@patch('subprocess.run')
|
@patch('subprocess.run')
|
||||||
def test_superman(mock_run, _, input):
|
def test_superman(mock_run, _, input):
|
||||||
mock_run.reset_mock()
|
mock_run.reset_mock()
|
||||||
args = ['super', 'repo7'] + shlex.split(input)
|
args = ['super', 'repo7'] + shlex.split(input)
|
||||||
__main__.main(args)
|
__main__.main(args)
|
||||||
expected_cmds = ['git'] + shlex.split(input)
|
expected_cmds = ['git'] + shlex.split(input)
|
||||||
mock_run.assert_called_once_with(expected_cmds, cwd='path7')
|
mock_run.assert_called_once_with(expected_cmds, cwd='path7', shell=False)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('input', [
|
@pytest.mark.parametrize('input', [
|
||||||
'diff --name-only --staged',
|
'diff --name-only --staged',
|
||||||
"commit -am 'lala kaka'",
|
"commit -am 'lala kaka'",
|
||||||
])
|
])
|
||||||
@patch('gita.utils.get_repos', return_value={'repo7': 'path7'})
|
@patch('gita.utils.get_repos', return_value={'repo7': {'path': 'path7', 'flags': []}})
|
||||||
@patch('subprocess.run')
|
@patch('subprocess.run')
|
||||||
def test_shell(mock_run, _, input):
|
def test_shell(mock_run, _, input):
|
||||||
mock_run.reset_mock()
|
mock_run.reset_mock()
|
||||||
args = ['shell', 'repo7'] + shlex.split(input)
|
args = ['shell', 'repo7', input]
|
||||||
__main__.main(args)
|
__main__.main(args)
|
||||||
expected_cmds = shlex.split(input)
|
expected_cmds = input
|
||||||
mock_run.assert_called_once_with(expected_cmds, cwd='path7', check=True, stderr=-2, stdout=-1)
|
mock_run.assert_called_once_with(expected_cmds, cwd='path7', check=True, shell=True, stderr=-2, stdout=-1)
|
||||||
|
|
||||||
|
|
||||||
class TestContext:
|
class TestContext:
|
||||||
|
@ -184,21 +301,21 @@ class TestContext:
|
||||||
|
|
||||||
@patch('gita.utils.get_context', return_value=Path('gname.context'))
|
@patch('gita.utils.get_context', return_value=Path('gname.context'))
|
||||||
@patch('gita.utils.get_groups', return_value={'gname': ['a', 'b']})
|
@patch('gita.utils.get_groups', return_value={'gname': ['a', 'b']})
|
||||||
def testDisplayContext(self, _, __, capfd):
|
def test_display_context(self, _, __, capfd):
|
||||||
__main__.main(['context'])
|
__main__.main(['context'])
|
||||||
out, err = capfd.readouterr()
|
out, err = capfd.readouterr()
|
||||||
assert err == ''
|
assert err == ''
|
||||||
assert 'gname: a b\n' == out
|
assert 'gname: a b\n' == out
|
||||||
|
|
||||||
@patch('gita.utils.get_context')
|
@patch('gita.utils.get_context')
|
||||||
def testReset(self, mock_ctx):
|
def test_reset(self, mock_ctx):
|
||||||
__main__.main(['context', 'none'])
|
__main__.main(['context', 'none'])
|
||||||
mock_ctx.return_value.unlink.assert_called()
|
mock_ctx.return_value.unlink.assert_called()
|
||||||
|
|
||||||
@patch('gita.utils.get_context', return_value=None)
|
@patch('gita.utils.get_context', return_value=None)
|
||||||
@patch('gita.common.get_config_dir', return_value=TEST_DIR)
|
@patch('gita.common.get_config_dir', return_value=TEST_DIR)
|
||||||
@patch('gita.utils.get_groups', return_value={'lala': ['b'], 'kaka': []})
|
@patch('gita.utils.get_groups', return_value={'lala': ['b'], 'kaka': []})
|
||||||
def testSetFirstTime(self, *_):
|
def test_set_first_time(self, *_):
|
||||||
ctx = TEST_DIR / 'lala.context'
|
ctx = TEST_DIR / 'lala.context'
|
||||||
assert not ctx.is_file()
|
assert not ctx.is_file()
|
||||||
__main__.main(['context', 'lala'])
|
__main__.main(['context', 'lala'])
|
||||||
|
@ -208,7 +325,7 @@ class TestContext:
|
||||||
@patch('gita.common.get_config_dir', return_value=TEST_DIR)
|
@patch('gita.common.get_config_dir', return_value=TEST_DIR)
|
||||||
@patch('gita.utils.get_groups', return_value={'lala': ['b'], 'kaka': []})
|
@patch('gita.utils.get_groups', return_value={'lala': ['b'], 'kaka': []})
|
||||||
@patch('gita.utils.get_context')
|
@patch('gita.utils.get_context')
|
||||||
def testSetSecondTime(self, mock_ctx, *_):
|
def test_set_second_time(self, mock_ctx, *_):
|
||||||
__main__.main(['context', 'kaka'])
|
__main__.main(['context', 'kaka'])
|
||||||
mock_ctx.return_value.rename.assert_called()
|
mock_ctx.return_value.rename.assert_called()
|
||||||
|
|
||||||
|
@ -216,7 +333,7 @@ class TestContext:
|
||||||
class TestGroupCmd:
|
class TestGroupCmd:
|
||||||
|
|
||||||
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME)
|
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME)
|
||||||
def testLs(self, _, capfd):
|
def test_ls(self, _, capfd):
|
||||||
args = argparse.Namespace()
|
args = argparse.Namespace()
|
||||||
args.to_group = None
|
args.to_group = None
|
||||||
args.group_cmd = 'ls'
|
args.group_cmd = 'ls'
|
||||||
|
@ -227,7 +344,7 @@ class TestGroupCmd:
|
||||||
assert 'xx yy\n' == out
|
assert 'xx yy\n' == out
|
||||||
|
|
||||||
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME)
|
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME)
|
||||||
def testLl(self, _, capfd):
|
def test_ll(self, _, capfd):
|
||||||
args = argparse.Namespace()
|
args = argparse.Namespace()
|
||||||
args.to_group = None
|
args.to_group = None
|
||||||
args.group_cmd = None
|
args.group_cmd = None
|
||||||
|
@ -239,7 +356,7 @@ class TestGroupCmd:
|
||||||
|
|
||||||
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME)
|
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME)
|
||||||
@patch('gita.utils.write_to_groups_file')
|
@patch('gita.utils.write_to_groups_file')
|
||||||
def testRename(self, mock_write, _):
|
def test_rename(self, mock_write, _):
|
||||||
args = argparse.Namespace()
|
args = argparse.Namespace()
|
||||||
args.gname = 'xx'
|
args.gname = 'xx'
|
||||||
args.new_name = 'zz'
|
args.new_name = 'zz'
|
||||||
|
@ -250,7 +367,7 @@ class TestGroupCmd:
|
||||||
mock_write.assert_called_once_with(expected, 'w')
|
mock_write.assert_called_once_with(expected, 'w')
|
||||||
|
|
||||||
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME)
|
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME)
|
||||||
def testRenameError(self, *_):
|
def test_rename_error(self, *_):
|
||||||
args = argparse.Namespace()
|
args = argparse.Namespace()
|
||||||
args.gname = 'xx'
|
args.gname = 'xx'
|
||||||
args.new_name = 'yy'
|
args.new_name = 'yy'
|
||||||
|
@ -266,7 +383,7 @@ class TestGroupCmd:
|
||||||
@patch('gita.utils.get_repos', return_value={'a': '', 'b': '', 'c': '', 'd': ''})
|
@patch('gita.utils.get_repos', return_value={'a': '', 'b': '', 'c': '', 'd': ''})
|
||||||
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME)
|
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME)
|
||||||
@patch('gita.utils.write_to_groups_file')
|
@patch('gita.utils.write_to_groups_file')
|
||||||
def testRm(self, mock_write, _, __, input, expected):
|
def test_rm(self, mock_write, _, __, input, expected):
|
||||||
utils.get_groups.cache_clear()
|
utils.get_groups.cache_clear()
|
||||||
args = ['group', 'rm'] + shlex.split(input)
|
args = ['group', 'rm'] + shlex.split(input)
|
||||||
__main__.main(args)
|
__main__.main(args)
|
||||||
|
@ -275,7 +392,7 @@ class TestGroupCmd:
|
||||||
@patch('gita.utils.get_repos', return_value={'a': '', 'b': '', 'c': '', 'd': ''})
|
@patch('gita.utils.get_repos', return_value={'a': '', 'b': '', 'c': '', 'd': ''})
|
||||||
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME)
|
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME)
|
||||||
@patch('gita.utils.write_to_groups_file')
|
@patch('gita.utils.write_to_groups_file')
|
||||||
def testAdd(self, mock_write, *_):
|
def test_add(self, mock_write, *_):
|
||||||
args = argparse.Namespace()
|
args = argparse.Namespace()
|
||||||
args.to_group = ['a', 'c']
|
args.to_group = ['a', 'c']
|
||||||
args.group_cmd = 'add'
|
args.group_cmd = 'add'
|
||||||
|
@ -287,7 +404,7 @@ class TestGroupCmd:
|
||||||
@patch('gita.utils.get_repos', return_value={'a': '', 'b': '', 'c': '', 'd': ''})
|
@patch('gita.utils.get_repos', return_value={'a': '', 'b': '', 'c': '', 'd': ''})
|
||||||
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME)
|
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME)
|
||||||
@patch('gita.utils.write_to_groups_file')
|
@patch('gita.utils.write_to_groups_file')
|
||||||
def testAddToExisting(self, mock_write, *_):
|
def test_add_to_existing(self, mock_write, *_):
|
||||||
args = argparse.Namespace()
|
args = argparse.Namespace()
|
||||||
args.to_group = ['a', 'c']
|
args.to_group = ['a', 'c']
|
||||||
args.group_cmd = 'add'
|
args.group_cmd = 'add'
|
||||||
|
@ -300,7 +417,7 @@ class TestGroupCmd:
|
||||||
@patch('gita.utils.get_repos', return_value={'a': '', 'b': '', 'c': '', 'd': ''})
|
@patch('gita.utils.get_repos', return_value={'a': '', 'b': '', 'c': '', 'd': ''})
|
||||||
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME)
|
@patch('gita.common.get_config_fname', return_value=GROUP_FNAME)
|
||||||
@patch('gita.utils.write_to_groups_file')
|
@patch('gita.utils.write_to_groups_file')
|
||||||
def testRmRepo(self, mock_write, *_):
|
def test_rm_repo(self, mock_write, *_):
|
||||||
args = argparse.Namespace()
|
args = argparse.Namespace()
|
||||||
args.from_group = ['a', 'c']
|
args.from_group = ['a', 'c']
|
||||||
args.group_cmd = 'rmrepo'
|
args.group_cmd = 'rmrepo'
|
||||||
|
@ -310,6 +427,21 @@ class TestGroupCmd:
|
||||||
mock_write.assert_called_once_with(
|
mock_write.assert_called_once_with(
|
||||||
{'xx': ['b'], 'yy': ['a', 'c', 'd']}, 'w')
|
{'xx': ['b'], 'yy': ['a', 'c', 'd']}, 'w')
|
||||||
|
|
||||||
|
@patch('gita.common.get_config_fname')
|
||||||
|
def test_integration(self, mock_path_fname, tmp_path, capfd):
|
||||||
|
def side_effect(input, _=None):
|
||||||
|
return tmp_path / f'{input}.csv'
|
||||||
|
mock_path_fname.side_effect = side_effect
|
||||||
|
|
||||||
|
__main__.main('add .'.split())
|
||||||
|
utils.get_repos.cache_clear()
|
||||||
|
__main__.main('group add gita -n test'.split())
|
||||||
|
utils.get_groups.cache_clear()
|
||||||
|
__main__.main('ll test'.split())
|
||||||
|
out, err = capfd.readouterr()
|
||||||
|
assert err == ''
|
||||||
|
assert 'gita' in out
|
||||||
|
|
||||||
|
|
||||||
@patch('gita.utils.is_git', return_value=True)
|
@patch('gita.utils.is_git', return_value=True)
|
||||||
@patch('gita.common.get_config_fname', return_value=PATH_FNAME)
|
@patch('gita.common.get_config_fname', return_value=PATH_FNAME)
|
||||||
|
@ -319,44 +451,61 @@ def test_rename(mock_rename, _, __):
|
||||||
args = ['rename', 'repo1', 'abc']
|
args = ['rename', 'repo1', 'abc']
|
||||||
__main__.main(args)
|
__main__.main(args)
|
||||||
mock_rename.assert_called_once_with(
|
mock_rename.assert_called_once_with(
|
||||||
{'repo1': '/a/bcd/repo1', 'repo2': '/e/fgh/repo2',
|
{'repo1': {'path': '/a/bcd/repo1', 'type': '', 'flags': []},
|
||||||
'xxx': '/a/b/c/repo3'},
|
'xxx': {'path': '/a/b/c/repo3', 'type': '', 'flags': []},
|
||||||
|
'repo2': {'path': '/e/fgh/repo2', 'type': '', 'flags': []}},
|
||||||
'repo1', 'abc')
|
'repo1', 'abc')
|
||||||
|
|
||||||
|
|
||||||
class TestInfo:
|
class TestInfo:
|
||||||
|
|
||||||
@patch('gita.common.get_config_fname', return_value='')
|
@patch('gita.common.get_config_fname', return_value='')
|
||||||
def testLl(self, _, capfd):
|
def test_ll(self, _, capfd):
|
||||||
args = argparse.Namespace()
|
args = argparse.Namespace()
|
||||||
args.info_cmd = None
|
args.info_cmd = None
|
||||||
__main__.f_info(args)
|
__main__.f_info(args)
|
||||||
out, err = capfd.readouterr()
|
out, err = capfd.readouterr()
|
||||||
assert 'In use: branch,commit_msg\nUnused: path\n' == out
|
assert 'In use: branch,commit_msg,commit_time\nUnused: path\n' == out
|
||||||
assert err == ''
|
assert err == ''
|
||||||
|
|
||||||
@patch('gita.common.get_config_fname', return_value='')
|
@patch('gita.common.get_config_fname')
|
||||||
@patch('yaml.dump')
|
def test_add(self, mock_get_fname, tmpdir):
|
||||||
def testAdd(self, mock_dump, _):
|
|
||||||
args = argparse.Namespace()
|
args = argparse.Namespace()
|
||||||
args.info_cmd = 'add'
|
args.info_cmd = 'add'
|
||||||
args.info_item = 'path'
|
args.info_item = 'path'
|
||||||
with patch('builtins.open', mock_open(), create=True):
|
with tmpdir.as_cwd():
|
||||||
|
csv_config = Path.cwd() / 'info.csv'
|
||||||
|
mock_get_fname.return_value = csv_config
|
||||||
__main__.f_info(args)
|
__main__.f_info(args)
|
||||||
mock_dump.assert_called_once()
|
items = info.get_info_items()
|
||||||
args, kwargs = mock_dump.call_args
|
assert items == ['branch', 'commit_msg', 'commit_time', 'path']
|
||||||
assert args[0] == ['branch', 'commit_msg', 'path']
|
|
||||||
assert kwargs == {'default_flow_style': None}
|
|
||||||
|
|
||||||
@patch('gita.common.get_config_fname', return_value='')
|
@patch('gita.common.get_config_fname')
|
||||||
@patch('yaml.dump')
|
def test_rm(self, mock_get_fname, tmpdir):
|
||||||
def testRm(self, mock_dump, _):
|
|
||||||
args = argparse.Namespace()
|
args = argparse.Namespace()
|
||||||
args.info_cmd = 'rm'
|
args.info_cmd = 'rm'
|
||||||
args.info_item = 'commit_msg'
|
args.info_item = 'commit_msg'
|
||||||
with patch('builtins.open', mock_open(), create=True):
|
with tmpdir.as_cwd():
|
||||||
|
csv_config = Path.cwd() / 'info.csv'
|
||||||
|
mock_get_fname.return_value = csv_config
|
||||||
__main__.f_info(args)
|
__main__.f_info(args)
|
||||||
mock_dump.assert_called_once()
|
items = info.get_info_items()
|
||||||
args, kwargs = mock_dump.call_args
|
assert items == ['branch', 'commit_time']
|
||||||
assert args[0] == ['branch']
|
|
||||||
assert kwargs == {'default_flow_style': None}
|
|
||||||
|
@patch('gita.common.get_config_fname')
|
||||||
|
def test_set_color(mock_get_fname, tmpdir):
|
||||||
|
args = argparse.Namespace()
|
||||||
|
args.color_cmd = 'set'
|
||||||
|
args.color = 'redrum' # this color doesn't exist
|
||||||
|
args.situation = 'in-sync'
|
||||||
|
with tmpdir.as_cwd():
|
||||||
|
csv_config = Path.cwd() / 'colors.csv'
|
||||||
|
mock_get_fname.return_value = csv_config
|
||||||
|
__main__.f_color(args)
|
||||||
|
info.get_color_encoding.cache_clear() # avoid side effect
|
||||||
|
items = info.get_color_encoding()
|
||||||
|
info.get_color_encoding.cache_clear() # avoid side effect
|
||||||
|
assert items == {'no-remote': 'white', 'in-sync': 'redrum',
|
||||||
|
'diverged': 'red', 'local-ahead': 'purple',
|
||||||
|
'remote-ahead': 'yellow'}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import pytest
|
import pytest
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
from unittest.mock import patch, mock_open
|
from unittest.mock import patch, mock_open
|
||||||
|
|
||||||
from gita import utils, info
|
from gita import utils, info
|
||||||
|
@ -8,16 +10,41 @@ from conftest import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('repo_path, paths, expected', [
|
||||||
|
('/a/b/c/repo', ['/a/b'], ('b', 'c')),
|
||||||
|
])
|
||||||
|
def test_generate_dir_hash(repo_path, paths, expected):
|
||||||
|
got = utils._generate_dir_hash(repo_path, paths)
|
||||||
|
assert got == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('repos, paths, expected', [
|
||||||
|
({'r1': {'path': '/a/b//repo1'}, 'r2': {'path': '/a/b/repo2'}},
|
||||||
|
['/a/b'], {'b': ['r1', 'r2']}),
|
||||||
|
({'r1': {'path': '/a/b//repo1'}, 'r2': {'path': '/a/b/c/repo2'}},
|
||||||
|
['/a/b'], {'b': ['r1', 'r2'], 'b-c': ['r2']}),
|
||||||
|
({'r1': {'path': '/a/b/c/repo1'}, 'r2': {'path': '/a/b/c/repo2'}},
|
||||||
|
['/a/b'], {'b-c': ['r1', 'r2'], 'b': ['r1', 'r2']}),
|
||||||
|
])
|
||||||
|
def test_auto_group(repos, paths, expected):
|
||||||
|
got = utils.auto_group(repos, paths)
|
||||||
|
assert got == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('test_input, diff_return, expected', [
|
@pytest.mark.parametrize('test_input, diff_return, expected', [
|
||||||
([{'abc': '/root/repo/'}, False], True, 'abc \x1b[31mrepo *+_ \x1b[0m msg'),
|
([{'abc': {'path': '/root/repo/', 'type': '', 'flags': []}}, False],
|
||||||
([{'abc': '/root/repo/'}, True], True, 'abc repo *+_ msg'),
|
True, 'abc \x1b[31mrepo *+_ \x1b[0m msg xx'),
|
||||||
([{'repo': '/root/repo2/'}, False], False, 'repo \x1b[32mrepo _ \x1b[0m msg'),
|
([{'abc': {'path': '/root/repo/', 'type': '', 'flags': []}}, True],
|
||||||
|
True, 'abc repo *+_ msg xx'),
|
||||||
|
([{'repo': {'path': '/root/repo2/', 'type': '', 'flags': []}}, False],
|
||||||
|
False, 'repo \x1b[32mrepo _ \x1b[0m msg xx'),
|
||||||
])
|
])
|
||||||
def test_describe(test_input, diff_return, expected, monkeypatch):
|
def test_describe(test_input, diff_return, expected, monkeypatch):
|
||||||
monkeypatch.setattr(info, 'get_head', lambda x: 'repo')
|
monkeypatch.setattr(info, 'get_head', lambda x: 'repo')
|
||||||
monkeypatch.setattr(info, 'run_quiet_diff', lambda _: diff_return)
|
monkeypatch.setattr(info, 'run_quiet_diff', lambda *_: diff_return)
|
||||||
monkeypatch.setattr(info, 'get_commit_msg', lambda _: "msg")
|
monkeypatch.setattr(info, 'get_commit_msg', lambda *_: "msg")
|
||||||
monkeypatch.setattr(info, 'has_untracked', lambda: True)
|
monkeypatch.setattr(info, 'get_commit_time', lambda *_: "xx")
|
||||||
|
monkeypatch.setattr(info, 'has_untracked', lambda *_: True)
|
||||||
monkeypatch.setattr('os.chdir', lambda x: None)
|
monkeypatch.setattr('os.chdir', lambda x: None)
|
||||||
print('expected: ', repr(expected))
|
print('expected: ', repr(expected))
|
||||||
print('got: ', repr(next(utils.describe(*test_input))))
|
print('got: ', repr(next(utils.describe(*test_input))))
|
||||||
|
@ -26,15 +53,14 @@ def test_describe(test_input, diff_return, expected, monkeypatch):
|
||||||
|
|
||||||
@pytest.mark.parametrize('path_fname, expected', [
|
@pytest.mark.parametrize('path_fname, expected', [
|
||||||
(PATH_FNAME, {
|
(PATH_FNAME, {
|
||||||
'repo1': '/a/bcd/repo1',
|
'repo1': {'path': '/a/bcd/repo1', 'type': '', 'flags': []},
|
||||||
'repo2': '/e/fgh/repo2',
|
'repo2': {'path': '/e/fgh/repo2', 'type': '', 'flags': []},
|
||||||
'xxx': '/a/b/c/repo3',
|
'xxx': {'path': '/a/b/c/repo3', 'type': '', 'flags': []},
|
||||||
}),
|
}),
|
||||||
(PATH_FNAME_EMPTY, {}),
|
(PATH_FNAME_EMPTY, {}),
|
||||||
(PATH_FNAME_CLASH, {
|
(PATH_FNAME_CLASH, {
|
||||||
'repo1': '/a/bcd/repo1',
|
'repo2': {'path': '/e/fgh/repo2', 'type': '', 'flags': ['--haha', '--pp']},
|
||||||
'repo2': '/e/fgh/repo2',
|
'repo1': {'path': '/root/x/repo1', 'type': '', 'flags': []}
|
||||||
'x/repo1': '/root/x/repo1'
|
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
@patch('gita.utils.is_git', return_value=True)
|
@patch('gita.utils.is_git', return_value=True)
|
||||||
|
@ -70,42 +96,46 @@ def test_get_groups(mock_group_fname, group_fname, expected):
|
||||||
@patch('os.path.getsize', return_value=True)
|
@patch('os.path.getsize', return_value=True)
|
||||||
def test_custom_push_cmd(*_):
|
def test_custom_push_cmd(*_):
|
||||||
with patch('builtins.open',
|
with patch('builtins.open',
|
||||||
mock_open(read_data='push:\n cmd: hand\n help: me')):
|
mock_open(read_data='{"push":{"cmd":"hand","help":"me","allow_all":true}}')):
|
||||||
cmds = utils.get_cmds_from_files()
|
cmds = utils.get_cmds_from_files()
|
||||||
assert cmds['push'] == {'cmd': 'hand', 'help': 'me'}
|
assert cmds['push'] == {'cmd': 'hand', 'help': 'me', 'allow_all': True}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'path_input, expected',
|
'path_input, expected',
|
||||||
[
|
[
|
||||||
(['/home/some/repo/'], '/home/some/repo,repo\n'), # add one new
|
(['/home/some/repo'], '/home/some/repo,some/repo,,\r\n'), # add one new
|
||||||
(['/home/some/repo1', '/repo2'],
|
(['/home/some/repo1', '/repo2'],
|
||||||
{'/repo2,repo2\n/home/some/repo1,repo1\n', # add two new
|
{'/repo2,repo2,,\r\n', # add two new
|
||||||
'/home/some/repo1,repo1\n/repo2,repo2\n'}), # add two new
|
'/home/some/repo1,repo1,,\r\n'}), # add two new
|
||||||
(['/home/some/repo1', '/nos/repo'],
|
(['/home/some/repo1', '/nos/repo'],
|
||||||
'/home/some/repo1,repo1\n'), # add one old one new
|
'/home/some/repo1,repo1,,\r\n'), # add one old one new
|
||||||
])
|
])
|
||||||
@patch('os.makedirs')
|
@patch('os.makedirs')
|
||||||
@patch('gita.utils.is_git', return_value=True)
|
@patch('gita.utils.is_git', return_value=True)
|
||||||
def test_add_repos(_0, _1, path_input, expected, monkeypatch):
|
def test_add_repos(_0, _1, path_input, expected, monkeypatch):
|
||||||
monkeypatch.setenv('XDG_CONFIG_HOME', '/config')
|
monkeypatch.setenv('XDG_CONFIG_HOME', '/config')
|
||||||
with patch('builtins.open', mock_open()) as mock_file:
|
with patch('builtins.open', mock_open()) as mock_file:
|
||||||
utils.add_repos({'repo': '/nos/repo'}, path_input)
|
utils.add_repos({'repo': {'path': '/nos/repo'}}, path_input)
|
||||||
mock_file.assert_called_with('/config/gita/repo_path', 'a+')
|
mock_file.assert_called_with('/config/gita/repos.csv', 'a+', newline='')
|
||||||
handle = mock_file()
|
handle = mock_file()
|
||||||
if type(expected) == str:
|
if type(expected) == str:
|
||||||
handle.write.assert_called_once_with(expected)
|
handle.write.assert_called_once_with(expected)
|
||||||
else:
|
else:
|
||||||
handle.write.assert_called_once()
|
# the write order is random
|
||||||
|
assert handle.write.call_count == 2
|
||||||
args, kwargs = handle.write.call_args
|
args, kwargs = handle.write.call_args
|
||||||
assert args[0] in expected
|
assert args[0] in expected
|
||||||
assert not kwargs
|
assert not kwargs
|
||||||
|
|
||||||
|
|
||||||
|
@patch('gita.utils.write_to_groups_file')
|
||||||
@patch('gita.utils.write_to_repo_file')
|
@patch('gita.utils.write_to_repo_file')
|
||||||
def test_rename_repo(mock_write):
|
def test_rename_repo(mock_write, _):
|
||||||
utils.rename_repo({'r1': '/a/b', 'r2': '/c/c'}, 'r2', 'xxx')
|
repos = {'r1': {'path': '/a/b', 'type': None},
|
||||||
mock_write.assert_called_once_with({'r1': '/a/b', 'xxx': '/c/c'}, 'w')
|
'r2': {'path': '/c/c', 'type': None}}
|
||||||
|
utils.rename_repo(repos, 'r2', 'xxx')
|
||||||
|
mock_write.assert_called_once_with(repos, 'w')
|
||||||
|
|
||||||
|
|
||||||
def test_async_output(capfd):
|
def test_async_output(capfd):
|
||||||
|
@ -124,3 +154,10 @@ def test_async_output(capfd):
|
||||||
out, err = capfd.readouterr()
|
out, err = capfd.readouterr()
|
||||||
assert err == ''
|
assert err == ''
|
||||||
assert out == 'myrepo: 0\nmyrepo: 0\n\nmyrepo: 1\nmyrepo: 1\n\nmyrepo: 2\nmyrepo: 2\n\nmyrepo: 3\nmyrepo: 3\n\n'
|
assert out == 'myrepo: 0\nmyrepo: 0\n\nmyrepo: 1\nmyrepo: 1\n\nmyrepo: 2\nmyrepo: 2\n\nmyrepo: 3\nmyrepo: 3\n\n'
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_git(tmpdir):
|
||||||
|
with tmpdir.as_cwd():
|
||||||
|
subprocess.run('git init --bare .'.split())
|
||||||
|
assert utils.is_git(Path.cwd()) is False
|
||||||
|
assert utils.is_git(Path.cwd(), is_bare=True) is True
|
||||||
|
|
766
work.vim
Normal file
766
work.vim
Normal file
|
@ -0,0 +1,766 @@
|
||||||
|
let SessionLoad = 1
|
||||||
|
if &cp | set nocp | endif
|
||||||
|
let s:cpo_save=&cpo
|
||||||
|
set cpo&vim
|
||||||
|
inoremap <silent> <Plug>(-fzf-complete-finish) l
|
||||||
|
inoremap <silent> <Plug>CocRefresh =coc#_complete()
|
||||||
|
inoremap <silent> <Plug>(fzf-maps-i) :call fzf#vim#maps('i', 0)
|
||||||
|
inoremap <expr> <Plug>(fzf-complete-buffer-line) fzf#vim#complete#buffer_line()
|
||||||
|
inoremap <expr> <Plug>(fzf-complete-line) fzf#vim#complete#line()
|
||||||
|
inoremap <expr> <Plug>(fzf-complete-file-ag) fzf#vim#complete#path('ag -l -g ""')
|
||||||
|
inoremap <expr> <Plug>(fzf-complete-file) fzf#vim#complete#path("find . -path '*/\.*' -prune -o -type f -print -o -type l -print | sed 's:^..::'")
|
||||||
|
inoremap <expr> <Plug>(fzf-complete-path) fzf#vim#complete#path("find . -path '*/\.*' -prune -o -print | sed '1d;s:^..::'")
|
||||||
|
inoremap <expr> <Plug>(fzf-complete-word) fzf#vim#complete#word()
|
||||||
|
inoremap <silent> <SNR>20_AutoPairsReturn =AutoPairsReturn()
|
||||||
|
inoremap <silent> <expr> <C-Space> coc#refresh()
|
||||||
|
inoremap <expr> <S-Tab> pumvisible() ? "\" : "\"
|
||||||
|
map! <D-v> *
|
||||||
|
nnoremap * *``
|
||||||
|
nmap <silent> ,ig <Plug>IndentGuidesToggle
|
||||||
|
noremap ,4 4gt
|
||||||
|
noremap ,3 3gt
|
||||||
|
noremap ,2 2gt
|
||||||
|
noremap ,1 1gt
|
||||||
|
nmap ,d :GitGutterFold
|
||||||
|
nmap ,r :Rg!
|
||||||
|
nmap ,b :Buffer
|
||||||
|
nmap ,l :Lines!
|
||||||
|
nmap ,w :BLines
|
||||||
|
nmap ,o :Files!
|
||||||
|
nmap ,f :GFiles!
|
||||||
|
nmap ,a :CtrlSF -R ""<Left>
|
||||||
|
nmap ,t :TagbarToggle
|
||||||
|
noremap , :noh
:call clearmatches()
|
||||||
|
vnoremap ,s :sort
|
||||||
|
nnoremap ,s :w
|
||||||
|
noremap ,e :qa!
" Quit all windows
|
||||||
|
noremap ,q :q
" Quit current windows
|
||||||
|
vnoremap < <gv
|
||||||
|
vnoremap > >gv
|
||||||
|
nnoremap N Nzzzv
|
||||||
|
vmap gx <Plug>NetrwBrowseXVis
|
||||||
|
nmap gx <Plug>NetrwBrowseX
|
||||||
|
nmap g> <Plug>(swap-next)
|
||||||
|
nmap g< <Plug>(swap-prev)
|
||||||
|
xmap gs <Plug>(swap-interactive)
|
||||||
|
nmap gs <Plug>(swap-interactive)
|
||||||
|
nmap <silent> gr <Plug>(coc-references)
|
||||||
|
nmap <silent> gi <Plug>(coc-implementation)
|
||||||
|
nmap <silent> gy <Plug>(coc-type-definition)
|
||||||
|
nmap <silent> gd <Plug>(coc-definition)
|
||||||
|
nnoremap n nzzzv
|
||||||
|
nnoremap <silent> <Plug>(-fzf-complete-finish) a
|
||||||
|
nnoremap <Plug>(-fzf-:) :
|
||||||
|
nnoremap <Plug>(-fzf-/) /
|
||||||
|
nnoremap <Plug>(-fzf-vim-do) :execute g:__fzf_command
|
||||||
|
vnoremap <silent> <Plug>NetrwBrowseXVis :call netrw#BrowseXVis()
|
||||||
|
nnoremap <silent> <Plug>NetrwBrowseX :call netrw#BrowseX(netrw#GX(),netrw#CheckIfRemote(netrw#GX()))
|
||||||
|
onoremap <silent> <Plug>(coc-classobj-a) :call coc#rpc#request('selectSymbolRange', [v:false, '', ['Interface', 'Struct', 'Class']])
|
||||||
|
onoremap <silent> <Plug>(coc-classobj-i) :call coc#rpc#request('selectSymbolRange', [v:true, '', ['Interface', 'Struct', 'Class']])
|
||||||
|
vnoremap <silent> <Plug>(coc-classobj-a) :call coc#rpc#request('selectSymbolRange', [v:false, visualmode(), ['Interface', 'Struct', 'Class']])
|
||||||
|
vnoremap <silent> <Plug>(coc-classobj-i) :call coc#rpc#request('selectSymbolRange', [v:true, visualmode(), ['Interface', 'Struct', 'Class']])
|
||||||
|
onoremap <silent> <Plug>(coc-funcobj-a) :call coc#rpc#request('selectSymbolRange', [v:false, '', ['Method', 'Function']])
|
||||||
|
onoremap <silent> <Plug>(coc-funcobj-i) :call coc#rpc#request('selectSymbolRange', [v:true, '', ['Method', 'Function']])
|
||||||
|
vnoremap <silent> <Plug>(coc-funcobj-a) :call coc#rpc#request('selectSymbolRange', [v:false, visualmode(), ['Method', 'Function']])
|
||||||
|
vnoremap <silent> <Plug>(coc-funcobj-i) :call coc#rpc#request('selectSymbolRange', [v:true, visualmode(), ['Method', 'Function']])
|
||||||
|
nnoremap <silent> <Plug>(coc-cursors-position) :call coc#rpc#request('cursorsSelect', [bufnr('%'), 'position', 'n'])
|
||||||
|
nnoremap <silent> <Plug>(coc-cursors-word) :call coc#rpc#request('cursorsSelect', [bufnr('%'), 'word', 'n'])
|
||||||
|
vnoremap <silent> <Plug>(coc-cursors-range) :call coc#rpc#request('cursorsSelect', [bufnr('%'), 'range', visualmode()])
|
||||||
|
nnoremap <silent> <Plug>(coc-refactor) :call CocActionAsync('refactor')
|
||||||
|
nnoremap <silent> <Plug>(coc-command-repeat) :call CocAction('repeatCommand')
|
||||||
|
nnoremap <silent> <Plug>(coc-float-jump) :call coc#float#jump()
|
||||||
|
nnoremap <silent> <Plug>(coc-float-hide) :call coc#float#close_all()
|
||||||
|
nnoremap <silent> <Plug>(coc-fix-current) :call CocActionAsync('doQuickfix')
|
||||||
|
nnoremap <silent> <Plug>(coc-openlink) :call CocActionAsync('openLink')
|
||||||
|
nnoremap <silent> <Plug>(coc-references-used) :call CocActionAsync('jumpUsed')
|
||||||
|
nnoremap <silent> <Plug>(coc-references) :call CocActionAsync('jumpReferences')
|
||||||
|
nnoremap <silent> <Plug>(coc-type-definition) :call CocActionAsync('jumpTypeDefinition')
|
||||||
|
nnoremap <silent> <Plug>(coc-implementation) :call CocActionAsync('jumpImplementation')
|
||||||
|
nnoremap <silent> <Plug>(coc-declaration) :call CocActionAsync('jumpDeclaration')
|
||||||
|
nnoremap <silent> <Plug>(coc-definition) :call CocActionAsync('jumpDefinition')
|
||||||
|
nnoremap <silent> <Plug>(coc-diagnostic-prev-error) :call CocActionAsync('diagnosticPrevious', 'error')
|
||||||
|
nnoremap <silent> <Plug>(coc-diagnostic-next-error) :call CocActionAsync('diagnosticNext', 'error')
|
||||||
|
nnoremap <silent> <Plug>(coc-diagnostic-prev) :call CocActionAsync('diagnosticPrevious')
|
||||||
|
nnoremap <silent> <Plug>(coc-diagnostic-next) :call CocActionAsync('diagnosticNext')
|
||||||
|
nnoremap <silent> <Plug>(coc-diagnostic-info) :call CocActionAsync('diagnosticInfo')
|
||||||
|
nnoremap <silent> <Plug>(coc-format) :call CocActionAsync('format')
|
||||||
|
nnoremap <silent> <Plug>(coc-rename) :call CocActionAsync('rename')
|
||||||
|
nnoremap <Plug>(coc-codeaction-cursor) :call CocActionAsync('codeAction', 'cursor')
|
||||||
|
nnoremap <Plug>(coc-codeaction-line) :call CocActionAsync('codeAction', 'line')
|
||||||
|
nnoremap <Plug>(coc-codeaction) :call CocActionAsync('codeAction', '')
|
||||||
|
vnoremap <silent> <Plug>(coc-codeaction-selected) :call CocActionAsync('codeAction', visualmode())
|
||||||
|
vnoremap <silent> <Plug>(coc-format-selected) :call CocActionAsync('formatSelected', visualmode())
|
||||||
|
nnoremap <Plug>(coc-codelens-action) :call CocActionAsync('codeLensAction')
|
||||||
|
nnoremap <Plug>(coc-range-select) :call CocActionAsync('rangeSelect', '', v:true)
|
||||||
|
vnoremap <silent> <Plug>(coc-range-select-backward) :call CocActionAsync('rangeSelect', visualmode(), v:false)
|
||||||
|
vnoremap <silent> <Plug>(coc-range-select) :call CocActionAsync('rangeSelect', visualmode(), v:true)
|
||||||
|
noremap <silent> <Plug>(swap-textobject-a) :call swap#textobj#select('a')
|
||||||
|
noremap <silent> <Plug>(swap-textobject-i) :call swap#textobj#select('i')
|
||||||
|
nnoremap <silent> <Plug>(swap-next) :call swap#prerequisite('n', repeat([['#', '#+1']], v:count1))
g@l
|
||||||
|
nnoremap <silent> <Plug>(swap-prev) :call swap#prerequisite('n', repeat([['#', '#-1']], v:count1))
g@l
|
||||||
|
xnoremap <silent> <Plug>(swap-interactive) :call swap#prerequisite('x')
gvg@
|
||||||
|
nnoremap <silent> <Plug>(swap-interactive) :call swap#prerequisite('n')
g@l
|
||||||
|
onoremap <silent> <Plug>(fzf-maps-o) :call fzf#vim#maps('o', 0)
|
||||||
|
xnoremap <silent> <Plug>(fzf-maps-x) :call fzf#vim#maps('x', 0)
|
||||||
|
nnoremap <silent> <Plug>(fzf-maps-n) :call fzf#vim#maps('n', 0)
|
||||||
|
tnoremap <silent> <Plug>(fzf-normal)
|
||||||
|
tnoremap <silent> <Plug>(fzf-insert) i
|
||||||
|
nnoremap <silent> <Plug>(fzf-normal) <Nop>
|
||||||
|
nnoremap <silent> <Plug>(fzf-insert) i
|
||||||
|
nnoremap <Plug>CtrlSFQuickfixPrompt :CtrlSFQuickfix
|
||||||
|
nnoremap <Plug>CtrlSFPrompt :CtrlSF
|
||||||
|
nnoremap <silent> <Plug>GitGutterPreviewHunk :call gitgutter#utility#warn('Please change your map <Plug>GitGutterPreviewHunk to <Plug>(GitGutterPreviewHunk)')
|
||||||
|
nnoremap <silent> <Plug>(GitGutterPreviewHunk) :GitGutterPreviewHunk
|
||||||
|
nnoremap <silent> <Plug>GitGutterUndoHunk :call gitgutter#utility#warn('Please change your map <Plug>GitGutterUndoHunk to <Plug>(GitGutterUndoHunk)')
|
||||||
|
nnoremap <silent> <Plug>(GitGutterUndoHunk) :GitGutterUndoHunk
|
||||||
|
nnoremap <silent> <Plug>GitGutterStageHunk :call gitgutter#utility#warn('Please change your map <Plug>GitGutterStageHunk to <Plug>(GitGutterStageHunk)')
|
||||||
|
nnoremap <silent> <Plug>(GitGutterStageHunk) :GitGutterStageHunk
|
||||||
|
xnoremap <silent> <Plug>GitGutterStageHunk :call gitgutter#utility#warn('Please change your map <Plug>GitGutterStageHunk to <Plug>(GitGutterStageHunk)')
|
||||||
|
xnoremap <silent> <Plug>(GitGutterStageHunk) :GitGutterStageHunk
|
||||||
|
nnoremap <silent> <expr> <Plug>GitGutterPrevHunk &diff ? '[c' : ":\call gitgutter#utility#warn('Please change your map \<Plug>GitGutterPrevHunk to \<Plug>(GitGutterPrevHunk)')\
"
|
||||||
|
nnoremap <silent> <expr> <Plug>(GitGutterPrevHunk) &diff ? '[c' : ":\execute v:count1 . 'GitGutterPrevHunk'\
"
|
||||||
|
nnoremap <silent> <expr> <Plug>GitGutterNextHunk &diff ? ']c' : ":\call gitgutter#utility#warn('Please change your map \<Plug>GitGutterNextHunk to \<Plug>(GitGutterNextHunk)')\
"
|
||||||
|
nnoremap <silent> <expr> <Plug>(GitGutterNextHunk) &diff ? ']c' : ":\execute v:count1 . 'GitGutterNextHunk'\
"
|
||||||
|
xnoremap <silent> <Plug>(GitGutterTextObjectOuterVisual) :call gitgutter#hunk#text_object(0)
|
||||||
|
xnoremap <silent> <Plug>(GitGutterTextObjectInnerVisual) :call gitgutter#hunk#text_object(1)
|
||||||
|
onoremap <silent> <Plug>(GitGutterTextObjectOuterPending) :call gitgutter#hunk#text_object(0)
|
||||||
|
onoremap <silent> <Plug>(GitGutterTextObjectInnerPending) :call gitgutter#hunk#text_object(1)
|
||||||
|
vmap <BS> "-d
|
||||||
|
vmap <D-x> "*d
|
||||||
|
vmap <D-c> "*y
|
||||||
|
vmap <D-v> "-d"*P
|
||||||
|
nmap <D-v> "*P
|
||||||
|
inoremap <expr>
complete_info()["selected"] != "-1" ? "\" : "\u\
"
|
||||||
|
inoremap ,s :w
|
||||||
|
let &cpo=s:cpo_save
|
||||||
|
unlet s:cpo_save
|
||||||
|
set autoindent
|
||||||
|
set background=dark
|
||||||
|
set backspace=2
|
||||||
|
set clipboard=unnamed
|
||||||
|
set expandtab
|
||||||
|
set fileencodings=ucs-bom,utf-8,default,latin1
|
||||||
|
set helplang=en
|
||||||
|
set hlsearch
|
||||||
|
set ignorecase
|
||||||
|
set laststatus=2
|
||||||
|
set modelines=0
|
||||||
|
set path=.,/usr/include,,,**
|
||||||
|
set runtimepath=~/.vim,~/.vim/plugged/vim-gitgutter/,~/.vim/plugged/ctrlsf.vim/,~/.vim/plugged/lightline.vim/,~/.vim/plugged/auto-pairs/,~/.vim/plugged/fzf/,~/.vim/plugged/fzf.vim/,~/.vim/plugged/goyo.vim/,~/.vim/plugged/gv.vim/,~/.vim/plugged/seoul256.vim/,~/.vim/plugged/vim-swap/,~/.vim/plugged/tagbar/,~/.vim/plugged/coc.nvim/,~/.vim/plugged/vim-fugitive/,~/.vim/plugged/vim-indent-guides/,/usr/share/vim/vimfiles,/usr/share/vim/vim82,/usr/share/vim/vimfiles/after,~/.vim/plugged/ctrlsf.vim/after,~/.vim/after
|
||||||
|
set shiftwidth=4
|
||||||
|
set smartcase
|
||||||
|
set noswapfile
|
||||||
|
set tabline=%!lightline#tabline()
|
||||||
|
set tabstop=4
|
||||||
|
set title
|
||||||
|
set updatetime=100
|
||||||
|
set wildignore=*.pyc
|
||||||
|
set wildmenu
|
||||||
|
set wildmode=longest:list,full
|
||||||
|
set window=0
|
||||||
|
set nowritebackup
|
||||||
|
let s:so_save = &so | let s:siso_save = &siso | set so=0 siso=0
|
||||||
|
let v:this_session=expand("<sfile>:p")
|
||||||
|
silent only
|
||||||
|
silent tabonly
|
||||||
|
cd ~/src/gita
|
||||||
|
if expand('%') == '' && !&modified && line('$') <= 1 && getline(1) == ''
|
||||||
|
let s:wipebuf = bufnr('%')
|
||||||
|
endif
|
||||||
|
set shortmess=aoO
|
||||||
|
argglobal
|
||||||
|
%argdel
|
||||||
|
$argadd gita/__main__.py
|
||||||
|
set stal=2
|
||||||
|
tabnew
|
||||||
|
tabrewind
|
||||||
|
edit gita/utils.py
|
||||||
|
set splitbelow splitright
|
||||||
|
wincmd _ | wincmd |
|
||||||
|
vsplit
|
||||||
|
1wincmd h
|
||||||
|
wincmd w
|
||||||
|
set nosplitbelow
|
||||||
|
set nosplitright
|
||||||
|
wincmd t
|
||||||
|
set winminheight=0
|
||||||
|
set winheight=1
|
||||||
|
set winminwidth=0
|
||||||
|
set winwidth=1
|
||||||
|
exe 'vert 1resize ' . ((&columns * 94 + 94) / 188)
|
||||||
|
exe 'vert 2resize ' . ((&columns * 93 + 94) / 188)
|
||||||
|
argglobal
|
||||||
|
let s:cpo_save=&cpo
|
||||||
|
set cpo&vim
|
||||||
|
inoremap <buffer> <silent> <M-n> :call AutoPairsJump()
a
|
||||||
|
inoremap <buffer> <silent> <expr> <M-p> AutoPairsToggle()
|
||||||
|
inoremap <buffer> <silent> <M-b> =AutoPairsBackInsert()
|
||||||
|
inoremap <buffer> <silent> <C-W> =AutoPairsFastWrap()
|
||||||
|
inoremap <buffer> <silent> <C-H> =AutoPairsDelete()
|
||||||
|
inoremap <buffer> <silent> <BS> =AutoPairsDelete()
|
||||||
|
inoremap <buffer> <silent> <M-'> =AutoPairsMoveCharacter('''')
|
||||||
|
inoremap <buffer> <silent> <M-"> =AutoPairsMoveCharacter('"')
|
||||||
|
inoremap <buffer> <silent> <M-}> =AutoPairsMoveCharacter('}')
|
||||||
|
inoremap <buffer> <silent> <M-{> =AutoPairsMoveCharacter('{')
|
||||||
|
inoremap <buffer> <silent> <M-]> =AutoPairsMoveCharacter(']')
|
||||||
|
inoremap <buffer> <silent> <M-[> =AutoPairsMoveCharacter('[')
|
||||||
|
inoremap <buffer> <silent> <M-)> =AutoPairsMoveCharacter(')')
|
||||||
|
inoremap <buffer> <silent> <M-(> =AutoPairsMoveCharacter('(')
|
||||||
|
nmap <buffer> ,hp <Plug>(GitGutterPreviewHunk)
|
||||||
|
nmap <buffer> ,hu <Plug>(GitGutterUndoHunk)
|
||||||
|
nmap <buffer> ,hs <Plug>(GitGutterStageHunk)
|
||||||
|
xmap <buffer> ,hs <Plug>(GitGutterStageHunk)
|
||||||
|
inoremap <buffer> <silent> § =AutoPairsMoveCharacter('''')
|
||||||
|
inoremap <buffer> <silent> ¢ =AutoPairsMoveCharacter('"')
|
||||||
|
inoremap <buffer> <silent> © =AutoPairsMoveCharacter(')')
|
||||||
|
inoremap <buffer> <silent> ¨ =AutoPairsMoveCharacter('(')
|
||||||
|
inoremap <buffer> <silent> î :call AutoPairsJump()
a
|
||||||
|
inoremap <buffer> <silent> <expr> ð AutoPairsToggle()
|
||||||
|
inoremap <buffer> <silent> â =AutoPairsBackInsert()
|
||||||
|
inoremap <buffer> <silent> ý =AutoPairsMoveCharacter('}')
|
||||||
|
inoremap <buffer> <silent> û =AutoPairsMoveCharacter('{')
|
||||||
|
inoremap <buffer> <silent> Ý =AutoPairsMoveCharacter(']')
|
||||||
|
inoremap <buffer> <silent> Û =AutoPairsMoveCharacter('[')
|
||||||
|
nmap <buffer> [c <Plug>(GitGutterPrevHunk)
|
||||||
|
nmap <buffer> ]c <Plug>(GitGutterNextHunk)
|
||||||
|
xmap <buffer> ac <Plug>(GitGutterTextObjectOuterVisual)
|
||||||
|
omap <buffer> ac <Plug>(GitGutterTextObjectOuterPending)
|
||||||
|
xmap <buffer> ic <Plug>(GitGutterTextObjectInnerVisual)
|
||||||
|
omap <buffer> ic <Plug>(GitGutterTextObjectInnerPending)
|
||||||
|
noremap <buffer> <silent> <M-n> :call AutoPairsJump()
|
||||||
|
noremap <buffer> <silent> <M-p> :call AutoPairsToggle()
|
||||||
|
inoremap <buffer> <silent> =AutoPairsDelete()
|
||||||
|
inoremap <buffer> <silent> =AutoPairsFastWrap()
|
||||||
|
inoremap <buffer> <silent> =AutoPairsSpace()
|
||||||
|
inoremap <buffer> <silent> " =AutoPairsInsert('"')
|
||||||
|
inoremap <buffer> <silent> ' =AutoPairsInsert('''')
|
||||||
|
inoremap <buffer> <silent> ( =AutoPairsInsert('(')
|
||||||
|
inoremap <buffer> <silent> ) =AutoPairsInsert(')')
|
||||||
|
noremap <buffer> <silent> î :call AutoPairsJump()
|
||||||
|
noremap <buffer> <silent> ð :call AutoPairsToggle()
|
||||||
|
inoremap <buffer> <silent> [ =AutoPairsInsert('[')
|
||||||
|
inoremap <buffer> <silent> ] =AutoPairsInsert(']')
|
||||||
|
inoremap <buffer> <silent> ` =AutoPairsInsert('`')
|
||||||
|
inoremap <buffer> <silent> { =AutoPairsInsert('{')
|
||||||
|
inoremap <buffer> <silent> } =AutoPairsInsert('}')
|
||||||
|
let &cpo=s:cpo_save
|
||||||
|
unlet s:cpo_save
|
||||||
|
setlocal autoindent
|
||||||
|
setlocal backupcopy=
|
||||||
|
setlocal nobinary
|
||||||
|
setlocal nobreakindent
|
||||||
|
setlocal breakindentopt=
|
||||||
|
setlocal bufhidden=
|
||||||
|
setlocal buflisted
|
||||||
|
setlocal buftype=
|
||||||
|
setlocal nocindent
|
||||||
|
setlocal cinkeys=0{,0},0),0],:,!^F,o,O,e
|
||||||
|
setlocal cinoptions=
|
||||||
|
setlocal cinwords=if,else,while,do,for,switch
|
||||||
|
set colorcolumn=80
|
||||||
|
setlocal colorcolumn=80
|
||||||
|
setlocal comments=b:#,fb:-
|
||||||
|
setlocal commentstring=#\ %s
|
||||||
|
setlocal complete=.,w,b,u,t,i
|
||||||
|
setlocal completefunc=
|
||||||
|
setlocal nocopyindent
|
||||||
|
setlocal cryptmethod=
|
||||||
|
setlocal nocursorbind
|
||||||
|
setlocal nocursorcolumn
|
||||||
|
setlocal nocursorline
|
||||||
|
setlocal cursorlineopt=both
|
||||||
|
setlocal define=
|
||||||
|
setlocal dictionary=
|
||||||
|
setlocal nodiff
|
||||||
|
setlocal equalprg=
|
||||||
|
setlocal errorformat=
|
||||||
|
setlocal expandtab
|
||||||
|
if &filetype != 'python'
|
||||||
|
setlocal filetype=python
|
||||||
|
endif
|
||||||
|
setlocal fixendofline
|
||||||
|
setlocal foldcolumn=0
|
||||||
|
set nofoldenable
|
||||||
|
setlocal nofoldenable
|
||||||
|
setlocal foldexpr=0
|
||||||
|
setlocal foldignore=#
|
||||||
|
setlocal foldlevel=0
|
||||||
|
setlocal foldmarker={{{,}}}
|
||||||
|
set foldmethod=indent
|
||||||
|
setlocal foldmethod=indent
|
||||||
|
setlocal foldminlines=1
|
||||||
|
setlocal foldnestmax=20
|
||||||
|
setlocal foldtext=foldtext()
|
||||||
|
setlocal formatexpr=
|
||||||
|
setlocal formatoptions=tcq
|
||||||
|
setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
|
||||||
|
setlocal formatprg=
|
||||||
|
setlocal grepprg=
|
||||||
|
setlocal iminsert=0
|
||||||
|
setlocal imsearch=-1
|
||||||
|
setlocal include=^\\s*\\(from\\|import\\)
|
||||||
|
setlocal includeexpr=substitute(substitute(substitute(v:fname,b:grandparent_match,b:grandparent_sub,''),b:parent_match,b:parent_sub,''),b:child_match,b:child_sub,'g')
|
||||||
|
setlocal indentexpr=GetPythonIndent(v:lnum)
|
||||||
|
setlocal indentkeys=0{,0},0),0],:,!^F,o,O,e,<:>,=elif,=except
|
||||||
|
setlocal noinfercase
|
||||||
|
setlocal iskeyword=@,48-57,_,192-255
|
||||||
|
setlocal keywordprg=pydoc
|
||||||
|
setlocal nolinebreak
|
||||||
|
setlocal nolisp
|
||||||
|
setlocal lispwords=
|
||||||
|
setlocal nolist
|
||||||
|
setlocal makeencoding=
|
||||||
|
setlocal makeprg=
|
||||||
|
setlocal matchpairs=(:),{:},[:]
|
||||||
|
setlocal modeline
|
||||||
|
setlocal modifiable
|
||||||
|
setlocal nrformats=bin,octal,hex
|
||||||
|
set number
|
||||||
|
setlocal number
|
||||||
|
setlocal numberwidth=4
|
||||||
|
setlocal omnifunc=pythoncomplete#Complete
|
||||||
|
setlocal path=
|
||||||
|
setlocal nopreserveindent
|
||||||
|
setlocal nopreviewwindow
|
||||||
|
setlocal quoteescape=\\
|
||||||
|
setlocal noreadonly
|
||||||
|
setlocal norelativenumber
|
||||||
|
setlocal noscrollbind
|
||||||
|
setlocal scrolloff=-1
|
||||||
|
setlocal shiftwidth=4
|
||||||
|
setlocal noshortname
|
||||||
|
setlocal showbreak=
|
||||||
|
setlocal sidescrolloff=-1
|
||||||
|
setlocal signcolumn=auto
|
||||||
|
setlocal nosmartindent
|
||||||
|
setlocal softtabstop=4
|
||||||
|
set spell
|
||||||
|
setlocal spell
|
||||||
|
setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
|
||||||
|
setlocal spellfile=
|
||||||
|
setlocal spelllang=en
|
||||||
|
setlocal statusline=%{lightline#link()}%#LightlineLeft_active_0#%(\ %{lightline#mode()}\ %)%{(&paste)?\"|\":\"\"}%(\ %{&paste?\"PASTE\":\"\"}\ %)%#LightlineLeft_active_0_1#%#LightlineLeft_active_1#%(\ %R\ %)%{(&readonly)&&(1||(&modified||!&modifiable))?\"|\":\"\"}%(\ %t\ %)%{(&modified||!&modifiable)?\"|\":\"\"}%(\ %M\ %)%#LightlineLeft_active_1_2#%#LightlineMiddle_active#%=%#LightlineRight_active_2_3#%#LightlineRight_active_2#%(\ %{&ff}\ %)%{1||1?\"|\":\"\"}%(\ %{&fenc!=#\"\"?&fenc:&enc}\ %)%{1?\"|\":\"\"}%(\ %{&ft!=#\"\"?&ft:\"no\ ft\"}\ %)%#LightlineRight_active_1_2#%#LightlineRight_active_1#%(\ %3p%%\ %)%#LightlineRight_active_0_1#%#LightlineRight_active_0#%(\ %3l:%-2c\ %)
|
||||||
|
setlocal suffixesadd=.py
|
||||||
|
setlocal noswapfile
|
||||||
|
setlocal synmaxcol=3000
|
||||||
|
if &syntax != 'python'
|
||||||
|
setlocal syntax=python
|
||||||
|
endif
|
||||||
|
setlocal tabstop=8
|
||||||
|
setlocal tagcase=
|
||||||
|
setlocal tagfunc=
|
||||||
|
setlocal tags=
|
||||||
|
setlocal termwinkey=
|
||||||
|
setlocal termwinscroll=10000
|
||||||
|
setlocal termwinsize=
|
||||||
|
setlocal textwidth=0
|
||||||
|
setlocal thesaurus=
|
||||||
|
setlocal noundofile
|
||||||
|
setlocal undolevels=-123456
|
||||||
|
setlocal wincolor=
|
||||||
|
setlocal nowinfixheight
|
||||||
|
setlocal nowinfixwidth
|
||||||
|
set nowrap
|
||||||
|
setlocal nowrap
|
||||||
|
setlocal wrapmargin=0
|
||||||
|
let s:l = 83 - ((21 * winheight(0) + 21) / 43)
|
||||||
|
if s:l < 1 | let s:l = 1 | endif
|
||||||
|
exe s:l
|
||||||
|
normal! zt
|
||||||
|
83
|
||||||
|
normal! 05|
|
||||||
|
wincmd w
|
||||||
|
argglobal
|
||||||
|
if bufexists("gita/__main__.py") | buffer gita/__main__.py | else | edit gita/__main__.py | endif
|
||||||
|
let s:cpo_save=&cpo
|
||||||
|
set cpo&vim
|
||||||
|
inoremap <buffer> <silent> <M-n> :call AutoPairsJump()
a
|
||||||
|
inoremap <buffer> <silent> <expr> <M-p> AutoPairsToggle()
|
||||||
|
inoremap <buffer> <silent> <M-b> =AutoPairsBackInsert()
|
||||||
|
inoremap <buffer> <silent> <C-W> =AutoPairsFastWrap()
|
||||||
|
inoremap <buffer> <silent> <C-H> =AutoPairsDelete()
|
||||||
|
inoremap <buffer> <silent> <BS> =AutoPairsDelete()
|
||||||
|
inoremap <buffer> <silent> <M-'> =AutoPairsMoveCharacter('''')
|
||||||
|
inoremap <buffer> <silent> <M-"> =AutoPairsMoveCharacter('"')
|
||||||
|
inoremap <buffer> <silent> <M-}> =AutoPairsMoveCharacter('}')
|
||||||
|
inoremap <buffer> <silent> <M-{> =AutoPairsMoveCharacter('{')
|
||||||
|
inoremap <buffer> <silent> <M-]> =AutoPairsMoveCharacter(']')
|
||||||
|
inoremap <buffer> <silent> <M-[> =AutoPairsMoveCharacter('[')
|
||||||
|
inoremap <buffer> <silent> <M-)> =AutoPairsMoveCharacter(')')
|
||||||
|
inoremap <buffer> <silent> <M-(> =AutoPairsMoveCharacter('(')
|
||||||
|
nmap <buffer> ,hp <Plug>(GitGutterPreviewHunk)
|
||||||
|
nmap <buffer> ,hu <Plug>(GitGutterUndoHunk)
|
||||||
|
nmap <buffer> ,hs <Plug>(GitGutterStageHunk)
|
||||||
|
xmap <buffer> ,hs <Plug>(GitGutterStageHunk)
|
||||||
|
inoremap <buffer> <silent> § =AutoPairsMoveCharacter('''')
|
||||||
|
inoremap <buffer> <silent> ¢ =AutoPairsMoveCharacter('"')
|
||||||
|
inoremap <buffer> <silent> © =AutoPairsMoveCharacter(')')
|
||||||
|
inoremap <buffer> <silent> ¨ =AutoPairsMoveCharacter('(')
|
||||||
|
inoremap <buffer> <silent> î :call AutoPairsJump()
a
|
||||||
|
inoremap <buffer> <silent> <expr> ð AutoPairsToggle()
|
||||||
|
inoremap <buffer> <silent> â =AutoPairsBackInsert()
|
||||||
|
inoremap <buffer> <silent> ý =AutoPairsMoveCharacter('}')
|
||||||
|
inoremap <buffer> <silent> û =AutoPairsMoveCharacter('{')
|
||||||
|
inoremap <buffer> <silent> Ý =AutoPairsMoveCharacter(']')
|
||||||
|
inoremap <buffer> <silent> Û =AutoPairsMoveCharacter('[')
|
||||||
|
nmap <buffer> [c <Plug>(GitGutterPrevHunk)
|
||||||
|
nmap <buffer> ]c <Plug>(GitGutterNextHunk)
|
||||||
|
xmap <buffer> ac <Plug>(GitGutterTextObjectOuterVisual)
|
||||||
|
omap <buffer> ac <Plug>(GitGutterTextObjectOuterPending)
|
||||||
|
xmap <buffer> ic <Plug>(GitGutterTextObjectInnerVisual)
|
||||||
|
omap <buffer> ic <Plug>(GitGutterTextObjectInnerPending)
|
||||||
|
noremap <buffer> <silent> <M-n> :call AutoPairsJump()
|
||||||
|
noremap <buffer> <silent> <M-p> :call AutoPairsToggle()
|
||||||
|
inoremap <buffer> <silent> =AutoPairsDelete()
|
||||||
|
inoremap <buffer> <silent> =AutoPairsFastWrap()
|
||||||
|
inoremap <buffer> <silent> =AutoPairsSpace()
|
||||||
|
inoremap <buffer> <silent> " =AutoPairsInsert('"')
|
||||||
|
inoremap <buffer> <silent> ' =AutoPairsInsert('''')
|
||||||
|
inoremap <buffer> <silent> ( =AutoPairsInsert('(')
|
||||||
|
inoremap <buffer> <silent> ) =AutoPairsInsert(')')
|
||||||
|
noremap <buffer> <silent> î :call AutoPairsJump()
|
||||||
|
noremap <buffer> <silent> ð :call AutoPairsToggle()
|
||||||
|
inoremap <buffer> <silent> [ =AutoPairsInsert('[')
|
||||||
|
inoremap <buffer> <silent> ] =AutoPairsInsert(']')
|
||||||
|
inoremap <buffer> <silent> ` =AutoPairsInsert('`')
|
||||||
|
inoremap <buffer> <silent> { =AutoPairsInsert('{')
|
||||||
|
inoremap <buffer> <silent> } =AutoPairsInsert('}')
|
||||||
|
let &cpo=s:cpo_save
|
||||||
|
unlet s:cpo_save
|
||||||
|
setlocal autoindent
|
||||||
|
setlocal backupcopy=
|
||||||
|
setlocal nobinary
|
||||||
|
setlocal nobreakindent
|
||||||
|
setlocal breakindentopt=
|
||||||
|
setlocal bufhidden=
|
||||||
|
setlocal buflisted
|
||||||
|
setlocal buftype=
|
||||||
|
setlocal nocindent
|
||||||
|
setlocal cinkeys=0{,0},0),0],:,!^F,o,O,e
|
||||||
|
setlocal cinoptions=
|
||||||
|
setlocal cinwords=if,else,while,do,for,switch
|
||||||
|
set colorcolumn=0
|
||||||
|
setlocal colorcolumn=0
|
||||||
|
setlocal comments=b:#,fb:-
|
||||||
|
setlocal commentstring=#\ %s
|
||||||
|
setlocal complete=.,w,b,u,t,i
|
||||||
|
setlocal completefunc=
|
||||||
|
setlocal nocopyindent
|
||||||
|
setlocal cryptmethod=
|
||||||
|
setlocal nocursorbind
|
||||||
|
setlocal nocursorcolumn
|
||||||
|
setlocal nocursorline
|
||||||
|
setlocal cursorlineopt=both
|
||||||
|
setlocal define=
|
||||||
|
setlocal dictionary=
|
||||||
|
setlocal nodiff
|
||||||
|
setlocal equalprg=
|
||||||
|
setlocal errorformat=
|
||||||
|
setlocal expandtab
|
||||||
|
if &filetype != 'python'
|
||||||
|
setlocal filetype=python
|
||||||
|
endif
|
||||||
|
setlocal fixendofline
|
||||||
|
setlocal foldcolumn=0
|
||||||
|
set nofoldenable
|
||||||
|
setlocal nofoldenable
|
||||||
|
setlocal foldexpr=0
|
||||||
|
setlocal foldignore=#
|
||||||
|
setlocal foldlevel=0
|
||||||
|
setlocal foldmarker={{{,}}}
|
||||||
|
set foldmethod=indent
|
||||||
|
setlocal foldmethod=indent
|
||||||
|
setlocal foldminlines=1
|
||||||
|
setlocal foldnestmax=20
|
||||||
|
setlocal foldtext=foldtext()
|
||||||
|
setlocal formatexpr=
|
||||||
|
setlocal formatoptions=tcq
|
||||||
|
setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
|
||||||
|
setlocal formatprg=
|
||||||
|
setlocal grepprg=
|
||||||
|
setlocal iminsert=0
|
||||||
|
setlocal imsearch=-1
|
||||||
|
setlocal include=^\\s*\\(from\\|import\\)
|
||||||
|
setlocal includeexpr=substitute(substitute(substitute(v:fname,b:grandparent_match,b:grandparent_sub,''),b:parent_match,b:parent_sub,''),b:child_match,b:child_sub,'g')
|
||||||
|
setlocal indentexpr=GetPythonIndent(v:lnum)
|
||||||
|
setlocal indentkeys=0{,0},0),0],:,!^F,o,O,e,<:>,=elif,=except
|
||||||
|
setlocal noinfercase
|
||||||
|
setlocal iskeyword=@,48-57,_,192-255
|
||||||
|
setlocal keywordprg=pydoc
|
||||||
|
setlocal nolinebreak
|
||||||
|
setlocal nolisp
|
||||||
|
setlocal lispwords=
|
||||||
|
setlocal nolist
|
||||||
|
setlocal makeencoding=
|
||||||
|
setlocal makeprg=
|
||||||
|
setlocal matchpairs=(:),{:},[:]
|
||||||
|
setlocal modeline
|
||||||
|
setlocal modifiable
|
||||||
|
setlocal nrformats=bin,octal,hex
|
||||||
|
set number
|
||||||
|
setlocal number
|
||||||
|
setlocal numberwidth=4
|
||||||
|
setlocal omnifunc=pythoncomplete#Complete
|
||||||
|
setlocal path=
|
||||||
|
setlocal nopreserveindent
|
||||||
|
setlocal nopreviewwindow
|
||||||
|
setlocal quoteescape=\\
|
||||||
|
setlocal noreadonly
|
||||||
|
setlocal norelativenumber
|
||||||
|
setlocal noscrollbind
|
||||||
|
setlocal scrolloff=-1
|
||||||
|
setlocal shiftwidth=4
|
||||||
|
setlocal noshortname
|
||||||
|
setlocal showbreak=
|
||||||
|
setlocal sidescrolloff=-1
|
||||||
|
setlocal signcolumn=auto
|
||||||
|
setlocal nosmartindent
|
||||||
|
setlocal softtabstop=4
|
||||||
|
set spell
|
||||||
|
setlocal spell
|
||||||
|
setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
|
||||||
|
setlocal spellfile=
|
||||||
|
setlocal spelllang=en
|
||||||
|
setlocal statusline=%{lightline#link()}%#LightlineLeft_inactive_0#%(\ %t\ %)%#LightlineLeft_inactive_0_1#%#LightlineMiddle_inactive#%=%#LightlineRight_inactive_1_2#%#LightlineRight_inactive_1#%(\ %3p%%\ %)%#LightlineRight_inactive_0_1#%#LightlineRight_inactive_0#%(\ %3l:%-2c\ %)
|
||||||
|
setlocal suffixesadd=.py
|
||||||
|
setlocal noswapfile
|
||||||
|
setlocal synmaxcol=3000
|
||||||
|
if &syntax != 'python'
|
||||||
|
setlocal syntax=python
|
||||||
|
endif
|
||||||
|
setlocal tabstop=8
|
||||||
|
setlocal tagcase=
|
||||||
|
setlocal tagfunc=
|
||||||
|
setlocal tags=
|
||||||
|
setlocal termwinkey=
|
||||||
|
setlocal termwinscroll=10000
|
||||||
|
setlocal termwinsize=
|
||||||
|
setlocal textwidth=0
|
||||||
|
setlocal thesaurus=
|
||||||
|
setlocal noundofile
|
||||||
|
setlocal undolevels=-123456
|
||||||
|
setlocal wincolor=
|
||||||
|
setlocal nowinfixheight
|
||||||
|
setlocal nowinfixwidth
|
||||||
|
set nowrap
|
||||||
|
setlocal nowrap
|
||||||
|
setlocal wrapmargin=0
|
||||||
|
let s:l = 34 - ((32 * winheight(0) + 21) / 43)
|
||||||
|
if s:l < 1 | let s:l = 1 | endif
|
||||||
|
exe s:l
|
||||||
|
normal! zt
|
||||||
|
34
|
||||||
|
normal! 09|
|
||||||
|
wincmd w
|
||||||
|
exe 'vert 1resize ' . ((&columns * 94 + 94) / 188)
|
||||||
|
exe 'vert 2resize ' . ((&columns * 93 + 94) / 188)
|
||||||
|
tabnext
|
||||||
|
edit gita/common.py
|
||||||
|
set splitbelow splitright
|
||||||
|
set nosplitbelow
|
||||||
|
set nosplitright
|
||||||
|
wincmd t
|
||||||
|
set winminheight=0
|
||||||
|
set winheight=1
|
||||||
|
set winminwidth=0
|
||||||
|
set winwidth=1
|
||||||
|
argglobal
|
||||||
|
let s:cpo_save=&cpo
|
||||||
|
set cpo&vim
|
||||||
|
inoremap <buffer> <silent> <M-n> :call AutoPairsJump()
a
|
||||||
|
inoremap <buffer> <silent> <expr> <M-p> AutoPairsToggle()
|
||||||
|
inoremap <buffer> <silent> <M-b> =AutoPairsBackInsert()
|
||||||
|
inoremap <buffer> <silent> <C-W> =AutoPairsFastWrap()
|
||||||
|
inoremap <buffer> <silent> <C-H> =AutoPairsDelete()
|
||||||
|
inoremap <buffer> <silent> <BS> =AutoPairsDelete()
|
||||||
|
inoremap <buffer> <silent> <M-'> =AutoPairsMoveCharacter('''')
|
||||||
|
inoremap <buffer> <silent> <M-"> =AutoPairsMoveCharacter('"')
|
||||||
|
inoremap <buffer> <silent> <M-}> =AutoPairsMoveCharacter('}')
|
||||||
|
inoremap <buffer> <silent> <M-{> =AutoPairsMoveCharacter('{')
|
||||||
|
inoremap <buffer> <silent> <M-]> =AutoPairsMoveCharacter(']')
|
||||||
|
inoremap <buffer> <silent> <M-[> =AutoPairsMoveCharacter('[')
|
||||||
|
inoremap <buffer> <silent> <M-)> =AutoPairsMoveCharacter(')')
|
||||||
|
inoremap <buffer> <silent> <M-(> =AutoPairsMoveCharacter('(')
|
||||||
|
nmap <buffer> ,hp <Plug>(GitGutterPreviewHunk)
|
||||||
|
nmap <buffer> ,hu <Plug>(GitGutterUndoHunk)
|
||||||
|
nmap <buffer> ,hs <Plug>(GitGutterStageHunk)
|
||||||
|
xmap <buffer> ,hs <Plug>(GitGutterStageHunk)
|
||||||
|
inoremap <buffer> <silent> § =AutoPairsMoveCharacter('''')
|
||||||
|
inoremap <buffer> <silent> ¢ =AutoPairsMoveCharacter('"')
|
||||||
|
inoremap <buffer> <silent> © =AutoPairsMoveCharacter(')')
|
||||||
|
inoremap <buffer> <silent> ¨ =AutoPairsMoveCharacter('(')
|
||||||
|
inoremap <buffer> <silent> î :call AutoPairsJump()
a
|
||||||
|
inoremap <buffer> <silent> <expr> ð AutoPairsToggle()
|
||||||
|
inoremap <buffer> <silent> â =AutoPairsBackInsert()
|
||||||
|
inoremap <buffer> <silent> ý =AutoPairsMoveCharacter('}')
|
||||||
|
inoremap <buffer> <silent> û =AutoPairsMoveCharacter('{')
|
||||||
|
inoremap <buffer> <silent> Ý =AutoPairsMoveCharacter(']')
|
||||||
|
inoremap <buffer> <silent> Û =AutoPairsMoveCharacter('[')
|
||||||
|
nmap <buffer> [c <Plug>(GitGutterPrevHunk)
|
||||||
|
nmap <buffer> ]c <Plug>(GitGutterNextHunk)
|
||||||
|
xmap <buffer> ac <Plug>(GitGutterTextObjectOuterVisual)
|
||||||
|
omap <buffer> ac <Plug>(GitGutterTextObjectOuterPending)
|
||||||
|
xmap <buffer> ic <Plug>(GitGutterTextObjectInnerVisual)
|
||||||
|
omap <buffer> ic <Plug>(GitGutterTextObjectInnerPending)
|
||||||
|
noremap <buffer> <silent> <M-n> :call AutoPairsJump()
|
||||||
|
noremap <buffer> <silent> <M-p> :call AutoPairsToggle()
|
||||||
|
inoremap <buffer> <silent> =AutoPairsDelete()
|
||||||
|
inoremap <buffer> <silent> =AutoPairsFastWrap()
|
||||||
|
inoremap <buffer> <silent> =AutoPairsSpace()
|
||||||
|
inoremap <buffer> <silent> " =AutoPairsInsert('"')
|
||||||
|
inoremap <buffer> <silent> ' =AutoPairsInsert('''')
|
||||||
|
inoremap <buffer> <silent> ( =AutoPairsInsert('(')
|
||||||
|
inoremap <buffer> <silent> ) =AutoPairsInsert(')')
|
||||||
|
noremap <buffer> <silent> î :call AutoPairsJump()
|
||||||
|
noremap <buffer> <silent> ð :call AutoPairsToggle()
|
||||||
|
inoremap <buffer> <silent> [ =AutoPairsInsert('[')
|
||||||
|
inoremap <buffer> <silent> ] =AutoPairsInsert(']')
|
||||||
|
inoremap <buffer> <silent> ` =AutoPairsInsert('`')
|
||||||
|
inoremap <buffer> <silent> { =AutoPairsInsert('{')
|
||||||
|
inoremap <buffer> <silent> } =AutoPairsInsert('}')
|
||||||
|
let &cpo=s:cpo_save
|
||||||
|
unlet s:cpo_save
|
||||||
|
setlocal autoindent
|
||||||
|
setlocal backupcopy=
|
||||||
|
setlocal nobinary
|
||||||
|
setlocal nobreakindent
|
||||||
|
setlocal breakindentopt=
|
||||||
|
setlocal bufhidden=
|
||||||
|
setlocal buflisted
|
||||||
|
setlocal buftype=
|
||||||
|
setlocal nocindent
|
||||||
|
setlocal cinkeys=0{,0},0),0],:,!^F,o,O,e
|
||||||
|
setlocal cinoptions=
|
||||||
|
setlocal cinwords=if,else,while,do,for,switch
|
||||||
|
set colorcolumn=0
|
||||||
|
setlocal colorcolumn=0
|
||||||
|
setlocal comments=b:#,fb:-
|
||||||
|
setlocal commentstring=#\ %s
|
||||||
|
setlocal complete=.,w,b,u,t,i
|
||||||
|
setlocal completefunc=
|
||||||
|
setlocal nocopyindent
|
||||||
|
setlocal cryptmethod=
|
||||||
|
setlocal nocursorbind
|
||||||
|
setlocal nocursorcolumn
|
||||||
|
setlocal nocursorline
|
||||||
|
setlocal cursorlineopt=both
|
||||||
|
setlocal define=
|
||||||
|
setlocal dictionary=
|
||||||
|
setlocal nodiff
|
||||||
|
setlocal equalprg=
|
||||||
|
setlocal errorformat=
|
||||||
|
setlocal expandtab
|
||||||
|
if &filetype != 'python'
|
||||||
|
setlocal filetype=python
|
||||||
|
endif
|
||||||
|
setlocal fixendofline
|
||||||
|
setlocal foldcolumn=0
|
||||||
|
set nofoldenable
|
||||||
|
setlocal nofoldenable
|
||||||
|
setlocal foldexpr=0
|
||||||
|
setlocal foldignore=#
|
||||||
|
setlocal foldlevel=0
|
||||||
|
setlocal foldmarker={{{,}}}
|
||||||
|
set foldmethod=indent
|
||||||
|
setlocal foldmethod=indent
|
||||||
|
setlocal foldminlines=1
|
||||||
|
setlocal foldnestmax=20
|
||||||
|
setlocal foldtext=foldtext()
|
||||||
|
setlocal formatexpr=
|
||||||
|
setlocal formatoptions=tcq
|
||||||
|
setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
|
||||||
|
setlocal formatprg=
|
||||||
|
setlocal grepprg=
|
||||||
|
setlocal iminsert=0
|
||||||
|
setlocal imsearch=-1
|
||||||
|
setlocal include=^\\s*\\(from\\|import\\)
|
||||||
|
setlocal includeexpr=substitute(substitute(substitute(v:fname,b:grandparent_match,b:grandparent_sub,''),b:parent_match,b:parent_sub,''),b:child_match,b:child_sub,'g')
|
||||||
|
setlocal indentexpr=GetPythonIndent(v:lnum)
|
||||||
|
setlocal indentkeys=0{,0},0),0],:,!^F,o,O,e,<:>,=elif,=except
|
||||||
|
setlocal noinfercase
|
||||||
|
setlocal iskeyword=@,48-57,_,192-255
|
||||||
|
setlocal keywordprg=pydoc
|
||||||
|
setlocal nolinebreak
|
||||||
|
setlocal nolisp
|
||||||
|
setlocal lispwords=
|
||||||
|
setlocal nolist
|
||||||
|
setlocal makeencoding=
|
||||||
|
setlocal makeprg=
|
||||||
|
setlocal matchpairs=(:),{:},[:]
|
||||||
|
setlocal modeline
|
||||||
|
setlocal modifiable
|
||||||
|
setlocal nrformats=bin,octal,hex
|
||||||
|
set number
|
||||||
|
setlocal number
|
||||||
|
setlocal numberwidth=4
|
||||||
|
setlocal omnifunc=pythoncomplete#Complete
|
||||||
|
setlocal path=
|
||||||
|
setlocal nopreserveindent
|
||||||
|
setlocal nopreviewwindow
|
||||||
|
setlocal quoteescape=\\
|
||||||
|
setlocal noreadonly
|
||||||
|
setlocal norelativenumber
|
||||||
|
setlocal noscrollbind
|
||||||
|
setlocal scrolloff=-1
|
||||||
|
setlocal shiftwidth=4
|
||||||
|
setlocal noshortname
|
||||||
|
setlocal showbreak=
|
||||||
|
setlocal sidescrolloff=-1
|
||||||
|
setlocal signcolumn=auto
|
||||||
|
setlocal nosmartindent
|
||||||
|
setlocal softtabstop=4
|
||||||
|
set spell
|
||||||
|
setlocal spell
|
||||||
|
setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
|
||||||
|
setlocal spellfile=
|
||||||
|
setlocal spelllang=en
|
||||||
|
setlocal statusline=%{lightline#link()}%#LightlineLeft_active_0#%(\ %{lightline#mode()}\ %)%{(&paste)?\"|\":\"\"}%(\ %{&paste?\"PASTE\":\"\"}\ %)%#LightlineLeft_active_0_1#%#LightlineLeft_active_1#%(\ %R\ %)%{(&readonly)&&(1||(&modified||!&modifiable))?\"|\":\"\"}%(\ %t\ %)%{(&modified||!&modifiable)?\"|\":\"\"}%(\ %M\ %)%#LightlineLeft_active_1_2#%#LightlineMiddle_active#%=%#LightlineRight_active_2_3#%#LightlineRight_active_2#%(\ %{&ff}\ %)%{1||1?\"|\":\"\"}%(\ %{&fenc!=#\"\"?&fenc:&enc}\ %)%{1?\"|\":\"\"}%(\ %{&ft!=#\"\"?&ft:\"no\ ft\"}\ %)%#LightlineRight_active_1_2#%#LightlineRight_active_1#%(\ %3p%%\ %)%#LightlineRight_active_0_1#%#LightlineRight_active_0#%(\ %3l:%-2c\ %)
|
||||||
|
setlocal suffixesadd=.py
|
||||||
|
setlocal noswapfile
|
||||||
|
setlocal synmaxcol=3000
|
||||||
|
if &syntax != 'python'
|
||||||
|
setlocal syntax=python
|
||||||
|
endif
|
||||||
|
setlocal tabstop=8
|
||||||
|
setlocal tagcase=
|
||||||
|
setlocal tagfunc=
|
||||||
|
setlocal tags=
|
||||||
|
setlocal termwinkey=
|
||||||
|
setlocal termwinscroll=10000
|
||||||
|
setlocal termwinsize=
|
||||||
|
setlocal textwidth=0
|
||||||
|
setlocal thesaurus=
|
||||||
|
setlocal noundofile
|
||||||
|
setlocal undolevels=-123456
|
||||||
|
setlocal wincolor=
|
||||||
|
setlocal nowinfixheight
|
||||||
|
setlocal nowinfixwidth
|
||||||
|
set nowrap
|
||||||
|
setlocal nowrap
|
||||||
|
setlocal wrapmargin=0
|
||||||
|
let s:l = 2 - ((1 * winheight(0) + 21) / 43)
|
||||||
|
if s:l < 1 | let s:l = 1 | endif
|
||||||
|
exe s:l
|
||||||
|
normal! zt
|
||||||
|
2
|
||||||
|
normal! 0
|
||||||
|
tabnext 1
|
||||||
|
set stal=1
|
||||||
|
badd +34 gita/__main__.py
|
||||||
|
badd +0 gita/utils.py
|
||||||
|
badd +0 gita/common.py
|
||||||
|
if exists('s:wipebuf') && len(win_findbuf(s:wipebuf)) == 0
|
||||||
|
silent exe 'bwipe ' . s:wipebuf
|
||||||
|
endif
|
||||||
|
unlet! s:wipebuf
|
||||||
|
set winheight=1 winwidth=20 shortmess=filnxtToOS
|
||||||
|
set winminheight=1 winminwidth=1
|
||||||
|
let s:sx = expand("<sfile>:p:r")."x.vim"
|
||||||
|
if filereadable(s:sx)
|
||||||
|
exe "source " . fnameescape(s:sx)
|
||||||
|
endif
|
||||||
|
let &so = s:so_save | let &siso = s:siso_save
|
||||||
|
nohlsearch
|
||||||
|
doautoall SessionLoadPost
|
||||||
|
unlet SessionLoad
|
||||||
|
" vim: set ft=vim :
|
Loading…
Add table
Add a link
Reference in a new issue