├── .editorconfig ├── .github ├── dependabot.yml ├── issue_template.md ├── pull_request_template.md ├── scripts │ └── changelog.sh ├── stale.yml └── workflows │ ├── aur.yaml │ ├── ci.yaml │ └── release.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── bin └── git-forgit ├── completions ├── _git-forgit ├── git-forgit.bash └── git-forgit.fish ├── conf.d ├── bin │ └── git-forgit └── forgit.plugin.fish ├── forgit.plugin.sh └── forgit.plugin.zsh /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | insert_final_newline = true 8 | tab_width = 4 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ## Check list 6 | 7 | - [ ] I have read through the [README](https://github.com/wfxr/forgit/blob/main/README.md) 8 | - [ ] I have the latest version of forgit 9 | - [ ] I have searched through the existing issues 10 | 11 | ## Environment info 12 | 13 | - OS 14 | - [ ] Linux 15 | - [ ] Mac OS X 16 | - [ ] Windows 17 | - [ ] Others: 18 | - Shell 19 | - [ ] bash 20 | - [ ] zsh 21 | - [ ] fish 22 | 23 | ## Problem / Steps to reproduce 24 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ## Check list 6 | 7 | - [ ] I have performed a self-review of my code 8 | - [ ] I have commented my code in hard-to-understand areas 9 | - [ ] I have made corresponding changes to the documentation 10 | 11 | ## Description 12 | 13 | 14 | 15 | ## Type of change 16 | 17 | - [ ] Bug fix 18 | - [ ] New feature 19 | - [ ] Refactor 20 | - [ ] Breaking change 21 | - [ ] Documentation change 22 | 23 | ## Test environment 24 | 25 | - Shell 26 | - [ ] bash 27 | - [ ] zsh 28 | - [ ] fish 29 | - OS 30 | - [ ] Linux 31 | - [ ] Mac OS X 32 | - [ ] Windows 33 | - [ ] Others: 34 | -------------------------------------------------------------------------------- /.github/scripts/changelog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | release_has_public_changes=false 4 | 5 | url=$(git remote get-url origin | sed -r 's/(.*)\.git/\1/') 6 | 7 | previous_tag=$(git describe --tags --abbrev=0 HEAD~) 8 | 9 | echo "Changes since $previous_tag:" 10 | echo 11 | 12 | # Loop through all commits since previous tag 13 | for rev in $(git log $previous_tag..HEAD --format="%H" --reverse --no-merges) 14 | do 15 | summary=$(git log $rev~..$rev --format="%s") 16 | # Exclude commits starting with "Meta" 17 | if [[ $summary != Meta* ]] 18 | then 19 | # Print markdown list of commit headlines 20 | echo "* [$summary]($url/commit/$rev)" 21 | # Append commit body indented (blank lines and signoff trailer removed) 22 | git log $rev~..$rev --format="%b" | sed '/^\s*$/d' | sed '/^Signed-off-by:/d' | \ 23 | while read -r line 24 | do 25 | # Escape markdown formatting symbols _ * ` 26 | echo " $line" | sed 's/_/\\_/g' | sed 's/`/\\`/g' | sed 's/\*/\\\*/g' 27 | done 28 | release_has_public_changes=true 29 | fi 30 | done 31 | 32 | if ! $release_has_public_changes 33 | then 34 | echo "No public changes since $previous_tag." >&2 35 | exit 1 36 | fi 37 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 180 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 14 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | - bug 10 | # Label to use when marking an issue as stale 11 | staleLabel: stale 12 | # Comment to post when marking an issue as stale. Set to `false` to disable 13 | markComment: > 14 | This issue has been automatically marked as stale because it has not had 15 | recent activity. It will be closed if no further activity occurs. Thank you 16 | for your contributions. 17 | # Comment to post when closing a stale issue. Set to `false` to disable 18 | closeComment: false 19 | -------------------------------------------------------------------------------- /.github/workflows/aur.yaml: -------------------------------------------------------------------------------- 1 | name: AUR 2 | 3 | on: 4 | workflow_run: 5 | workflows: 6 | - "Release" 7 | types: 8 | - completed 9 | 10 | jobs: 11 | aur: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Publish 15 | uses: zokugun/github-actions-aur-releaser@v1 16 | with: 17 | package_name: forgit 18 | aur_private_key: ${{ secrets.AUR_PRIVATE_KEY }} 19 | aur_username: ${{ secrets.AUR_USERNAME }} 20 | aur_email: ${{ secrets.AUR_EMAIL }} 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: ci 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events, but only for the default branch 7 | on: 8 | push: 9 | branches: 10 | - '**' 11 | pull_request: 12 | branches: 13 | - '**' 14 | 15 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 16 | jobs: 17 | # This workflow contains a single job called "build" 18 | build: 19 | # The type of runner that the job will run on 20 | runs-on: ${{ matrix.os }} 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | os: [macos-latest, ubuntu-latest] 25 | 26 | # Steps represent a sequence of tasks that will be executed as part of the job 27 | steps: 28 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 29 | - uses: actions/checkout@v4 30 | 31 | # Runs a single command using the runners shell 32 | - name: Install prerequisites 33 | shell: bash 34 | run: | 35 | if [[ "${{ matrix.os }}" == macos* ]]; then 36 | brew install zsh fish shellcheck 37 | elif [[ "${{ matrix.os }}" == ubuntu* ]]; then 38 | sudo apt-add-repository -y ppa:fish-shell/release-3 39 | sudo apt -y update 40 | sudo apt -y install zsh fish shellcheck 41 | fi 42 | 43 | - name: Show version 44 | run: | 45 | bash --version; echo 46 | zsh --version; echo 47 | fish --version; echo 48 | 49 | - name: Shellcheck 50 | run: shellcheck forgit.plugin.sh bin/git-forgit 51 | 52 | - name: Test bash 53 | run: bash forgit.plugin.sh 54 | 55 | - name: Test zsh 56 | run: zsh forgit.plugin.zsh 57 | 58 | - name: Test fish 59 | run: fish conf.d/forgit.plugin.fish 60 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | schedule: 5 | # Run on every first day of the month 6 | - cron: '0 0 1 * *' 7 | push: 8 | tags: 9 | - '**' 10 | 11 | jobs: 12 | 13 | changelog: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Generate Changelog 22 | id: changelog 23 | run: .github/scripts/changelog.sh > CHANGELOG.md 24 | # When the script returns with an error, it indicates that we don't have 25 | # any public changes to be released. This is expected behavior, so we 26 | # quit the workflow successfully in this case. 27 | continue-on-error: true 28 | 29 | - name: Upload Changelog 30 | uses: actions/upload-artifact@v4 31 | if: steps.changelog.outcome == 'success' 32 | with: 33 | name: changelog 34 | path: CHANGELOG.md 35 | 36 | - name: Set version 37 | id: set_version 38 | if: steps.changelog.outcome == 'success' 39 | # Set version depending on whether this is a regular monthly release or 40 | # a custom release triggered by a tag push. 41 | run: | 42 | if [ "${{ github.event_name }}" == "push" ]; then 43 | VERSION=${{ github.ref_name }} 44 | else 45 | VERSION=$(date +'%y.%m.0') 46 | fi 47 | echo "VERSION=$VERSION" 48 | echo "VERSION=$VERSION" >> $GITHUB_OUTPUT 49 | 50 | outputs: 51 | changeLogOutcome: ${{ steps.changelog.outcome }} 52 | version: ${{ steps.set_version.outputs.VERSION }} 53 | 54 | release: 55 | runs-on: ubuntu-latest 56 | needs: changelog 57 | if: needs.changelog.outputs.changeLogOutcome == 'success' 58 | steps: 59 | - name: Checkout 60 | uses: actions/checkout@v4 61 | with: 62 | fetch-depth: 0 63 | 64 | - name: Download Changelog 65 | uses: actions/download-artifact@v4 66 | with: 67 | name: changelog 68 | 69 | - name: Create Package 70 | run: tar -czf forgit-${{ needs.changelog.outputs.version }}.tar.gz --exclude LICENSE --exclude README.md * 71 | 72 | - name: Release 73 | uses: softprops/action-gh-release@v2 74 | with: 75 | # The release action implicitly creates the tag if it does not exist. 76 | tag_name: ${{ needs.changelog.outputs.version }} 77 | body_path: CHANGELOG.md 78 | files: forgit-${{ needs.changelog.outputs.version }}.tar.gz 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ._zinit/ 2 | *.zwc 3 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | # See https://pre-commit.com for more information 3 | # See https://pre-commit.com/hooks.html for more hooks 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v3.4.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-added-large-files 10 | - id: mixed-line-ending 11 | - repo: local 12 | hooks: 13 | - id: update-license 14 | name: update license 15 | pass_filenames: false 16 | language: system 17 | always_run: true 18 | entry: > 19 | sh -c ' 20 | (curl -fsSL https://wfxr.mit-license.org/2017/license.txt && echo) > ./LICENSE && 21 | git add ./LICENSE || { git checkout ./LICENSE && exit 1; } 22 | ' 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2017-2021 Wenxuan Zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the “Software”), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

💤 forgit

2 |

3 | Utility tool for using git interactively. Powered by junegunn/fzf. 4 |

5 | 6 |

7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | pre-commit 21 | 22 | 23 | Contributors 24 | 25 |

