├── gcd.gif ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── garden.yaml ├── LICENSE ├── README.md └── gcd.sh /gcd.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davvid/gcd/HEAD/gcd.gif -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [davvid] 2 | patreon: daveinthesky 3 | -------------------------------------------------------------------------------- /garden.yaml: -------------------------------------------------------------------------------- 1 | # Use "cargo install garden-tools" to install garden (https://gitlab.com/garden-rs/garden). 2 | # Usage: 3 | # garden check 4 | commands: 5 | check: shellcheck --exclude=SC2148 gcd.sh 6 | setup: sudo apt install shellcheck 7 | 8 | trees: 9 | gcd: 10 | description: Git worktree navigator 11 | path: "${GARDEN_CONFIG_DIR}" 12 | url: "git@gitlab.com:davvid/gcd.git" 13 | remotes: 14 | github: "git@github.com:davvid/gcd.git" 15 | gitconfig: 16 | remote.publish.pushurl: 17 | - "git@gitlab.com:davvid/gcd.git" 18 | - "git@github.com:davvid/gcd.git" 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Test 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Cancel Previous Runs 11 | uses: styfle/cancel-workflow-action@0.12.1 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | - name: Install Rust 15 | run: rustup toolchain install stable --profile minimal --no-self-update 16 | - name: Install Garden 17 | run: cargo install garden-tools 18 | - uses: Swatinem/rust-cache@v2 19 | - name: Install Dependencies 20 | run: | 21 | set -x 22 | sudo apt-get update 23 | # Runtime dependencies (required) 24 | sudo apt-get install shellcheck 25 | - name: Run tests and checks 26 | run: garden -vv check 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024, David Aguilar 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gcd - Git worktree navigator 2 | 3 | `gcd` lets you quickly navigate to Git worktrees on your filesystem. 4 | 5 | `cdg` lets you quickly navigate to directories within your current worktree. 6 | 7 | ![Preview](gcd.gif) 8 | 9 | ## Dependencies 10 | 11 | * [eza](https://github.com/eza-community/eza) (required) 12 | 13 | * [fzf](https://github.com/junegunn/fzf) (required) 14 | 15 | * [fd](https://github.com/sharkdp/fd) (optional) 16 | 17 | ```bash 18 | sudo apt install eza fzf fd-find 19 | ``` 20 | 21 | ## Installation 22 | 23 | * Clone this repository to `~/.config/git/gcd`: 24 | 25 | ```bash 26 | mkdir -p ~/.config/git 27 | git clone https://gitlab.com/davvid/gcd.git ~/.config/git/gcd 28 | ``` 29 | 30 | * Edit your `~/.bashrc` or `~/.zshrc` and add this snippet: 31 | 32 | ```bash 33 | if test -f ~/.config/git/gcd/gcd.sh 34 | then 35 | source ~/.config/git/gcd/gcd.sh 36 | fi 37 | ``` 38 | 39 | ## Configuration 40 | 41 | `gcd` searches for worktrees in `$HOME` by default. 42 | 43 | `gcd` can be configured to search in other directories by setting the 44 | `gcd.paths` git config variable: 45 | 46 | ```bash 47 | git config --global --add gcd.paths '$HOME/src' 48 | git config --global --add gcd.paths '$HOME/dev' 49 | ``` 50 | 51 | `$VARIABLES` are expanded by `gcd` so that you can reuse your git configuration 52 | in more scenarios. 53 | 54 | `gcd` traverses two levels deep into the configured paths by default. 55 | You can configure `gcd` to search deeper by setting the `gcd.depth` variable. 56 | 57 | ```bash 58 | git config --global gcd.depth 3 59 | ``` 60 | 61 | *NOTE*: higher depth values cause more disk IO and can have an impact on performance. 62 | -------------------------------------------------------------------------------- /gcd.sh: -------------------------------------------------------------------------------- 1 | # This script is intended to be sourced into your ~/.zshrc or ~/.bashrc. 2 | 3 | # Search for worktrees and change directories. 4 | gcd () { 5 | __gcd_initialize 6 | __gcd_dir=$(__gcd_worktrees | __gcd_fzf "$@") 7 | __gcd_finalize 8 | if test -n "${__gcd_dir}" 9 | then 10 | cd "${__gcd_dir}" || return 0 11 | fi 12 | } 13 | 14 | # search for directories inside the current repository and change directories. 15 | # "cdg /" goes to the root of the current repository. 16 | cdg () { 17 | if test "$(git rev-parse --is-inside-work-tree 2>/dev/null)" != "true" 18 | then 19 | return 0 20 | fi 21 | __gcd_initialize 22 | __gcd_curdir="${PWD}" 23 | __gcd_gitdir=$(git rev-parse --show-cdup 2>/dev/null) || return 0 24 | if test -n "${__gcd_gitdir}" 25 | then 26 | if ! cd "${__gcd_gitdir}" 27 | then 28 | __gcd_finalize 29 | return 0 30 | fi 31 | fi 32 | __gcd_dir=$( 33 | (echo . && git ls-tree -r -d --name-only HEAD 2>/dev/null) | __gcd_fzf "$@" 34 | ) 35 | __gcd_finalize 36 | 37 | if test -n "${__gcd_dir}" 38 | then 39 | cd "${__gcd_dir}" || return 0 40 | else 41 | cd "${__gcd_curdir}" || return 0 42 | fi 43 | } 44 | 45 | # Initialize the zsh shell environment. 46 | __gcd_initialize () { 47 | __gcd_restore_zsh_wordsplit= 48 | if test -n "${ZSH_VERSION}" && test -z "$(setopt | grep shwordsplit)" 49 | then 50 | __gcd_restore_zsh_wordsplit=true 51 | set -o shwordsplit 52 | fi 53 | } 54 | 55 | 56 | # Restore the zsh shell environment. 57 | __gcd_finalize () { 58 | if test -n "${__gcd_restore_zsh_wordsplit}" 59 | then 60 | set +o shwordsplit 61 | fi 62 | } 63 | 64 | # Custom fzf used by gcd / gcdi 65 | __gcd_fzf () { 66 | fzf \ 67 | --ansi \ 68 | --border=none \ 69 | --cycle \ 70 | --filepath-word \ 71 | --info=inline-right \ 72 | --keep-right \ 73 | --preview='eza --all --color=always --git-ignore --group-directories-first --icons {}' \ 74 | --preview-window=down,33%,border-none \ 75 | --query="$*" \ 76 | --scheme=path \ 77 | --tiebreak=end,chunk,length 78 | } 79 | 80 | # Find worktrees and print their paths to stdout. 81 | __gcd_worktrees () { 82 | gcd_paths=$(git config --get-all gcd.paths | envsubst) 83 | if test -z "${gcd_paths}" 84 | then 85 | gcd_paths="${HOME}" 86 | fi 87 | OIFS="${IFS}" 88 | IFS=' 89 | ' 90 | set -- 91 | for gcd_path in ${gcd_paths} 92 | do 93 | set -- "$@" "${gcd_path}" 94 | done 95 | IFS="${OIFS}" 96 | 97 | gcd_depth=$(git config gcd.depth || echo 2) 98 | # Add + 1 to account for .git. 99 | gcd_depth=$((gcd_depth + 1)) 100 | fdfind_cmd= 101 | fdfind_args="--color=never --case-sensitive --hidden --no-ignore --max-depth=${gcd_depth}" 102 | if type fd >/dev/null 2>&1 103 | then 104 | fdfind_cmd=fd 105 | elif type fdfind >/dev/null 2>&1 106 | then 107 | fdfind_cmd=fdfind 108 | fi 109 | if test -n "${fdfind_cmd}" 110 | then 111 | # shellcheck disable=SC2086 112 | "${fdfind_cmd}" ${fdfind_args} '^\.git$' "$@" | xargs -P 4 -n 1 dirname 2>/dev/null 113 | else 114 | find -L "$@" -maxdepth ${gcd_depth} -name .git -printf '%h\n' 2>/dev/null 115 | fi 116 | } 117 | --------------------------------------------------------------------------------