26 | 27 | This tool is designed to help you use git more efficiently. 28 | It's **lightweight** and **easy to use**. 29 | 30 | # 📥 Installation 31 | 32 | ## Requirements 33 | 34 | - [`fzf`](https://github.com/junegunn/fzf) version `0.49.0` or higher 35 | 36 | If your OS package manager bundles an older version of `fzf`, you might install it using [`fzf`'s own install script'](https://github.com/junegunn/fzf?tab=readme-ov-file#using-git). 37 | 38 | ## Shell package managers 39 | 40 | ``` zsh 41 | # for zplug 42 | zplug 'wfxr/forgit' 43 | 44 | # for zgen 45 | zgen load 'wfxr/forgit' 46 | 47 | # for antigen 48 | antigen bundle 'wfxr/forgit' 49 | 50 | # for fisher (requires fisher v4.4.3 or higher) 51 | fisher install wfxr/forgit 52 | 53 | # for omf 54 | omf install https://github.com/wfxr/forgit 55 | 56 | # for zinit 57 | zinit load wfxr/forgit 58 | 59 | # for oh-my-zsh 60 | git clone https://github.com/wfxr/forgit.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/forgit 61 | 62 | # for sheldon.cli 63 | [plugins.forgit] 64 | github = "wfxr/forgit" 65 | rev = "25.02.0" 66 | use = ["forgit.plugin.zsh"] 67 | apply = ["source"] 68 | 69 | # manually 70 | # Clone the repository and source it in your shell's rc file or put bin/git-forgit into your $PATH 71 | ``` 72 | 73 | ## Homebrew 74 | 75 | To install using brew 76 | ```sh 77 | brew install forgit 78 | ``` 79 | 80 | Then add the following to your shell's config file: 81 | ```sh 82 | # Fish: 83 | # ~/.config/fish/config.fish: 84 | [ -f $HOMEBREW_PREFIX/share/forgit/forgit.plugin.fish ]; and source $HOMEBREW_PREFIX/share/forgit/forgit.plugin.fish 85 | 86 | # Zsh: 87 | # ~/.zshrc: 88 | [ -f $HOMEBREW_PREFIX/share/forgit/forgit.plugin.zsh ] && source $HOMEBREW_PREFIX/share/forgit/forgit.plugin.zsh 89 | 90 | # Bash: 91 | # ~/.bashrc: 92 | [ -f $HOMEBREW_PREFIX/share/forgit/forgit.plugin.sh ] && source $HOMEBREW_PREFIX/share/forgit/forgit.plugin.sh 93 | ``` 94 | 95 | ## Arch User Repository 96 | 97 | [AUR](https://wiki.archlinux.org/title/Arch_User_Repository) packages, maintained by the developers of forgit, are available. Install the [forgit](https://aur.archlinux.org/packages/forgit) package for the latest release or [forgit-git](https://aur.archlinux.org/packages/forgit-git) to stay up to date with the latest commits from the default branch of this repository. 98 | 99 | # Completions 100 | 101 | Forgit offers completions for all supported shells. Completions are automatically configured when installing forgit through Homebrew or the AUR. All other installation methods mentioned above require manual setup for completions. The necessary steps depend on the shell you use. 102 | 103 | ## Bash 104 | 105 | - Put [`completions/git-forgit.bash`](https://github.com/wfxr/forgit/blob/main/completions/git-forgit.bash) in 106 | `~/.local/share/bash-completion/completions` to have bash tab completion for `git forgit` and configured git aliases. 107 | - Source [`completions/git-forgit.bash`](https://github.com/wfxr/forgit/blob/main/completions/git-forgit.bash) explicitly to have 108 | bash tab completion for forgit shell functions and aliases (e.g., `gcb ` completes branches). 109 | 110 | ## Fish 111 | 112 | - Put [`completions/git-forgit.fish`](https://github.com/wfxr/forgit/blob/main/completions/git-forgit.fish) in `~/.config/fish/completions/` to have fish tab completion for `git forgit` and configured git aliases, as well as shell command aliases, such as `ga`. 113 | 114 | ## Zsh 115 | 116 | - Put [`completions/_git-forgit`](completions/_git-forgit) in a directory in your `$fpath` (e.g., `/usr/share/zsh/site-functions`) to have zsh tab completion for `git forgit` and configured git aliases, as well as shell command aliases, such as `forgit::add` and `ga`. 117 | 118 | If you're having issues after updating, and commands such as `forgit::add` or aliases `ga` aren't working, remove your completions cache and restart your shell. 119 | 120 | ```zsh 121 | > rm ~/.zcompdump 122 | > zsh 123 | ``` 124 | 125 | # 📝 Features 126 | 127 | - **Interactive `git add` selector** (`ga`) 128 | 129 | ![screenshot](https://raw.githubusercontent.com/wfxr/i/master/forgit-ga.png) 130 | 131 | - **Interactive `git log` viewer** (`glo`) 132 | 133 | ![screenshot](https://raw.githubusercontent.com/wfxr/i/master/forgit-glo.png) 134 | 135 | *The log graph can be disabled by option `FORGIT_LOG_GRAPH_ENABLE` (see discuss in [issue #71](https://github.com/wfxr/forgit/issues/71)).* 136 | 137 | - **Interactive `.gitignore` generator** (`gi`) 138 | 139 | ![screenshot](https://raw.githubusercontent.com/wfxr/i/master/forgit-gi.png) 140 | 141 | - **Interactive `git diff` viewer** (`gd`) 142 | 143 | - **Interactive `git show` viewer** (`gso`) 144 | 145 | - **Interactive `git reset HEAD ` selector** (`grh`) 146 | 147 | - **Interactive `git checkout ` selector** (`gcf`) 148 | 149 | - **Interactive `git checkout ` selector** (`gcb`) 150 | 151 | - **Interactive `git branch -D ` selector** (`gbd`) 152 | 153 | - **Interactive `git checkout ` selector** (`gct`) 154 | 155 | - **Interactive `git checkout ` selector** (`gco`) 156 | 157 | - **Interactive `git revert ` selector** (`grc`) 158 | 159 | - **Interactive `git stash` viewer** (`gss`) 160 | 161 | - **Interactive `git stash push` selector** (`gsp`) 162 | 163 | - **Interactive `git clean` selector** (`gclean`) 164 | 165 | - **Interactive `git cherry-pick` selector** (`gcp`) 166 | 167 | - **Interactive `git rebase -i` selector** (`grb`) 168 | 169 | - **Interactive `git reflog` viewer** (`grl`) 170 | 171 | - **Interactive `git blame` selector** (`gbl`) 172 | 173 | - **Interactive `git commit --fixup && git rebase -i --autosquash` selector** (`gfu`) 174 | 175 | - **Interactive `git commit --squash && git rebase -i --autosquash` selector** (`gsq`) 176 | 177 | - **Interactive `git commit --fixup=reword && git rebase -i --autosquash` selector** (`grw`) 178 | 179 | # ⌨ Keybindings 180 | 181 | | Key | Action | 182 | | :-------------------------------------------: | ------------------------------------------- | 183 | | Enter | Confirm | 184 | | Tab | Toggle mark and move down | 185 | | Shift - Tab | Toggle mark and move up | 186 | | ? | Toggle preview window | 187 | | Alt - W | Toggle preview wrap | 188 | | Ctrl - S | Toggle sort | 189 | | Ctrl - R | Toggle selection | 190 | | Ctrl - Y | Copy commit hash/stash ID* | 191 | | Ctrl - K / P | Selection move up | 192 | | Ctrl - J / N | Selection move down | 193 | | Alt - K / P | Preview move up | 194 | | Alt - J / N | Preview move down | 195 | | Alt - E | Open file in default editor (when possible) | 196 | 197 | \* Available when the selection contains a commit hash or a stash ID. 198 | For Linux users `FORGIT_COPY_CMD` should be set to make copy work. Example: `FORGIT_COPY_CMD='xclip -selection clipboard'`. 199 | 200 | # ⚙ Options 201 | 202 | Options can be set via environment variables. They have to be **exported** in 203 | order to be recognized by `forgit`. 204 | 205 | For instance, if you want to order branches in `gcb` by the last committed date you could: 206 | 207 | ```shell 208 | export FORGIT_CHECKOUT_BRANCH_BRANCH_GIT_OPTS='--sort=-committerdate' 209 | ``` 210 | 211 | ## shell aliases 212 | 213 | You can change the default aliases by defining these variables below before sourcing the forgit shell plugin. 214 | (To disable all aliases, Set the `FORGIT_NO_ALIASES` flag.) 215 | 216 | ``` bash 217 | forgit_log=glo 218 | forgit_reflog=grl 219 | forgit_diff=gd 220 | forgit_show=gso 221 | forgit_add=ga 222 | forgit_reset_head=grh 223 | forgit_ignore=gi 224 | forgit_attributes=gat 225 | forgit_checkout_file=gcf 226 | forgit_checkout_branch=gcb 227 | forgit_branch_delete=gbd 228 | forgit_checkout_tag=gct 229 | forgit_checkout_commit=gco 230 | forgit_revert_commit=grc 231 | forgit_clean=gclean 232 | forgit_stash_show=gss 233 | forgit_stash_push=gsp 234 | forgit_cherry_pick=gcp 235 | forgit_rebase=grb 236 | forgit_blame=gbl 237 | forgit_fixup=gfu 238 | forgit_squash=gsq 239 | forgit_reword=grw 240 | ``` 241 | 242 | ## git integration 243 | 244 | You can use forgit as a sub-command of git by making `git-forgit` available in `$PATH`: 245 | 246 | ```sh 247 | # after `forgit` was loaded 248 | PATH="$PATH:$FORGIT_INSTALL_DIR/bin" 249 | ``` 250 | 251 | *Some plugin managers can help do this.* 252 | 253 | Then, any forgit command will be a sub-command of git: 254 | 255 | ```cmd 256 | git forgit log 257 | git forgit add 258 | git forgit diff 259 | ``` 260 | 261 | Optionally you can add [aliases in git](https://git-scm.com/book/en/v2/Git-Basics-Git-Aliases): 262 | 263 | ```sh 264 | git config --global alias.cf 'forgit checkout_file' 265 | ``` 266 | 267 | And use forgit functions via a git alias: 268 | 269 | ```sh 270 | git cf 271 | ``` 272 | 273 | ## git options 274 | 275 | If you want to customize `git`'s behavior within forgit there is a dedicated variable for each forgit command. 276 | These are passed to the according `git` calls. 277 | 278 | | Command | Option | 279 | | -------- | --------------------------------------------------------------------------- | 280 | | `ga` | `FORGIT_ADD_GIT_OPTS` | 281 | | `glo` | `FORGIT_LOG_GIT_OPTS` | 282 | | `grl` | `FORGIT_REFLOG_GIT_OPTS` | 283 | | `gd` | `FORGIT_DIFF_GIT_OPTS` | 284 | | `gso` | `FORGIT_SHOW_GIT_OPTS` | 285 | | `grh` | `FORGIT_RESET_HEAD_GIT_OPTS` | 286 | | `gcf` | `FORGIT_CHECKOUT_FILE_GIT_OPTS` | 287 | | `gcb` | `FORGIT_CHECKOUT_BRANCH_GIT_OPTS`, `FORGIT_CHECKOUT_BRANCH_BRANCH_GIT_OPTS` | 288 | | `gbd` | `FORGIT_BRANCH_DELETE_GIT_OPTS` | 289 | | `gct` | `FORGIT_CHECKOUT_TAG_GIT_OPTS` | 290 | | `gco` | `FORGIT_CHECKOUT_COMMIT_GIT_OPTS` | 291 | | `grc` | `FORGIT_REVERT_COMMIT_GIT_OPTS` | 292 | | `gss` | `FORGIT_STASH_SHOW_GIT_OPTS` | 293 | | `gsp` | `FORGIT_STASH_PUSH_GIT_OPTS` | 294 | | `gclean` | `FORGIT_CLEAN_GIT_OPTS` | 295 | | `grb` | `FORGIT_REBASE_GIT_OPTS` | 296 | | `gbl` | `FORGIT_BLAME_GIT_OPTS` | 297 | | `gfu` | `FORGIT_FIXUP_GIT_OPTS` | 298 | | `gsq` | `FORGIT_SQUASH_GIT_OPTS` | 299 | | `grw` | `FORGIT_REWORD_GIT_OPTS` | 300 | | `gcp` | `FORGIT_CHERRY_PICK_GIT_OPTS` | 301 | 302 | ## pagers 303 | 304 | Forgit will use the default configured pager from git (`core.pager`, 305 | `pager.show`, `pager.diff`) but can be altered with the following environment 306 | variables: 307 | 308 | | Use case | Option | Fallbacks to | 309 | | ------------------------ | ------------------------- | ------------------------------------------------- | 310 | | common pager | `FORGIT_PAGER` | `git config core.pager` _or_ `cat` | 311 | | pager on `git show` | `FORGIT_SHOW_PAGER` | `git config pager.show` _or_ `$FORGIT_PAGER` | 312 | | pager on `git diff` | `FORGIT_DIFF_PAGER` | `git config pager.diff` _or_ `$FORGIT_PAGER` | 313 | | pager on `git blame` | `FORGIT_BLAME_PAGER` | `git config pager.blame` _or_ `$FORGIT_PAGER` | 314 | | pager on `gitignore` | `FORGIT_IGNORE_PAGER` | `bat -l gitignore --color always` _or_ `cat` | 315 | | pager on `gitatrributes` | `FORGIT_ATTRIBUTES_PAGER` | `bat -l gitattributes --color always` _or_ `cat` | 316 | | git log format | `FORGIT_GLO_FORMAT` | `%C(auto)%h%d %s %C(black)%C(bold)%cr%reset` | 317 | 318 | ## fzf options 319 | 320 | You can add default fzf options for `forgit`, including keybindings, layout, etc. 321 | (No need to repeat the options already defined in `FZF_DEFAULT_OPTS`) 322 | 323 | ``` bash 324 | export FORGIT_FZF_DEFAULT_OPTS=" 325 | --exact 326 | --border 327 | --cycle 328 | --reverse 329 | --height '80%' 330 | " 331 | ``` 332 | 333 | Customizing fzf options for each command individually is also supported: 334 | 335 | | Command | Option | 336 | |----------|-----------------------------------| 337 | | `ga` | `FORGIT_ADD_FZF_OPTS` | 338 | | `glo` | `FORGIT_LOG_FZF_OPTS` | 339 | | `grl` | `FORGIT_REFLOG_FZF_OPTS` | 340 | | `gi` | `FORGIT_IGNORE_FZF_OPTS` | 341 | | `gat` | `FORGIT_ATTRIBUTES_FZF_OPTS` | 342 | | `gd` | `FORGIT_DIFF_FZF_OPTS` | 343 | | `gso` | `FORGIT_SHOW_FZF_OPTS` | 344 | | `grh` | `FORGIT_RESET_HEAD_FZF_OPTS` | 345 | | `gcf` | `FORGIT_CHECKOUT_FILE_FZF_OPTS` | 346 | | `gcb` | `FORGIT_CHECKOUT_BRANCH_FZF_OPTS` | 347 | | `gbd` | `FORGIT_BRANCH_DELETE_FZF_OPTS` | 348 | | `gct` | `FORGIT_CHECKOUT_TAG_FZF_OPTS` | 349 | | `gco` | `FORGIT_CHECKOUT_COMMIT_FZF_OPTS` | 350 | | `grc` | `FORGIT_REVERT_COMMIT_FZF_OPTS` | 351 | | `gss` | `FORGIT_STASH_FZF_OPTS` | 352 | | `gsp` | `FORGIT_STASH_PUSH_FZF_OPTS` | 353 | | `gclean` | `FORGIT_CLEAN_FZF_OPTS` | 354 | | `grb` | `FORGIT_REBASE_FZF_OPTS` | 355 | | `gbl` | `FORGIT_BLAME_FZF_OPTS` | 356 | | `gfu` | `FORGIT_FIXUP_FZF_OPTS` | 357 | | `gsq` | `FORGIT_SQUASH_FZF_OPTS` | 358 | | `grw` | `FORGIT_REWORD_FZF_OPTS` | 359 | | `gcp` | `FORGIT_CHERRY_PICK_FZF_OPTS` | 360 | 361 | Complete loading order of fzf options is: 362 | 363 | 1. `FZF_DEFAULT_OPTS` (fzf global) 364 | 2. `FORGIT_FZF_DEFAULT_OPTS` (forgit global) 365 | 3. `FORGIT_CMD_FZF_OPTS` (command specific) 366 | 367 | Examples: 368 | 369 | - `ctrl-d` to drop the selected stash but do not quit fzf (`gss` specific). 370 | 371 | ```sh 372 | export FORGIT_STASH_FZF_OPTS=' 373 | --bind="ctrl-d:reload(git stash drop $(cut -d: -f1 <<<{}) 1>/dev/null && git stash list)" 374 | ' 375 | ``` 376 | 377 | - `ctrl-e` to view the logs in a vim buffer (`glo` specific). 378 | 379 | ```sh 380 | export FORGIT_LOG_FZF_OPTS=' 381 | --bind="ctrl-e:execute(echo {} |grep -Eo [a-f0-9]+ |head -1 |xargs git show |vim -)" 382 | ' 383 | ``` 384 | 385 | ## other options 386 | 387 | | Option | Description | Default | 388 | |-----------------------------|-------------------------------------------|-----------------------------------------------| 389 | | `FORGIT_LOG_FORMAT` | git log format | `%C(auto)%h%d %s %C(black)%C(bold)%cr%Creset` | 390 | | `FORGIT_PREVIEW_CONTEXT` | lines of diff context in preview mode | 3 | 391 | | `FORGIT_FULLSCREEN_CONTEXT` | lines of diff context in full-screen mode | 10 | 392 | | `FORGIT_DIR_VIEW` | command used to preview directories | `tree` if available, otherwise `find` | 393 | 394 | # 📦 Optional dependencies 395 | 396 | - [`delta`](https://github.com/dandavison/delta) / [`diff-so-fancy`](https://github.com/so-fancy/diff-so-fancy): For better human-readable diffs. 397 | 398 | - [`bat`](https://github.com/sharkdp/bat.git): Syntax highlighting for `gitignore` and `gitattributes`. 399 | 400 | - [`emoji-cli`](https://github.com/wfxr/emoji-cli): Emoji support for `git log`. 401 | 402 | - [`tree`](https://gitlab.com/OldManProgrammer/unix-tree): Directory tree view for `gclean`. 403 | 404 | # 💡 Tips 405 | 406 | - Most of the commands accept optional arguments (e.g., `glo develop`, `glo f738479..188a849b -- main.go`, `gco main`). 407 | - `gd` supports specifying revision(e.g., `gd HEAD~`, `gd v1.0 README.md`). 408 | - Call `gi` or `gat` with arguments to get the wanted `.gitignore`/`.gitattributes` contents directly(e.g., `gi cmake c++`). 409 | 410 | # 📃 License 411 | 412 | [MIT](https://wfxr.mit-license.org/2017) (c) Wenxuan Zhang 413 | -------------------------------------------------------------------------------- /bin/git-forgit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # MIT (c) Wenxuan Zhang 3 | 4 | # This file is meant to be executed directly. If it's available on the PATH, 5 | # it can also be used as a subcommand of git, which then forwards all arguments 6 | # on to forgit. So, all of these commands will work as expected: 7 | # 8 | # `git forgit log` 9 | # `git forgit checkout_file` 10 | # `git forgit checkout_file README.md` 11 | # 12 | # This gives users the choice to set aliases inside of their git config instead 13 | # of their shell config if they prefer. 14 | 15 | # Check if fzf is installed 16 | installed_fzf_version=$(fzf --version 2>/dev/null | awk '{print $1}') 17 | if [[ -z "$installed_fzf_version" ]]; then 18 | echo "fzf is not installed. Please install fzf first." 19 | exit 1 20 | fi 21 | 22 | # Check fzf version 23 | required_fzf_version="0.49.0" 24 | higher_fzf_version=$(printf '%s\n' "$required_fzf_version" "$installed_fzf_version" | sort -V | tail -n1) 25 | if [[ "$higher_fzf_version" != "$installed_fzf_version" ]]; then 26 | echo "fzf version $required_fzf_version or higher is required. You have $installed_fzf_version." 27 | exit 1 28 | fi 29 | 30 | # Set shell for fzf preview commands 31 | SHELL="$(which bash)" 32 | export SHELL 33 | 34 | # Get absolute forgit path 35 | FORGIT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)/$(basename -- "${BASH_SOURCE[0]}") 36 | 37 | FORGIT_FZF_DEFAULT_OPTS=" 38 | $FZF_DEFAULT_OPTS 39 | --ansi 40 | --height='80%' 41 | --bind='alt-k:preview-up,alt-p:preview-up' 42 | --bind='alt-j:preview-down,alt-n:preview-down' 43 | --bind='ctrl-r:toggle-all' 44 | --bind='ctrl-s:toggle-sort' 45 | --bind='?:toggle-preview' 46 | --bind='alt-w:toggle-preview-wrap' 47 | --preview-window='right:60%' 48 | +1 49 | $FORGIT_FZF_DEFAULT_OPTS 50 | " 51 | 52 | _forgit_warn() { printf "%b[Warn]%b %s\n" '\e[0;33m' '\e[0m' "$@" >&2; } 53 | _forgit_info() { printf "%b[Info]%b %s\n" '\e[0;32m' '\e[0m' "$@" >&2; } 54 | _forgit_inside_work_tree() { git rev-parse --is-inside-work-tree >/dev/null; } 55 | # tac is not available on OSX, tail -r is not available on Linux, so we use either of them 56 | _forgit_reverse_lines() { tac 2> /dev/null || tail -r; } 57 | 58 | _forgit_previous_commit() { 59 | # "SHA~" is invalid when the commit is the first commit, but we can use "--root" instead 60 | if [[ "$(git rev-parse "$1")" == "$(git rev-list --max-parents=0 HEAD)" ]]; then 61 | echo "--root" 62 | else 63 | echo "$1~" 64 | fi 65 | } 66 | 67 | _forgit_contains_non_flags() { 68 | while (("$#")); do 69 | case "$1" in 70 | -*) shift ;; 71 | *) 72 | return 0 73 | ;; 74 | esac 75 | done 76 | return 1 77 | } 78 | 79 | # optional render emoji characters (https://github.com/wfxr/emoji-cli) 80 | _forgit_emojify() { 81 | if hash emojify &>/dev/null; then 82 | emojify 83 | else 84 | cat 85 | fi 86 | } 87 | 88 | # extract the first git sha occurring in the input and strip trailing newline 89 | _forgit_extract_sha() { 90 | grep -Eo '[a-f0-9]+' | head -1 | tr -d '[:space:]' 91 | } 92 | 93 | # extract the first git sha and copy it to the clipboard 94 | _forgit_yank_sha() { 95 | echo "$1" | _forgit_extract_sha | ${FORGIT_COPY_CMD:-pbcopy} 96 | } 97 | 98 | # extract the first stash name in the input 99 | _forgit_extract_stash_name() { 100 | cut -d: -f1 | tr -d '[:space:]' 101 | } 102 | 103 | # extract the first stash name and copy it to the clipboard 104 | _forgit_yank_stash_name() { 105 | echo "$1" | _forgit_extract_stash_name | ${FORGIT_COPY_CMD:-pbcopy} 106 | } 107 | 108 | # parse a space separated string into an array 109 | # arrays parsed with this function are global 110 | _forgit_parse_array() { 111 | ${IFS+"false"} && unset old_IFS || old_IFS="$IFS" 112 | # read the value of the second argument 113 | # into an array that has the name of the first argument 114 | IFS=" " read -r -a "$1" <<< "$2" 115 | ${old_IFS+"false"} && unset IFS || IFS="$old_IFS" 116 | } 117 | 118 | # parse the input arguments and print only those after the "--" 119 | # separator as a single line of quoted arguments to stdout 120 | _forgit_quote_files() { 121 | local files add 122 | files=() 123 | add=false 124 | while (( "$#" )); do 125 | case "$1" in 126 | --) 127 | add=true 128 | shift 129 | ;; 130 | *) 131 | if [ $add == true ]; then 132 | files+=("'$1'") 133 | fi 134 | shift 135 | ;; 136 | esac 137 | done 138 | echo "${files[*]}" 139 | } 140 | 141 | _forgit_log_graph_enable=${FORGIT_LOG_GRAPH_ENABLE:-"true"} 142 | _forgit_log_format=${FORGIT_LOG_FORMAT:-%C(auto)%h%d %s %C(black)%C(bold)%cr%Creset} 143 | _forgit_log_preview_options=("--graph" "--pretty=format:$_forgit_log_format" "--color=always" "--abbrev-commit" "--date=relative") 144 | _forgit_fullscreen_context=${FORGIT_FULLSCREEN_CONTEXT:-10} 145 | _forgit_preview_context=${FORGIT_PREVIEW_CONTEXT:-3} 146 | _forgit_dir_view=${FORGIT_DIR_VIEW:-$(hash tree &> /dev/null && echo 'tree' || echo 'find')} 147 | 148 | _forgit_pager() { 149 | local pager 150 | pager=$(_forgit_get_pager "$1") 151 | [[ -z "${pager}" ]] && exit 1 152 | eval "${pager} ${*:2}" 153 | } 154 | 155 | _forgit_get_pager() { 156 | local pager 157 | pager=${1:-core} 158 | case "$pager" in 159 | core) echo -n "${FORGIT_PAGER:-$(git config core.pager || echo 'cat')}" ;; 160 | show) echo -n "${FORGIT_SHOW_PAGER:-$(git config pager.show || _forgit_get_pager)}" ;; 161 | diff) echo -n "${FORGIT_DIFF_PAGER:-$(git config pager.diff || _forgit_get_pager)}" ;; 162 | ignore) echo -n "${FORGIT_IGNORE_PAGER:-$(hash bat &>/dev/null && echo 'bat -l gitignore --color=always' || echo 'cat')}" ;; 163 | attributes) echo -n "${FORGIT_ATTRIBUTES_PAGER:-$(hash bat &>/dev/null && echo 'bat -l gitattributes --color=always' || echo 'cat')}" ;; 164 | blame) echo -n "${FORGIT_BLAME_PAGER:-$(git config pager.blame || _forgit_get_pager)}" ;; 165 | enter) echo -n "${FORGIT_ENTER_PAGER:-"LESS='-r' less"}" ;; 166 | *) echo "pager not found: $1" >&2 ;; 167 | esac 168 | } 169 | 170 | _forgit_is_file_tracked() { 171 | git ls-files "$1" --error-unmatch &> /dev/null 172 | } 173 | 174 | _forgit_list_files() { 175 | local rootdir 176 | rootdir=$(git rev-parse --show-toplevel) 177 | # git escapes special characters in it's output when core.quotePath is 178 | # true or unset. Git always expects unquoted file paths as input. This 179 | # leads to issues when we consume output from git and use it to build 180 | # input for other git commands. Use the -z flag to ensure file paths are 181 | # unquoted. 182 | # uniq is necessary because unmerged files are printed once for each 183 | # merge conflict. 184 | # With the -z flag, git also uses \0 line termination, so we 185 | # have to replace the terminators. 186 | git ls-files -z "$@" "$rootdir" | tr '\0' '\n' | uniq 187 | } 188 | 189 | _forgit_log_preview() { 190 | local sha 191 | sha=$(echo "$1" | _forgit_extract_sha) 192 | shift 193 | echo "$sha" | xargs -I% git show --color=always -U"$_forgit_preview_context" % -- "$@" | _forgit_pager show 194 | } 195 | 196 | _forgit_log_enter() { 197 | local sha 198 | sha=$(echo "$1" | _forgit_extract_sha) 199 | shift 200 | echo "$sha" | xargs -I% "${FORGIT}" show % "$@" 201 | } 202 | 203 | # git commit viewer 204 | _forgit_log() { 205 | _forgit_inside_work_tree || return 1 206 | local opts graph quoted_files log_format 207 | quoted_files=$(_forgit_quote_files "$@") 208 | opts=" 209 | $FORGIT_FZF_DEFAULT_OPTS 210 | +s +m --tiebreak=index 211 | --bind=\"enter:execute($FORGIT log_enter {} $quoted_files)\" 212 | --bind=\"ctrl-y:execute-silent($FORGIT yank_sha {})\" 213 | --preview=\"$FORGIT log_preview {} $quoted_files\" 214 | $FORGIT_LOG_FZF_OPTS 215 | " 216 | graph=() 217 | [[ $_forgit_log_graph_enable == true ]] && graph=(--graph) 218 | log_format=${FORGIT_GLO_FORMAT:-$_forgit_log_format} 219 | _forgit_log_git_opts=() 220 | _forgit_parse_array _forgit_log_git_opts "$FORGIT_LOG_GIT_OPTS" 221 | git log "${graph[@]}" --color=always --format="$log_format" "${_forgit_log_git_opts[@]}" "$@" | 222 | _forgit_emojify | 223 | FZF_DEFAULT_OPTS="$opts" fzf 224 | fzf_exit_code=$? 225 | # exit successfully on 130 (ctrl-c/esc) 226 | [[ $fzf_exit_code == 130 ]] && return 0 227 | return $fzf_exit_code 228 | } 229 | 230 | # git reflog viewer 231 | _forgit_reflog() { 232 | _forgit_inside_work_tree || return 1 233 | _forgit_contains_non_flags "$@" && { git reflog "$@"; return $?; } 234 | local opts reflog_format 235 | opts=" 236 | $FORGIT_FZF_DEFAULT_OPTS 237 | +s +m --tiebreak=index 238 | --bind=\"enter:execute($FORGIT log_enter {})\" 239 | --bind=\"ctrl-y:execute-silent($FORGIT yank_sha {})\" 240 | --preview=\"$FORGIT log_preview {}\" 241 | $FORGIT_REFLOG_FZF_OPTS 242 | " 243 | reflog_format=${FORGIT_GRL_FORMAT:-$_forgit_log_format} 244 | _forgit_reflog_git_opts=() 245 | _forgit_parse_array _forgit_reflog_git_opts "$FORGIT_REFLOG_GIT_OPTS" 246 | git reflog show --color=always --format="$reflog_format" "${_forgit_reflog_git_opts[@]}" "$@" | 247 | _forgit_emojify | 248 | FZF_DEFAULT_OPTS="$opts" fzf 249 | fzf_exit_code=$? 250 | # exit successfully on 130 (ctrl-c/esc) 251 | [[ $fzf_exit_code == 130 ]] && return 0 252 | return $fzf_exit_code 253 | } 254 | 255 | _forgit_get_files_from_diff_line() { 256 | # Construct a null-terminated list of the filenames 257 | # The input looks like one of these lines: 258 | # [R100] file -> another file 259 | # [A] file with spaces 260 | # [D] oldfile 261 | # And we transform it to this representation for further usage with "xargs -0": 262 | # file\0another file\0 263 | # file with spaces\0 264 | # oldfile\0 265 | # We have to do a two-step sed -> tr pipe because OSX's sed implementation does 266 | # not support the null-character directly. 267 | sed 's/^[[:space:]]*\[[A-Z0-9]*\][[:space:]]*//' | sed 's/ -> /\n/' | tr '\n' '\0' 268 | } 269 | 270 | _forgit_get_single_file_from_diff_line() { 271 | # Similar to the function above, but only gets a single file from a single line 272 | # Gets the new name of renamed files 273 | sed 's/^[[:space:]]*\[[A-Z0-9]*\][[:space:]]*//' | sed 's/.*-> //' 274 | } 275 | 276 | _forgit_exec_diff() { 277 | _forgit_diff_git_opts=() 278 | _forgit_parse_array _forgit_diff_git_opts "$FORGIT_DIFF_GIT_OPTS" 279 | git diff --color=always "${_forgit_diff_git_opts[@]}" "$@" 280 | } 281 | 282 | _forgit_diff_view() { 283 | local input_line=$1 284 | local diff_context=$2 285 | local repo 286 | local commits=() 287 | repo=$(git rev-parse --show-toplevel) 288 | cd "$repo" || return 1 289 | if [ $# -gt 2 ]; then 290 | IFS=" " read -r -a commits <<< "${*:3}" 291 | fi 292 | echo "$input_line" | _forgit_get_files_from_diff_line | xargs -0 \ 293 | "$FORGIT" exec_diff "${commits[@]}" -U"$diff_context" -- | _forgit_pager diff 294 | } 295 | 296 | _forgit_edit_diffed_file() { 297 | local input_line rootdir 298 | input_line=$1 299 | rootdir=$(git rev-parse --show-toplevel) 300 | filename=$(echo "$input_line" | _forgit_get_single_file_from_diff_line) 301 | $EDITOR "$rootdir/$filename" >/dev/tty /dev/null ; then 318 | if [[ $# -gt 1 ]] && git rev-parse "$2" -- &>/dev/null; then 319 | commits=("$1" "$2") && files=("${@:3}") 320 | else 321 | commits=("$1") && files=("${@:2}") 322 | fi 323 | else 324 | files=("$@") 325 | fi 326 | } 327 | # Git stashes are named "stash@{x}", which contains the fzf placeholder "{x}". 328 | # In order to support passing stashes as arguments to _forgit_diff, we have to 329 | # prevent fzf from interpreting this substring by escaping the opening bracket. 330 | # The string is evaluated a few subsequent times, so we need multiple escapes. 331 | for commit in "${commits[@]}"; do 332 | escaped_commits+="'${commit//\{/\\\\\{}' " 333 | done 334 | opts=" 335 | $FORGIT_FZF_DEFAULT_OPTS 336 | +m -0 --bind=\"enter:execute($FORGIT diff_enter {} $escaped_commits | $FORGIT pager enter)\" 337 | --preview=\"$FORGIT diff_view {} '$_forgit_preview_context' $escaped_commits\" 338 | --bind=\"alt-e:execute($FORGIT edit_diffed_file {})+refresh-preview\" 339 | $FORGIT_DIFF_FZF_OPTS 340 | --prompt=\"${commits[*]} > \" 341 | " 342 | _forgit_diff_git_opts=() 343 | _forgit_parse_array _forgit_diff_git_opts "$FORGIT_DIFF_GIT_OPTS" 344 | git diff --name-status "${_forgit_diff_git_opts[@]}" "${commits[@]}" -- "${files[@]}" | 345 | sed -E 's/^([[:alnum:]]+)[[:space:]]+(.*)$/[\1] \2/' | 346 | sed 's/ / -> /2' | expand -t 8 | 347 | FZF_DEFAULT_OPTS="$opts" fzf 348 | fzf_exit_code=$? 349 | # exit successfully on 130 (ctrl-c/esc) 350 | [[ $fzf_exit_code == 130 ]] && return 0 351 | return $fzf_exit_code 352 | } 353 | 354 | _forgit_exec_show() { 355 | _forgit_show_git_opts=() 356 | _forgit_parse_array _forgit_show_git_opts "$FORGIT_SHOW_GIT_OPTS" 357 | git show --pretty="" --diff-merges=first-parent --color=always "${_forgit_show_git_opts[@]}" "$@" 358 | } 359 | 360 | _forgit_show_view() { 361 | local input_line=$1 362 | local diff_context=$2 363 | local commit=$3 364 | local repo 365 | repo=$(git rev-parse --show-toplevel) 366 | cd "$repo" || return 1 367 | echo "$input_line" | _forgit_get_files_from_diff_line | xargs -0 \ 368 | "$FORGIT" exec_show "${commit}^{commit}" -U"$diff_context" -- | _forgit_pager diff 369 | } 370 | 371 | _forgit_show_preview() { 372 | local input_line=$1 373 | local diff_context=$2 374 | local commit=$3 375 | if [[ "$FZF_PREVIEW_LABEL" =~ "Diff" ]]; then 376 | _forgit_show_view "${input_line}" "${diff_context}" "${commit}" 377 | else 378 | git show --quiet --color=always "${FZF_PROMPT%% *}" 379 | fi 380 | } 381 | 382 | _forgit_show_enter() { 383 | file=$1 384 | commit=$2 385 | _forgit_show_view "$file" "$_forgit_fullscreen_context" "${commit}" 386 | } 387 | 388 | # git show viewer 389 | _forgit_show() { 390 | _forgit_inside_work_tree || return 1 391 | local files opts commit escaped_commit 392 | files=() 393 | if [[ $# -ne 0 ]]; then 394 | if git rev-parse "$1" -- &>/dev/null ; then 395 | commit="$1" && files=("${@:2}") 396 | else 397 | commit="HEAD" && files=("$@") 398 | fi 399 | else 400 | commit="HEAD" 401 | fi 402 | # Escape opening brackets to support stashes (see comment in _forgit_diff) 403 | escaped_commit=${commit//\{/\\\\\{} 404 | opts=" 405 | $FORGIT_FZF_DEFAULT_OPTS 406 | +m -0 --bind=\"enter:execute($FORGIT show_enter {} $escaped_commit | $FORGIT pager enter)\" 407 | --preview=\"$FORGIT show_preview {} '$_forgit_preview_context' $escaped_commit\" 408 | --preview-label=\" Diff \" 409 | --bind=\"alt-e:execute($FORGIT edit_diffed_file {})+refresh-preview\" 410 | --bind=\"alt-t:transform:[[ ! \\\"\$FZF_PREVIEW_LABEL\\\" =~ 'Diff' ]] && 411 | echo 'change-preview-label( Diff )+refresh-preview' || 412 | echo 'change-preview-label( Commit Message )+refresh-preview'\" 413 | $FORGIT_SHOW_FZF_OPTS 414 | --prompt=\"${commit} > \" 415 | " 416 | _forgit_show_git_opts=() 417 | _forgit_parse_array _forgit_show_git_opts "$FORGIT_SHOW_GIT_OPTS" 418 | # Add "^{commit}" suffix after the actual commit. This suppresses the tag information in case it is a tag. 419 | # See: https://git-scm.com/docs/git-show#Documentation/git-show.txt-codegitshow-s--formatsv100commitcode 420 | git show --pretty="" --name-status --diff-merges=first-parent "${_forgit_show_git_opts[@]}" "${commit}^{commit}" \ 421 | -- "${files[@]}" | 422 | sed -E 's/^([[:alnum:]]+)[[:space:]]+(.*)$/[\1] \2/' | 423 | sed 's/ / -> /2' | expand -t 8 | 424 | FZF_DEFAULT_OPTS="$opts" fzf 425 | fzf_exit_code=$? 426 | # exit successfully on 130 (ctrl-c/esc) 427 | [[ $fzf_exit_code == 130 ]] && return 0 428 | return $fzf_exit_code 429 | } 430 | 431 | _forgit_add_preview() { 432 | file=$(echo "$1" | _forgit_get_single_file_from_add_line) 433 | if (git status -s -- "$file" | grep '^??') &>/dev/null; then # diff with /dev/null for untracked files 434 | git diff --color=always --no-index -- /dev/null "$file" | _forgit_pager diff | sed '2 s/added:/untracked:/' 435 | else 436 | git diff --color=always -- "$file" | _forgit_pager diff 437 | fi 438 | } 439 | 440 | _forgit_git_add() { 441 | _forgit_add_git_opts=() 442 | _forgit_parse_array _forgit_add_git_opts "$FORGIT_ADD_GIT_OPTS" 443 | git add "${_forgit_add_git_opts[@]}" "$@" 444 | } 445 | 446 | _forgit_get_single_file_from_add_line() { 447 | # NOTE: paths listed by 'git status -su' mixed with quoted and unquoted style 448 | # remove indicators | remove original path for rename case | remove surrounding quotes 449 | sed 's/^.*] //' | 450 | sed 's/.* -> //' | 451 | sed -e 's/^\"//' -e 's/\"$//' 452 | } 453 | 454 | _forgit_edit_add_file() { 455 | local input_line=$1 456 | filename=$(echo "$input_line" | _forgit_get_single_file_from_add_line) 457 | $EDITOR "$filename" >/dev/tty newest when you multiselect 650 | # The instances of "cut", "nl" and "sort" all serve this purpose 651 | # Please see https://github.com/wfxr/forgit/issues/253 for more details 652 | 653 | opts=" 654 | $FORGIT_FZF_DEFAULT_OPTS 655 | --preview=\"$FORGIT cherry_pick_preview {}\" 656 | --multi --ansi --with-nth 2.. -0 --tiebreak=index 657 | $FORGIT_CHERRY_PICK_FZF_OPTS 658 | " 659 | # Note: do not add any pipe after the fzf call here, otherwise the fzf_exitval is not propagated properly. 660 | # Any eventual post processing can be done afterwards when the "commits" variable is assigned below. 661 | fzf_selection=$(git log --right-only --color=always --cherry-pick --oneline "$base"..."$target" | nl | 662 | FZF_DEFAULT_OPTS="$opts" fzf) 663 | fzf_exitval=$? 664 | [[ $fzf_exitval != 0 ]] && return $fzf_exitval 665 | [[ -z "$fzf_selection" ]] && return $fzf_exitval 666 | 667 | commits=() 668 | while IFS='' read -r commit; do 669 | commits+=("$commit") 670 | done < <(echo "$fzf_selection" | sort -n -k 1 | cut -f2 | cut -d' ' -f1 | _forgit_reverse_lines) 671 | [ ${#commits[@]} -eq 0 ] && return 1 672 | 673 | _forgit_cherry_pick_git_opts=() 674 | _forgit_parse_array _forgit_cherry_pick_git_opts "$FORGIT_CHERRY_PICK_GIT_OPTS" 675 | git cherry-pick "${_forgit_cherry_pick_git_opts[@]}" "${commits[@]}" 676 | } 677 | 678 | _forgit_cherry_pick_from_branch_preview() { 679 | git log --right-only --color=always --cherry-pick --oneline "$1"..."$2" 680 | } 681 | 682 | _forgit_cherry_pick_from_branch() { 683 | _forgit_inside_work_tree || return 1 684 | local opts branch exitval input_branch args base 685 | 686 | base=$(git branch --show-current) 687 | [[ -z "$base" ]] && echo "Current commit is not on a branch." && return 1 688 | 689 | args=("$@") 690 | if [[ $# -ne 0 ]]; then 691 | input_branch=${args[0]} 692 | fi 693 | opts=" 694 | $FORGIT_FZF_DEFAULT_OPTS 695 | +s +m --tiebreak=index --header-lines=1 696 | --preview=\"$FORGIT cherry_pick_from_branch_preview '$base' {1}\" 697 | $FORGIT_CHERRY_PICK_FROM_BRANCH_FZF_OPTS 698 | " 699 | # loop until either the branch selector is closed or a commit to be cherry 700 | # picked has been selected from within a branch 701 | while true 702 | do 703 | if [[ -z $input_branch ]]; then 704 | branch="$(git branch --color=always --all | 705 | LC_ALL=C sort -k1.1,1.1 -rs | 706 | FZF_DEFAULT_OPTS="$opts" fzf | 707 | awk '{print $1}')" 708 | else 709 | branch=$input_branch 710 | fi 711 | 712 | unset input_branch 713 | [[ -z "$branch" ]] && return 1 714 | 715 | _forgit_cherry_pick "$branch" 716 | 717 | exitval=$? 718 | [[ $exitval != 130 ]] || [[ $# -ne 0 ]] && return $exitval 719 | done 720 | } 721 | 722 | _forgit_rebase() { 723 | _forgit_inside_work_tree || return 1 724 | _forgit_contains_non_flags "$@" && { git rebase "$@"; return $?; } 725 | local opts graph target_commit prev_commit 726 | graph=() 727 | [[ $_forgit_log_graph_enable == true ]] && graph=(--graph) 728 | _forgit_rebase_git_opts=() 729 | _forgit_parse_array _forgit_rebase_git_opts "$FORGIT_REBASE_GIT_OPTS" 730 | opts=" 731 | $FORGIT_FZF_DEFAULT_OPTS 732 | +s +m --tiebreak=index 733 | --bind=\"ctrl-y:execute-silent($FORGIT yank_sha {})\" 734 | --preview=\"$FORGIT file_preview {}\" 735 | $FORGIT_REBASE_FZF_OPTS 736 | " 737 | target_commit=$( 738 | git log "${graph[@]}" --color=always --format="$_forgit_log_format" | 739 | _forgit_emojify | 740 | FZF_DEFAULT_OPTS="$opts" fzf | 741 | _forgit_extract_sha) 742 | if [[ -n "$target_commit" ]]; then 743 | prev_commit=$(_forgit_previous_commit "$target_commit") 744 | git rebase -i "${_forgit_rebase_git_opts[@]}" "$@" "$prev_commit" 745 | fi 746 | } 747 | 748 | _forgit_file_preview() { 749 | local sha 750 | sha=$(echo "$1" | _forgit_extract_sha) 751 | shift 752 | echo "$sha" | xargs -I% git show --color=always % -- "$@" | _forgit_pager show 753 | } 754 | 755 | _forgit_fixup() { 756 | _forgit_inside_work_tree || return 1 757 | git diff --cached --quiet && echo 'Nothing to fixup: there are no staged changes.' && return 1 758 | _forgit_edit_commit --fixup "$FORGIT_FIXUP_FZF_OPTS" "$FORGIT_FIXUP_GIT_OPTS" 759 | } 760 | 761 | _forgit_squash() { 762 | _forgit_inside_work_tree || return 1 763 | git diff --cached --quiet && echo 'Nothing to squash: there are no staged changes.' && return 1 764 | _forgit_edit_commit --squash "$FORGIT_SQUASH_FZF_OPTS" "$FORGIT_SQUASH_GIT_OPTS" 765 | } 766 | 767 | _forgit_edit_commit() { 768 | local action fzf_opts opts graph target_commit prev_commit 769 | action=$1 770 | fzf_opts=$2 771 | graph=() 772 | [[ $_forgit_log_graph_enable == true ]] && graph=(--graph) 773 | git_opts=() 774 | _forgit_parse_array git_opts "$3" 775 | opts=" 776 | $FORGIT_FZF_DEFAULT_OPTS 777 | +s +m --tiebreak=index 778 | --bind=\"ctrl-y:execute-silent($FORGIT yank_sha {})\" 779 | --preview=\"$FORGIT file_preview {}\" 780 | $fzf_opts 781 | " 782 | target_commit=$( 783 | git log "${graph[@]}" --color=always --format="$_forgit_log_format" | 784 | _forgit_emojify | 785 | FZF_DEFAULT_OPTS="$opts" fzf | 786 | _forgit_extract_sha) 787 | # GIT_EDITOR=: is needed to skip the editor 788 | if [[ -n "$target_commit" ]] && GIT_EDITOR=: git commit "${git_opts[@]}" "$action" "$target_commit"; then 789 | prev_commit=$(_forgit_previous_commit "$target_commit") 790 | # rebase will fail if there are unstaged changes so --autostash is needed to temporarily stash them 791 | # GIT_SEQUENCE_EDITOR=: is needed to skip the editor 792 | GIT_SEQUENCE_EDITOR=: git rebase --autostash -i --autosquash "$prev_commit" 793 | fi 794 | } 795 | 796 | _forgit_reword() { 797 | _forgit_inside_work_tree || return 1 798 | local opts graph quoted_files target_commit prev_commit 799 | graph=() 800 | [[ $_forgit_log_graph_enable == true ]] && graph=(--graph) 801 | git_opts=() 802 | _forgit_parse_array _forgit_reword_git_opts "$FORGIT_REWORD_GIT_OPTS" 803 | opts=" 804 | $FORGIT_FZF_DEFAULT_OPTS 805 | +s +m --tiebreak=index 806 | --bind=\"ctrl-y:execute-silent($FORGIT yank_sha {})\" 807 | --preview=\"$FORGIT file_preview {}\" 808 | $FORGIT_REWORD_FZF_OPTS 809 | " 810 | target_commit=$( 811 | git log "${graph[@]}" --color=always --format="$_forgit_log_format" | 812 | _forgit_emojify | 813 | FZF_DEFAULT_OPTS="$opts" fzf | 814 | _forgit_extract_sha) 815 | if [[ -n "$target_commit" ]] && git commit "${git_opts[@]}" --fixup=reword:"$target_commit"; then 816 | prev_commit=$(_forgit_previous_commit "$target_commit") 817 | # rebase will fail if there are unstaged changes so --autostash is needed to temporarily stash them 818 | # GIT_SEQUENCE_EDITOR=: is needed to skip the editor 819 | GIT_SEQUENCE_EDITOR=: git rebase --autostash -i --autosquash "$prev_commit" 820 | fi 821 | } 822 | 823 | _forgit_checkout_file_preview() { 824 | git diff --color=always -- "$1" | _forgit_pager diff 825 | } 826 | 827 | _forgit_git_checkout_file() { 828 | _forgit_checkout_file_git_opts=() 829 | _forgit_parse_array _forgit_checkout_file_git_opts "$FORGIT_CHECKOUT_FILE_GIT_OPTS" 830 | git checkout "${_forgit_checkout_file_git_opts[@]}" "$@" 831 | } 832 | 833 | # git checkout-file selector 834 | _forgit_checkout_file() { 835 | _forgit_inside_work_tree || return 1 836 | local files opts 837 | [[ $# -ne 0 ]] && { _forgit_git_checkout_file -- "$@"; return $?; } 838 | opts=" 839 | $FORGIT_FZF_DEFAULT_OPTS 840 | -m -0 841 | --preview=\"$FORGIT checkout_file_preview {}\" 842 | $FORGIT_CHECKOUT_FILE_FZF_OPTS 843 | " 844 | files=() 845 | while IFS='' read -r file; do 846 | files+=("$file") 847 | done < <(_forgit_list_files --modified | 848 | FZF_DEFAULT_OPTS="$opts" fzf) 849 | [[ "${#files[@]}" -gt 0 ]] && _forgit_git_checkout_file "${files[@]}" 850 | } 851 | 852 | _forgit_git_checkout_branch() { 853 | _forgit_checkout_branch_git_opts=() 854 | _forgit_parse_array _forgit_checkout_branch_git_opts "$FORGIT_CHECKOUT_BRANCH_GIT_OPTS" 855 | git checkout "${_forgit_checkout_branch_git_opts[@]}" "$@" 856 | } 857 | 858 | # git checkout-branch selector 859 | _forgit_checkout_branch() { 860 | _forgit_inside_work_tree || return 1 861 | # if called with arguments, check if branch exists, else create a new one 862 | if [[ $# -ne 0 ]]; then 863 | if [[ "$*" == "-" ]] || git show-branch "$@" &>/dev/null; then 864 | git switch "$@" 865 | else 866 | git switch -c "$@" 867 | fi 868 | checkout_status=$? 869 | git status --short 870 | return $checkout_status 871 | fi 872 | 873 | local opts branch 874 | opts=" 875 | $FORGIT_FZF_DEFAULT_OPTS 876 | +s +m --tiebreak=index --header-lines=1 877 | --preview=\"$FORGIT branch_preview {1}\" 878 | $FORGIT_CHECKOUT_BRANCH_FZF_OPTS 879 | " 880 | _forgit_checkout_branch_branch_git_opts=() 881 | _forgit_parse_array _forgit_checkout_branch_branch_git_opts "$FORGIT_CHECKOUT_BRANCH_BRANCH_GIT_OPTS" 882 | branch="$(git branch --color=always "${_forgit_checkout_branch_branch_git_opts[@]:---all}" | LC_ALL=C sort -k1.1,1.1 -rs | 883 | FZF_DEFAULT_OPTS="$opts" fzf | awk '{print $1}')" 884 | [[ -z "$branch" ]] && return 1 885 | 886 | # track the remote branch if possible 887 | if [[ "$branch" == "remotes/"* ]]; then 888 | if git branch | grep -qw "${branch#remotes/*/}"; then 889 | # hack to force creating a new branch which tracks the remote if a local branch already exists 890 | _forgit_git_checkout_branch -b "track/${branch#remotes/*/}" --track "$branch" 891 | elif ! _forgit_git_checkout_branch --track "$branch" 2>/dev/null; then 892 | _forgit_git_checkout_branch "$branch" 893 | fi 894 | else 895 | _forgit_git_checkout_branch "$branch" 896 | fi 897 | } 898 | 899 | _forgit_git_checkout_tag() { 900 | _forgit_checkout_tag_git_opts=() 901 | _forgit_parse_array _forgit_checkout_tag_git_opts "$FORGIT_CHECKOUT_TAG_GIT_OPTS" 902 | git checkout "${_forgit_checkout_tag_git_opts[@]}" "$@" 903 | } 904 | 905 | # git checkout-tag selector 906 | _forgit_checkout_tag() { 907 | _forgit_inside_work_tree || return 1 908 | local opts 909 | [[ $# -ne 0 ]] && { _forgit_git_checkout_tag "$@"; return $?; } 910 | opts=" 911 | $FORGIT_FZF_DEFAULT_OPTS 912 | +s +m --tiebreak=index 913 | --preview=\"$FORGIT branch_preview {}\" 914 | $FORGIT_CHECKOUT_TAG_FZF_OPTS 915 | " 916 | tag="$(git tag -l --sort=-v:refname | FZF_DEFAULT_OPTS="$opts" fzf)" 917 | [[ -z "$tag" ]] && return 1 918 | _forgit_git_checkout_tag "$tag" 919 | } 920 | 921 | _forgit_checkout_commit_preview() { 922 | echo "$1" | _forgit_extract_sha | xargs -I% git show --color=always % | _forgit_pager show 923 | } 924 | 925 | _forgit_git_checkout_commit() { 926 | _forgit_checkout_commit_git_opts=() 927 | _forgit_parse_array _forgit_checkout_commit_git_opts "$FORGIT_CHECKOUT_COMMIT_GIT_OPTS" 928 | git checkout "${_forgit_checkout_commit_git_opts[@]}" "$@" 929 | } 930 | 931 | # git checkout-commit selector 932 | _forgit_checkout_commit() { 933 | _forgit_inside_work_tree || return 1 934 | local opts graph commit 935 | [[ $# -ne 0 ]] && { _forgit_git_checkout_commit "$@"; return $?; } 936 | opts=" 937 | $FORGIT_FZF_DEFAULT_OPTS 938 | +s +m --tiebreak=index 939 | --bind=\"ctrl-y:execute-silent($FORGIT yank_sha {})\" 940 | --preview=\"$FORGIT checkout_commit_preview {}\" 941 | $FORGIT_CHECKOUT_COMMIT_FZF_OPTS 942 | " 943 | graph=() 944 | [[ $_forgit_log_graph_enable == true ]] && graph=(--graph) 945 | commit="$(git log "${graph[@]}" --color=always --format="$_forgit_log_format" | 946 | _forgit_emojify | 947 | FZF_DEFAULT_OPTS="$opts" fzf | _forgit_extract_sha)" 948 | _forgit_git_checkout_commit "$commit" 949 | } 950 | 951 | _forgit_branch_preview() { 952 | # the trailing '--' ensures that this works for branches that have a name 953 | # that is identical to a file 954 | git log "$1" "${_forgit_log_preview_options[@]}" -- 955 | } 956 | 957 | _forgit_git_branch_delete() { 958 | _forgit_branch_delete_git_opts=() 959 | _forgit_parse_array _forgit_branch_delete_git_opts "$FORGIT_BRANCH_DELETE_GIT_OPTS" 960 | git branch "${_forgit_branch_delete_git_opts[@]}" -D "$@" 961 | } 962 | 963 | _forgit_branch_delete() { 964 | _forgit_inside_work_tree || return 1 965 | local opts 966 | [[ $# -ne 0 ]] && { _forgit_git_branch_delete "$@"; return $?; } 967 | 968 | opts=" 969 | $FORGIT_FZF_DEFAULT_OPTS 970 | +s --multi --tiebreak=index --header-lines=1 971 | --preview=\"$FORGIT branch_preview {1}\" 972 | $FORGIT_BRANCH_DELETE_FZF_OPTS 973 | " 974 | 975 | for branch in $(git branch --color=always | 976 | LC_ALL=C sort -k1.1,1.1 -rs | 977 | FZF_DEFAULT_OPTS="$opts" fzf | 978 | awk '{print $1}') 979 | do 980 | _forgit_git_branch_delete "$branch" 981 | done 982 | } 983 | 984 | _forgit_revert_preview() { 985 | echo "$1" | 986 | cut -f2- | 987 | _forgit_extract_sha | 988 | xargs -I% git show --color=always % | 989 | _forgit_pager show 990 | } 991 | 992 | _forgit_git_revert() { 993 | _forgit_revert_commit_git_opts=() 994 | _forgit_parse_array _forgit_revert_commit_git_opts "$FORGIT_REVERT_COMMIT_GIT_OPTS" 995 | git revert "${_forgit_revert_commit_git_opts[@]}" "$@" 996 | } 997 | 998 | # git revert-commit selector 999 | _forgit_revert_commit() { 1000 | _forgit_inside_work_tree || return 1 1001 | local opts commits IFS 1002 | [[ $# -ne 0 ]] && { _forgit_git_revert "$@"; return $?; } 1003 | 1004 | opts=" 1005 | $FORGIT_FZF_DEFAULT_OPTS 1006 | -m +s --tiebreak=index 1007 | --ansi --with-nth 2.. 1008 | --preview=\"$FORGIT revert_preview {}\" 1009 | $FORGIT_REVERT_COMMIT_FZF_OPTS 1010 | " 1011 | graph=() 1012 | [[ $_forgit_log_graph_enable == true ]] && graph=(--graph) 1013 | 1014 | # in this function, we do something interesting to maintain proper ordering as it's assumed 1015 | # you generally want to revert newest->oldest when you multiselect 1016 | # The instances of "cut", "nl" and "sort" all serve this purpose 1017 | # Please see https://github.com/wfxr/forgit/issues/253 for more details 1018 | 1019 | commits=() 1020 | while IFS='' read -r commit; do 1021 | commits+=("$commit") 1022 | done < <( 1023 | git log "${graph[@]}" --color=always --format="$_forgit_log_format" | 1024 | _forgit_emojify | 1025 | nl | 1026 | FZF_DEFAULT_OPTS="$opts" fzf | 1027 | sort -n -k 1 | 1028 | cut -f2- | 1029 | sed 's/^[^a-f^0-9]*\([a-f0-9]*\).*/\1/') 1030 | 1031 | [ ${#commits[@]} -eq 0 ] && return 1 1032 | 1033 | _forgit_git_revert "${commits[@]}" 1034 | } 1035 | 1036 | _forgit_blame_preview() { 1037 | if _forgit_is_file_tracked "$1"; then 1038 | _forgit_blame_git_opts=() 1039 | _forgit_parse_array _forgit_blame_git_opts "$FORGIT_BLAME_GIT_OPTS" 1040 | git blame --date=short "${_forgit_blame_git_opts[@]}" "$@" | _forgit_pager blame 1041 | else 1042 | echo "File not tracked" 1043 | fi 1044 | } 1045 | 1046 | _forgit_git_blame() { 1047 | _forgit_blame_git_opts=() 1048 | _forgit_parse_array _forgit_blame_git_opts "$FORGIT_BLAME_GIT_OPTS" 1049 | git blame "${_forgit_blame_git_opts[@]}" "$@" 1050 | } 1051 | 1052 | # git blame viewer 1053 | _forgit_blame() { 1054 | _forgit_inside_work_tree || return 1 1055 | local opts flags file 1056 | _forgit_contains_non_flags "$@" && { _forgit_git_blame "$@"; return $?; } 1057 | flags=() 1058 | while IFS='' read -r flag; do 1059 | flags+=("$flag") 1060 | done < <(git rev-parse --flags "$@") 1061 | opts=" 1062 | $FORGIT_FZF_DEFAULT_OPTS 1063 | --preview=\"$FORGIT blame_preview {} ${flags[*]}\" 1064 | $FORGIT_BLAME_FZF_OPTS 1065 | " 1066 | # flags is not quoted here, which is fine given that they are retrieved 1067 | # with git rev-parse and can only contain flags 1068 | file=$(FZF_DEFAULT_OPTS="$opts" fzf) 1069 | [[ -z "$file" ]] && return 1 1070 | _forgit_git_blame "$file" "${flags[@]}" 1071 | } 1072 | 1073 | # git ignore generator 1074 | export FORGIT_GI_REPO_REMOTE=${FORGIT_GI_REPO_REMOTE:-https://github.com/dvcs/gitignore} 1075 | export FORGIT_GI_REPO_LOCAL="${FORGIT_GI_REPO_LOCAL:-${XDG_CACHE_HOME:-$HOME/.cache}/forgit/gi/repos/dvcs/gitignore}" 1076 | export FORGIT_GI_TEMPLATES=${FORGIT_GI_TEMPLATES:-$FORGIT_GI_REPO_LOCAL/templates} 1077 | 1078 | _forgit_path_preview() { 1079 | local path name ext pager 1080 | path=$1 1081 | name=$2 1082 | ext=$3 1083 | pager=$4 1084 | quoted_files=() 1085 | while IFS='' read -r file; do 1086 | quoted_files+=("'$file'") 1087 | done < <(find -L "$path" -type f -name "$name" -o -name "$name$ext") 1088 | _forgit_pager "$pager" "${quoted_files[@]}" 2>/dev/null 1089 | } 1090 | 1091 | _forgit_ignore() { 1092 | [ -d "$FORGIT_GI_REPO_LOCAL" ] \ 1093 | || _forgit_repo_update "$FORGIT_GI_REPO_REMOTE" "$FORGIT_GI_REPO_LOCAL" 1094 | local IFS args opts 1095 | opts=" 1096 | $FORGIT_FZF_DEFAULT_OPTS 1097 | -m --preview-window='right:70%' 1098 | --preview=\"$FORGIT path_preview $FORGIT_GI_TEMPLATES {2} .gitignore ignore\" 1099 | $FORGIT_IGNORE_FZF_OPTS 1100 | " 1101 | args=("$@") 1102 | if [[ $# -eq 0 ]]; then 1103 | args=() 1104 | while IFS='' read -r arg; do 1105 | args+=("$arg") 1106 | done < <(_forgit_paths_list "$FORGIT_GI_TEMPLATES" .gitignore | 1107 | nl -w4 -s' ' | 1108 | FZF_DEFAULT_OPTS="$opts" fzf | awk '{print $2}') 1109 | fi 1110 | [ ${#args[@]} -eq 0 ] && return 1 1111 | _forgit_path_get "$FORGIT_GI_TEMPLATES" .gitignore "${args[@]}" 1112 | } 1113 | 1114 | # git attributes generator 1115 | export FORGIT_ATTR_REPO_REMOTE=${FORGIT_ATTR_REPO_REMOTE:-https://github.com/gitattributes/gitattributes} 1116 | export FORGIT_ATTR_REPO_LOCAL=${FORGIT_ATTR_REPO_LOCAL:-${XDG_CACHE_HOME:-$HOME/.cache}/forgit/gat/repos/gitattributes/gitattributes} 1117 | export FORGIT_ATTR_TEMPLATES=${FORGIT_ATTR_TEMPLATES:-$FORGIT_ATTR_REPO_LOCAL} 1118 | 1119 | _forgit_attributes() { 1120 | [ -d "$FORGIT_ATTR_REPO_LOCAL" ] \ 1121 | || _forgit_repo_update "$FORGIT_ATTR_REPO_REMOTE" "$FORGIT_ATTR_REPO_LOCAL" 1122 | local IFS args opts 1123 | opts=" 1124 | $FORGIT_FZF_DEFAULT_OPTS 1125 | -m --preview-window='right:70%' 1126 | --preview=\"$FORGIT path_preview $FORGIT_ATTR_TEMPLATES {2} .gitattributes attributes\" 1127 | $FORGIT_ATTRIBUTES_FZF_OPTS 1128 | " 1129 | args=("$@") 1130 | if [[ $# -eq 0 ]]; then 1131 | args=() 1132 | while IFS='' read -r arg; do 1133 | args+=("$arg") 1134 | done < <(_forgit_paths_list "$FORGIT_ATTR_TEMPLATES" .gitattributes | 1135 | nl -w4 -s' ' | 1136 | FZF_DEFAULT_OPTS="$opts" fzf | awk '{print $2}') 1137 | fi 1138 | [ ${#args[@]} -eq 0 ] && return 1 1139 | _forgit_path_get "$FORGIT_ATTR_TEMPLATES" .gitattributes "${args[@]}" 1140 | } 1141 | 1142 | _forgit_repo_update() { 1143 | local remote path 1144 | remote=$1 1145 | path=$2 1146 | if [[ -d "$path" ]]; then 1147 | _forgit_info 'Updating repo...' 1148 | (cd "$path" && git pull --no-rebase --ff) || return 1 1149 | else 1150 | _forgit_info 'Initializing repo...' 1151 | git clone --depth=1 "$remote" "$path" 1152 | fi 1153 | } 1154 | 1155 | _forgit_path_get() { 1156 | local path ext item filename header 1157 | path=$1 1158 | ext=$2 1159 | shift 2 1160 | for item in "$@"; do 1161 | if filename=$(find -L "$path" -type f \( -iname "${item}$ext" -o -iname "${item}" \) -print -quit); then 1162 | [[ -z "$filename" ]] && _forgit_warn "No template found for '$item'." && continue 1163 | header="${filename##*/}" && header="${header%"$ext"}" 1164 | echo "### $header" && cat "$filename" && echo 1165 | fi 1166 | done 1167 | } 1168 | 1169 | _forgit_paths_list() { 1170 | local path ext 1171 | path=$1 1172 | ext=$2 1173 | find "$path" -name "*$ext" -print |sed -e "s#$ext\$##" -e 's#.*/##' -e '/^$/d' | sort -fu 1174 | } 1175 | 1176 | public_commands=( 1177 | "add" 1178 | "attributes" 1179 | "blame" 1180 | "branch_delete" 1181 | "checkout_branch" 1182 | "checkout_commit" 1183 | "checkout_file" 1184 | "checkout_tag" 1185 | "cherry_pick" 1186 | "cherry_pick_from_branch" 1187 | "clean" 1188 | "diff" 1189 | "fixup" 1190 | "squash" 1191 | "reword" 1192 | "ignore" 1193 | "log" 1194 | "reflog" 1195 | "rebase" 1196 | "reset_head" 1197 | "revert_commit" 1198 | "show" 1199 | "stash_show" 1200 | "stash_push" 1201 | ) 1202 | 1203 | private_commands=( 1204 | "add_preview" 1205 | "blame_preview" 1206 | "branch_preview" 1207 | "checkout_commit_preview" 1208 | "checkout_file_preview" 1209 | "cherry_pick_from_branch_preview" 1210 | "cherry_pick_preview" 1211 | "clean_preview" 1212 | "diff_enter" 1213 | "exec_show" 1214 | "file_preview" 1215 | "path_preview" 1216 | "revert_preview" 1217 | "reset_head_preview" 1218 | "show_enter" 1219 | "show_preview" 1220 | "stash_push_preview" 1221 | "stash_show_preview" 1222 | "yank_sha" 1223 | "yank_stash_name" 1224 | "log_preview" 1225 | "log_enter" 1226 | "exec_diff" 1227 | "diff_view" 1228 | "edit_diffed_file" 1229 | "edit_add_file" 1230 | "pager" 1231 | ) 1232 | 1233 | cmd="$1" 1234 | shift 1235 | 1236 | # shellcheck disable=SC2076 1237 | if [[ ! " ${public_commands[*]} " =~ " ${cmd} " ]] && [[ ! " ${private_commands[*]} " =~ " ${cmd} " ]]; then 1238 | if [[ -z "$cmd" ]]; then 1239 | printf "forgit: missing command\n\n" 1240 | else 1241 | printf "forgit: '%s' is not a valid forgit command.\n\n" "$cmd" 1242 | fi 1243 | printf "The following commands are supported:\n" 1244 | printf "\t%s\n" "${public_commands[@]}" 1245 | exit 1 1246 | fi 1247 | 1248 | _forgit_"${cmd}" "$@" 1249 | -------------------------------------------------------------------------------- /completions/_git-forgit: -------------------------------------------------------------------------------- 1 | #compdef git-forgit -p forgit::* 2 | #description Utility tool for using git interactively 3 | # 4 | # forgit completions for zsh 5 | # 6 | # Place this file in your $fpath (e.g. /usr/share/zsh/site-functions) to enable 7 | # tab completions for forgit. 8 | 9 | _git-branches() { 10 | _alternative "branches:branchname:($(git branch -a --format '%(refname:short)'))" 11 | } 12 | 13 | _git-checkout-file() { 14 | _alternative "files:filename:($(git ls-files --modified))" 15 | } 16 | 17 | _git-stash-show() { 18 | _alternative "files:filename:($(git stash list | sed -n -e 's/:.*//p'))" 19 | } 20 | 21 | # The completions for git already define a _git-diff completion function, but 22 | # it provides the wrong results when called from _git-forgit because it heavily 23 | # depends on the context it's been called from (usage of $curcontext and 24 | # $CURRENT), so we use a simplified version here which always provides the same 25 | # results independent of the context. 26 | _git-forgit-diff() { 27 | _alternative \ 28 | 'commit-ranges::__git_commit_ranges' \ 29 | 'blobs-and-trees-in-treeish::__git_blobs_and_trees_in_treeish' \ 30 | 'files::__git_changed-in-working-tree_files' \ 31 | 'blobs::__git_blobs ' 32 | } 33 | 34 | _git-staged() { 35 | _alternative "files:filename:($(git diff --name-only --staged))" 36 | } 37 | 38 | _git-forgit-reflog() { 39 | declare -a cmds 40 | cmds=('expire:prune old reflog entries' 'delete:delete entries from reflog' 'show:show log of ref' 'exists:check whether a ref has a reflog') 41 | _alternative 'cmds:: _describe -t cmds cmd cmds' 'refs:: __git_references' 42 | } 43 | 44 | _git-forgit() { 45 | local subcommand cmd 46 | subcommand="${words[1]}" 47 | if [[ "$subcommand" != "forgit"* ]]; then 48 | # Forgit is obviously called via a git alias. Get the original 49 | # aliased subcommand and proceed as if it was the previous word. 50 | cmd=$(git config --get "alias.$subcommand" | cut -d ' ' -f 2) 51 | else 52 | # The last word is the relevant command 53 | cmd=${words[(( ${#words} - 1 ))]} 54 | fi 55 | 56 | case ${cmd} in 57 | forgit) 58 | local -a subcommands 59 | subcommands=( 60 | 'add:git add selector' 61 | 'blame:git blame viewer' 62 | 'branch_delete:git branch deletion selector' 63 | 'checkout_branch:git checkout branch selector' 64 | 'checkout_commit:git checkout commit selector' 65 | 'checkout_file:git checkout-file selector' 66 | 'checkout_tag:git checkout tag selector' 67 | 'cherry_pick:git cherry-picking' 68 | 'cherry_pick_from_branch:git cherry-picking with interactive branch selection' 69 | 'clean:git clean selector' 70 | 'diff:git diff viewer' 71 | 'fixup:git fixup' 72 | 'ignore:git ignore generator' 73 | 'log:git commit viewer' 74 | 'reflog:git reflog viewer' 75 | 'rebase:git rebase' 76 | 'reset_head:git reset HEAD (unstage) selector' 77 | 'revert_commit:git revert commit selector' 78 | 'reword:git fixup=reword' 79 | 'squash:git squash' 80 | 'stash_show:git stash viewer' 81 | 'stash_push:git stash push selector' 82 | ) 83 | _describe -t commands 'git forgit' subcommands 84 | ;; 85 | add) _git-add ;; 86 | branch_delete) _git-branches ;; 87 | checkout_branch) _git-branches ;; 88 | checkout_commit) __git_recent_commits ;; 89 | checkout_file) _git-checkout-file ;; 90 | checkout_tag) __git_tags ;; 91 | cherry_pick) _git-cherry-pick ;; 92 | cherry_pick_from_branch) _git-branches ;; 93 | clean) _git-clean ;; 94 | diff) _git-forgit-diff ;; 95 | fixup) __git_branch_names ;; 96 | log) _git-log ;; 97 | reflog) _git-forgit-reflog ;; 98 | rebase) _git-rebase ;; 99 | reset_head) _git-staged ;; 100 | revert_commit) __git_recent_commits ;; 101 | reword) __git_branch_names ;; 102 | squash) __git_branch_names ;; 103 | stash_show) _git-stash-show ;; 104 | show) _git-show ;; 105 | esac 106 | } 107 | 108 | # We're reusing existing completion functions, so load those first 109 | # if not already loaded and check if completion function exists afterwards. 110 | (( $+functions[_git-add] )) || _git 111 | (( $+functions[_git-add] )) || return 1 112 | # Completions for forgit plugin shell functions (also works for aliases) 113 | compdef _git-add forgit::add 114 | compdef _git-branches forgit::branch::delete 115 | compdef _git-branches forgit::checkout::branch 116 | compdef __git_recent_commits forgit::checkout::commit 117 | compdef _git-checkout-file forgit::checkout::file 118 | compdef __git_tags forgit::checkout::tag 119 | compdef _git-cherry-pick forgit::cherry::pick 120 | compdef _git-branches forgit::cherry::pick::from::branch 121 | compdef _git-clean forgit::clean 122 | compdef _git-forgit-diff forgit::diff 123 | compdef __git_branch_names forgit::fixup 124 | compdef _git-log forgit::log 125 | compdef _git-reflog forgit::reflog 126 | compdef _git-rebase forgit::rebase 127 | compdef _git-staged forgit::reset::head 128 | compdef __git_recent_commits forgit::revert::commit 129 | compdef __git_branch_names forgit::reword 130 | compdef __git_branch_names forgit::squash 131 | compdef _git-stash-show forgit::stash::show 132 | compdef _git-show forgit::show 133 | 134 | # this is the case of calling the command and pressing tab 135 | # the very first time of a shell session, we have to manually 136 | # call the dispatch function 137 | if [[ $funcstack[1] == "_git-forgit" ]]; then 138 | _git-forgit "$@" 139 | fi 140 | -------------------------------------------------------------------------------- /completions/git-forgit.bash: -------------------------------------------------------------------------------- 1 | # forgit completions for bash 2 | 3 | # When using forgit as a subcommand of git, put this file in one of the 4 | # following places and it will be loaded automatically on tab completion of 5 | # 'git forgit' or any configured git aliases of it: 6 | # 7 | # /usr/share/bash-completion/completions 8 | # ~/.local/share/bash-completion/completions 9 | # 10 | # When using forgit via the shell plugin, source this file explicitly after 11 | # forgit.plugin.zsh to enable tab completion for shell functions and aliases. 12 | 13 | _git_branch_delete() 14 | { 15 | __gitcomp_nl "$(__git_heads)" 16 | } 17 | 18 | _git_checkout_branch() 19 | { 20 | __gitcomp_nl "$(__git branch -a --format '%(refname:short)')" 21 | } 22 | 23 | _git_checkout_file() 24 | { 25 | __gitcomp_nl "$(__git ls-files --modified)" 26 | } 27 | 28 | _git_checkout_tag() 29 | { 30 | __gitcomp_nl "$(__git_tags)" 31 | } 32 | 33 | _git_stash_show() 34 | { 35 | __gitcomp_nl "$(__git stash list | sed -n -e 's/:.*//p')" 36 | } 37 | 38 | # Completion for git-forgit 39 | # This includes git aliases, e.g. "alias.cb=forgit checkout_branch" will 40 | # correctly complete available branches on "git cb". 41 | _git_forgit() 42 | { 43 | local subcommand cword cur prev cmds 44 | 45 | subcommand="${COMP_WORDS[1]}" 46 | if [[ "$subcommand" != "forgit" ]] 47 | then 48 | # Forgit is obviously called via a git alias. Get the original 49 | # aliased subcommand and proceed as if it was the previous word. 50 | prev=$(git config --get "alias.$subcommand" | cut -d' ' -f 2) 51 | cword=$((${COMP_CWORD} + 1)) 52 | else 53 | cword=${COMP_CWORD} 54 | prev=${COMP_WORDS[COMP_CWORD-1]} 55 | fi 56 | 57 | cur=${COMP_WORDS[COMP_CWORD]} 58 | 59 | cmds=" 60 | add 61 | blame 62 | branch_delete 63 | checkout_branch 64 | checkout_commit 65 | checkout_file 66 | checkout_tag 67 | cherry_pick 68 | cherry_pick_from_branch 69 | clean 70 | diff 71 | fixup 72 | ignore 73 | log 74 | reflog 75 | rebase 76 | reset_head 77 | revert_commit 78 | reword 79 | show 80 | squash 81 | stash_show 82 | stash_push 83 | " 84 | 85 | case ${cword} in 86 | 2) 87 | COMPREPLY=($(compgen -W "${cmds}" -- ${cur})) 88 | ;; 89 | 3) 90 | case ${prev} in 91 | add) _git_add ;; 92 | branch_delete) _git_branch_delete ;; 93 | checkout_branch) _git_checkout_branch ;; 94 | checkout_commit) _git_checkout ;; 95 | checkout_file) _git_checkout_file ;; 96 | checkout_tag) _git_checkout_tag ;; 97 | cherry_pick) _git_cherry_pick ;; 98 | cherry_pick_from_branch) _git_checkout_branch ;; 99 | clean) _git_clean ;; 100 | diff) _git_diff ;; 101 | fixup) _git_branch ;; 102 | log) _git_log ;; 103 | reflog) _git_reflog ;; 104 | rebase) _git_rebase ;; 105 | reset_head) _git_reset ;; 106 | revert_commit) _git_revert ;; 107 | reword) _git_branch ;; 108 | show) _git_show ;; 109 | squash) _git_branch ;; 110 | stash_show) _git_stash_show ;; 111 | esac 112 | ;; 113 | *) 114 | COMPREPLY=() 115 | ;; 116 | esac 117 | } 118 | 119 | # Check if forgit plugin is loaded 120 | if [[ $(type -t forgit::add) == function ]] 121 | then 122 | # We're reusing existing git completion functions, so load those first 123 | # and check if completion function exists afterwards. 124 | _completion_loader git 125 | [[ $(type -t __git_complete) == function ]] || return 1 126 | 127 | # Completion for forgit plugin shell functions 128 | __git_complete forgit::add _git_add 129 | __git_complete forgit::branch::delete _git_branch_delete 130 | __git_complete forgit::checkout::branch _git_checkout_branch 131 | __git_complete forgit::checkout::commit _git_checkout 132 | __git_complete forgit::checkout::file _git_checkout_file 133 | __git_complete forgit::checkout::tag _git_checkout_tag 134 | __git_complete forgit::cherry::pick _git_cherry_pick 135 | __git_complete forgit::cherry::pick::from::branch _git_checkout_branch 136 | __git_complete forgit::clean _git_clean 137 | __git_complete forgit::diff _git_diff 138 | __git_complete forgit::fixup _git_branch 139 | __git_complete forgit::log _git_log 140 | __git_complete forgit::reflog _git_reflog 141 | __git_complete forgit::rebase _git_rebase 142 | __git_complete forgit::reset::head _git_reset 143 | __git_complete forgit::revert::commit _git_revert 144 | __git_complete forgit::reword _git_branch 145 | __git_complete forgit::show _git_show 146 | __git_complete forgit::squash _git_branch 147 | __git_complete forgit::stash::show _git_stash_show 148 | 149 | # Completion for forgit plugin shell aliases 150 | if [[ -z "$FORGIT_NO_ALIASES" ]]; then 151 | __git_complete "${forgit_add}" _git_add 152 | __git_complete "${forgit_branch_delete}" _git_branch_delete 153 | __git_complete "${forgit_checkout_branch}" _git_checkout_branch 154 | __git_complete "${forgit_checkout_commit}" _git_checkout 155 | __git_complete "${forgit_checkout_file}" _git_checkout_file 156 | __git_complete "${forgit_checkout_tag}" _git_checkout_tag 157 | __git_complete "${forgit_cherry_pick}" _git_checkout_branch 158 | __git_complete "${forgit_clean}" _git_clean 159 | __git_complete "${forgit_diff}" _git_diff 160 | __git_complete "${forgit_fixup}" _git_branch 161 | __git_complete "${forgit_log}" _git_log 162 | __git_complete "${forgit_reflog}" _git_reflog 163 | __git_complete "${forgit_rebase}" _git_rebase 164 | __git_complete "${forgit_reset_head}" _git_reset 165 | __git_complete "${forgit_revert_commit}" _git_revert 166 | __git_complete "${forgit_reword_commit}" _git_branch 167 | __git_complete "${forgit_show}" _git_show 168 | __git_complete "${forgit_squash}" _git_branch 169 | __git_complete "${forgit_stash_show}" _git_stash_show 170 | fi 171 | fi 172 | -------------------------------------------------------------------------------- /completions/git-forgit.fish: -------------------------------------------------------------------------------- 1 | # 2 | # forgit completions for fish plugin 3 | # 4 | # Place this file inside your /completions/ directory. 5 | # It's usually located at ~/.config/fish/completions/. The file is lazily 6 | # sourced when git-forgit command or forgit subcommand of git is invoked. 7 | 8 | function __fish_forgit_needs_subcommand 9 | for subcmd in add blame branch_delete checkout_branch checkout_commit checkout_file checkout_tag \ 10 | cherry_pick cherry_pick_from_branch clean diff fixup ignore log reflog rebase reset_head \ 11 | revert_commit reword squash stash_show stash_push 12 | if contains -- $subcmd (commandline -opc) 13 | return 1 14 | end 15 | end 16 | return 0 17 | end 18 | 19 | # Load helper functions in git completion file 20 | not functions -q __fish_git && source $__fish_data_dir/completions/git.fish 21 | 22 | # No file completion by default 23 | complete -c git-forgit -x 24 | 25 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a add -d 'git add selector' 26 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a blame -d 'git blame viewer' 27 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a branch_delete -d 'git branch deletion selector' 28 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a checkout_branch -d 'git checkout branch selector' 29 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a checkout_commit -d 'git checkout commit selector' 30 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a checkout_file -d 'git checkout-file selector' 31 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a checkout_tag -d 'git checkout tag selector' 32 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a cherry_pick -d 'git cherry-picking' 33 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a cherry_pick_from_branch -d 'git cherry-picking with interactive branch selection' 34 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a clean -d 'git clean selector' 35 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a diff -d 'git diff viewer' 36 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a fixup -d 'git fixup' 37 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a ignore -d 'git ignore generator' 38 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a log -d 'git commit viewer' 39 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a reflog -d 'git reflog viewer' 40 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a rebase -d 'git rebase' 41 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a reset_head -d 'git reset HEAD (unstage) selector' 42 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a revert_commit -d 'git revert commit selector' 43 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a reword -d 'git fixup=reword' 44 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a show -d 'git show viewer' 45 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a squash -d 'git squash' 46 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a stash_show -d 'git stash viewer' 47 | complete -c git-forgit -n __fish_forgit_needs_subcommand -a stash_push -d 'git stash push selector' 48 | 49 | complete -c git-forgit -n '__fish_seen_subcommand_from add' -a "(complete -C 'git add ')" 50 | complete -c git-forgit -n '__fish_seen_subcommand_from branch_delete' -a "(__fish_git_local_branches)" 51 | complete -c git-forgit -n '__fish_seen_subcommand_from checkout_branch' -a "(complete -C 'git switch ')" 52 | complete -c git-forgit -n '__fish_seen_subcommand_from checkout_commit' -a "(__fish_git_commits)" 53 | complete -c git-forgit -n '__fish_seen_subcommand_from checkout_file' -a "(__fish_git_files modified)" 54 | complete -c git-forgit -n '__fish_seen_subcommand_from checkout_tag' -a "(__fish_git_tags)" -d Tag 55 | complete -c git-forgit -n '__fish_seen_subcommand_from cherry_pick' -a "(complete -C 'git cherry-pick ')" 56 | complete -c git-forgit -n '__fish_seen_subcommand_from clean' -a "(__fish_git_files untracked ignored)" 57 | complete -c git-forgit -n '__fish_seen_subcommand_from diff' -a "(complete -C 'git diff ')" 58 | complete -c git-forgit -n '__fish_seen_subcommand_from fixup' -a "(__fish_git_local_branches)" 59 | complete -c git-forgit -n '__fish_seen_subcommand_from log' -a "(complete -C 'git log ')" 60 | complete -c git-forgit -n '__fish_seen_subcommand_from reflog' -a "(complete -C 'git reflog ')" 61 | complete -c git-forgit -n '__fish_seen_subcommand_from rebase' -a "(complete -C 'git rebase ')" 62 | complete -c git-forgit -n '__fish_seen_subcommand_from reset_head' -a "(__fish_git_files all-staged)" 63 | complete -c git-forgit -n '__fish_seen_subcommand_from revert_commit' -a "(__fish_git_commits)" 64 | complete -c git-forgit -n '__fish_seen_subcommand_from reword' -a "(__fish_git_local_branches)" 65 | complete -c git-forgit -n '__fish_seen_subcommand_from show' -a "(complete -C 'git show ')" 66 | complete -c git-forgit -n '__fish_seen_subcommand_from squash' -a "(__fish_git_local_branches)" 67 | complete -c git-forgit -n '__fish_seen_subcommand_from stash_show' -a "(__fish_git_complete_stashes)" 68 | complete -c git-forgit -n '__fish_seen_subcommand_from stash_push' -a "(__fish_git_files modified deleted modified-staged-deleted)" 69 | -------------------------------------------------------------------------------- /conf.d/bin/git-forgit: -------------------------------------------------------------------------------- 1 | ../../bin/git-forgit -------------------------------------------------------------------------------- /conf.d/forgit.plugin.fish: -------------------------------------------------------------------------------- 1 | # MIT (c) Chris Apple 2 | 3 | set -l install_dir (dirname (status dirname)) 4 | set -x FORGIT_INSTALL_DIR "$install_dir/conf.d" 5 | set -x FORGIT "$FORGIT_INSTALL_DIR/bin/git-forgit" 6 | if not test -e "$FORGIT" 7 | set -x FORGIT_INSTALL_DIR "$install_dir/vendor_conf.d" 8 | set -x FORGIT "$FORGIT_INSTALL_DIR/bin/git-forgit" 9 | end 10 | 11 | function forgit::warn 12 | printf "%b[Warn]%b %s\n" '\e[0;33m' '\e[0m' "$argv" >&2 13 | end 14 | 15 | # backwards compatibility: 16 | # export all user-defined FORGIT variables to make them available in git-forgit 17 | set unexported_vars 0 18 | set | awk -F ' ' '{ print $1 }' | grep FORGIT_ | while read var 19 | if not set -x | grep -q "^$var\b" 20 | if test $unexported_vars = 0 21 | forgit::warn "Config options have to be exported in future versions of forgit." 22 | forgit::warn "Please update your config accordingly:" 23 | end 24 | forgit::warn " set -x $var \"$$var\"" 25 | set unexported_vars (math $unexported_vars + 1) 26 | set -x $var $$var 27 | end 28 | end 29 | 30 | # alias `git-forgit` to the full-path of the command 31 | alias git-forgit "$FORGIT" 32 | 33 | # register abbreviations 34 | if test -z "$FORGIT_NO_ALIASES" 35 | abbr -a -- (string collect $forgit_add; or string collect "ga") git-forgit add 36 | abbr -a -- (string collect $forgit_reset_head; or string collect "grh") git-forgit reset_head 37 | abbr -a -- (string collect $forgit_log; or string collect "glo") git-forgit log 38 | abbr -a -- (string collect $forgit_reflog; or string collect "grl") git-forgit reflog 39 | abbr -a -- (string collect $forgit_diff; or string collect "gd") git-forgit diff 40 | abbr -a -- (string collect $forgit_show; or string collect "gso") git-forgit show 41 | abbr -a -- (string collect $forgit_ignore; or string collect "gi") git-forgit ignore 42 | abbr -a -- (string collect $forgit_attributes; or string collect "gat") git-forgit attributes 43 | abbr -a -- (string collect $forgit_checkout_file; or string collect "gcf") git-forgit checkout_file 44 | abbr -a -- (string collect $forgit_checkout_branch; or string collect "gcb") git-forgit checkout_branch 45 | abbr -a -- (string collect $forgit_branch_delete; or string collect "gbd") git-forgit branch_delete 46 | abbr -a -- (string collect $forgit_clean; or string collect "gclean") git-forgit clean 47 | abbr -a -- (string collect $forgit_stash_show; or string collect "gss") git-forgit stash_show 48 | abbr -a -- (string collect $forgit_stash_push; or string collect "gsp") git-forgit stash_push 49 | abbr -a -- (string collect $forgit_cherry_pick; or string collect "gcp") git-forgit cherry_pick_from_branch 50 | abbr -a -- (string collect $forgit_rebase; or string collect "grb") git-forgit rebase 51 | abbr -a -- (string collect $forgit_fixup; or string collect "gfu") git-forgit fixup 52 | abbr -a -- (string collect $forgit_squash; or string collect "gsq") git-forgit squash 53 | abbr -a -- (string collect $forgit_reword; or string collect "grw") git-forgit reword 54 | abbr -a -- (string collect $forgit_checkout_commit; or string collect "gco") git-forgit checkout_commit 55 | abbr -a -- (string collect $forgit_revert_commit; or string collect "grc") git-forgit revert_commit 56 | abbr -a -- (string collect $forgit_blame; or string collect "gbl") git-forgit blame 57 | abbr -a -- (string collect $forgit_checkout_tag; or string collect "gct") git-forgit checkout_tag 58 | end 59 | -------------------------------------------------------------------------------- /forgit.plugin.sh: -------------------------------------------------------------------------------- 1 | forgit.plugin.zsh -------------------------------------------------------------------------------- /forgit.plugin.zsh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # MIT (c) Wenxuan Zhang 3 | 4 | # all commands are prefixed with command and all built-ins with builtin. 5 | # These shell built-ins prevent the wrong commands getting executed in case a 6 | # user added a shell alias with the same name. 7 | 8 | forgit::error() { command printf "%b[Error]%b %s\n" '\e[0;31m' '\e[0m' "$@" >&2; builtin return 1; } 9 | forgit::warn() { command printf "%b[Warn]%b %s\n" '\e[0;33m' '\e[0m' "$@" >&2; } 10 | 11 | # determine installation path 12 | if [[ -n "$ZSH_VERSION" ]]; then 13 | # shellcheck disable=2277,2296,2299 14 | 0="${ZERO:-${${0:#$ZSH_ARGZERO}:-${(%):-%N}}}" 15 | # shellcheck disable=2277,2296,2298 16 | 0="${${(M)0:#/*}:-$PWD/$0}" 17 | FORGIT_INSTALL_DIR="${0:h}" 18 | elif [[ -n "$BASH_VERSION" ]]; then 19 | FORGIT_INSTALL_DIR="$(command dirname -- "${BASH_SOURCE[0]}")" 20 | else 21 | forgit::error "Only zsh and bash are supported" 22 | fi 23 | 24 | builtin export FORGIT_INSTALL_DIR 25 | FORGIT="$FORGIT_INSTALL_DIR/bin/git-forgit" 26 | 27 | # backwards compatibility: 28 | # export all user-defined FORGIT variables to make them available in git-forgit 29 | unexported_vars=0 30 | # Set posix mode in bash to only get variables, see #256. 31 | [[ -n "$BASH_VERSION" ]] && builtin set -o posix 32 | builtin set | command awk -F '=' '{ print $1 }' | command grep FORGIT_ | while builtin read -r var; do 33 | if ! builtin export | command grep -q "\(^$var=\|^export $var=\)"; then if [[ $unexported_vars == 0 ]]; then 34 | forgit::warn "Config options have to be exported in future versions of forgit." 35 | forgit::warn "Please update your config accordingly:" 36 | fi 37 | forgit::warn " export $var" 38 | unexported_vars=$((unexported_vars + 1)) 39 | # shellcheck disable=SC2163 40 | builtin export "$var" 41 | fi 42 | done 43 | builtin unset unexported_vars 44 | [[ -n "$BASH_VERSION" ]] && builtin set +o posix 45 | 46 | # register shell functions 47 | forgit::log() { 48 | "$FORGIT" log "$@" 49 | } 50 | 51 | forgit::reflog() { 52 | "$FORGIT" reflog "$@" 53 | } 54 | 55 | forgit::diff() { 56 | "$FORGIT" diff "$@" 57 | } 58 | 59 | forgit::show() { 60 | "$FORGIT" show "$@" 61 | } 62 | 63 | forgit::add() { 64 | "$FORGIT" add "$@" 65 | } 66 | 67 | forgit::reset::head() { 68 | "$FORGIT" reset_head "$@" 69 | } 70 | 71 | forgit::stash::show() { 72 | "$FORGIT" stash_show "$@" 73 | } 74 | 75 | forgit::stash::push() { 76 | "$FORGIT" stash_push "$@" 77 | } 78 | 79 | forgit::clean() { 80 | "$FORGIT" clean "$@" 81 | } 82 | 83 | forgit::cherry::pick() { 84 | "$FORGIT" cherry_pick "$@" 85 | } 86 | 87 | forgit::cherry::pick::from::branch() { 88 | "$FORGIT" cherry_pick_from_branch "$@" 89 | } 90 | 91 | forgit::rebase() { 92 | "$FORGIT" rebase "$@" 93 | } 94 | 95 | forgit::fixup() { 96 | "$FORGIT" fixup "$@" 97 | } 98 | 99 | forgit::squash() { 100 | "$FORGIT" squash "$@" 101 | } 102 | 103 | forgit::reword() { 104 | "$FORGIT" reword "$@" 105 | } 106 | 107 | forgit::checkout::file() { 108 | "$FORGIT" checkout_file "$@" 109 | } 110 | 111 | forgit::checkout::branch() { 112 | "$FORGIT" checkout_branch "$@" 113 | } 114 | 115 | forgit::checkout::tag() { 116 | "$FORGIT" checkout_tag "$@" 117 | } 118 | 119 | forgit::checkout::commit() { 120 | "$FORGIT" checkout_commit "$@" 121 | } 122 | 123 | forgit::branch::delete() { 124 | "$FORGIT" branch_delete "$@" 125 | } 126 | 127 | forgit::revert::commit() { 128 | "$FORGIT" revert_commit "$@" 129 | } 130 | 131 | forgit::blame() { 132 | "$FORGIT" blame "$@" 133 | } 134 | 135 | forgit::ignore() { 136 | "$FORGIT" ignore "$@" 137 | } 138 | 139 | forgit::ignore::update() { 140 | "$FORGIT" ignore_update "$@" 141 | } 142 | 143 | forgit::ignore::get() { 144 | "$FORGIT" ignore_get "$@" 145 | } 146 | 147 | forgit::ignore::list() { 148 | "$FORGIT" ignore_list "$@" 149 | } 150 | 151 | forgit::ignore::clean() { 152 | "$FORGIT" ignore_clean "$@" 153 | } 154 | 155 | forgit::attributes() { 156 | "$FORGIT" attributes "$@" 157 | } 158 | 159 | # register aliases 160 | # shellcheck disable=SC2139 161 | if [[ -z "$FORGIT_NO_ALIASES" ]]; then 162 | 163 | builtin export forgit_add="${forgit_add:-ga}" 164 | builtin export forgit_reset_head="${forgit_reset_head:-grh}" 165 | builtin export forgit_log="${forgit_log:-glo}" 166 | builtin export forgit_reflog="${forgit_reflog:-grl}" 167 | builtin export forgit_diff="${forgit_diff:-gd}" 168 | builtin export forgit_show="${forgit_show:-gso}" 169 | builtin export forgit_ignore="${forgit_ignore:-gi}" 170 | builtin export forgit_attributes="${forgit_attributes:-gat}" 171 | builtin export forgit_checkout_file="${forgit_checkout_file:-gcf}" 172 | builtin export forgit_checkout_branch="${forgit_checkout_branch:-gcb}" 173 | builtin export forgit_checkout_commit="${forgit_checkout_commit:-gco}" 174 | builtin export forgit_checkout_tag="${forgit_checkout_tag:-gct}" 175 | builtin export forgit_branch_delete="${forgit_branch_delete:-gbd}" 176 | builtin export forgit_revert_commit="${forgit_revert_commit:-grc}" 177 | builtin export forgit_clean="${forgit_clean:-gclean}" 178 | builtin export forgit_stash_show="${forgit_stash_show:-gss}" 179 | builtin export forgit_stash_push="${forgit_stash_push:-gsp}" 180 | builtin export forgit_cherry_pick="${forgit_cherry_pick:-gcp}" 181 | builtin export forgit_rebase="${forgit_rebase:-grb}" 182 | builtin export forgit_fixup="${forgit_fixup:-gfu}" 183 | builtin export forgit_squash="${forgit_squash:-gsq}" 184 | builtin export forgit_reword="${forgit_reword:-grw}" 185 | builtin export forgit_blame="${forgit_blame:-gbl}" 186 | 187 | builtin alias "${forgit_add}"='forgit::add' 188 | builtin alias "${forgit_reset_head}"='forgit::reset::head' 189 | builtin alias "${forgit_log}"='forgit::log' 190 | builtin alias "${forgit_reflog}"='forgit::reflog' 191 | builtin alias "${forgit_diff}"='forgit::diff' 192 | builtin alias "${forgit_show}"='forgit::show' 193 | builtin alias "${forgit_ignore}"='forgit::ignore' 194 | builtin alias "${forgit_attributes}"='forgit::attributes' 195 | builtin alias "${forgit_checkout_file}"='forgit::checkout::file' 196 | builtin alias "${forgit_checkout_branch}"='forgit::checkout::branch' 197 | builtin alias "${forgit_checkout_commit}"='forgit::checkout::commit' 198 | builtin alias "${forgit_checkout_tag}"='forgit::checkout::tag' 199 | builtin alias "${forgit_branch_delete}"='forgit::branch::delete' 200 | builtin alias "${forgit_revert_commit}"='forgit::revert::commit' 201 | builtin alias "${forgit_clean}"='forgit::clean' 202 | builtin alias "${forgit_stash_show}"='forgit::stash::show' 203 | builtin alias "${forgit_stash_push}"='forgit::stash::push' 204 | builtin alias "${forgit_cherry_pick}"='forgit::cherry::pick::from::branch' 205 | builtin alias "${forgit_rebase}"='forgit::rebase' 206 | builtin alias "${forgit_fixup}"='forgit::fixup' 207 | builtin alias "${forgit_squash}"='forgit::squash' 208 | builtin alias "${forgit_reword}"='forgit::reword' 209 | builtin alias "${forgit_blame}"='forgit::blame' 210 | 211 | fi 212 | --------------------------------------------------------------------------------