├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── CONTRIBUTING.md ├── README.md ├── ROADMAP.md ├── TESTING.md ├── addon-info.json ├── autoload └── gitv │ └── util │ └── line.vim ├── doc ├── gitv.txt └── tags ├── ftplugin └── gitv.vim ├── img ├── gitv-bisecting.png ├── gitv-commit.png ├── gitv-diffsplit.png ├── gitv-diffstat.png └── gitv-rebasing.png ├── plugin └── gitv.vim └── syntax └── gitv.vim /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Hi there! 2 | 3 | If you're submitting a bug, please run `:call Gitv_GetDebugInfo()` and paste your clipboard here. 4 | 5 | Otherwise, please disregard this message. 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tags 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | gitv is not heavily active. 4 | No real guidelines are currently in place outside of bug submissions. 5 | 6 | ## Bugs 7 | 8 | Please make sure you are running the latest version of gitv. 9 | 10 | gitv interacts with a lot of technologies, and there is a lot of needed 11 | information for bugs. 12 | 13 | Please open gitv and run `:call Gitv_GetDebugInfo()` and paste the contents of 14 | your clipboard into the pull request. 15 | 16 | ## Testing 17 | 18 | We are in need of testers across various platforms, especially OSX and Windows. 19 | 20 | If you are interested in testing, please [email][5] or get in touch via 21 | [matrix][2]. 22 | 23 | ## Questions, Feature Requests 24 | 25 | Questions and feature requests are highly encouraged. 26 | 27 | Please submit questions and feature requests to the [issue tracker][1] or ask 28 | them on [matrix.org][2]. 29 | 30 | ## Pull Requests 31 | 32 | [Pull requests and patches][3] are encouraged. 33 | 34 | You may want to check [the roadmap][4] to see the currently planned features 35 | before contributing, or to get an idea of what to help out with. 36 | 37 | [1]: https://github.com/gregsexton/gitv/issues 38 | [2]: https://riot.im/app/#/room/#gitv:matrix.org 39 | [3]: https://github.com/gregsexton/gitv/pulls 40 | [4]: https://github.com/gregsexton/gitv/blob/master/ROADMAP.md 41 | [5]: mailto:r.l.bongers@gmail.com 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Maintainers Needed 2 | 3 | I am no longer maintaining this project as I no longer use Gitv. 4 | If you would like to maintain this project, please email gregsexton. 5 | 6 | # Readme 7 | 8 | gitv is a repository viewer similar to gitk. It is an extension of the 9 | [fugitive git plugin][5] for vim. It is essentially a wrapper around 10 | `git log --graph`, allowing you to see your branching history. It allows you to 11 | view commits, diffstats, inline diffs, and file or folder specific history, and 12 | more. It allows you to perform operations on the commit tree interactively, 13 | such as merges, cherry picks, reversions, resets, deletions, checkouts, 14 | bisections, and rebase operations. 15 | 16 | This repo has the most bleeding edge version of gitv. Stable versions are 17 | available at [the vim.org page][1]. 18 | 19 | [Bugs, suggestions,][2] [pull requests and patches][3] are all very welcome. 20 | 21 | We are currently actively looking for feature requests and bugs regarding the 22 | latest [pre-release][4]. 23 | 24 | ## Basic usage 25 | 26 | Start the plugin by running :Gitv in Vim when viewing a file in a git repository. 27 | 28 | This plugin is an extension of the [fugitive git plugin][5] by Tim Pope. 29 | 30 | I hope you like it! 31 | 32 | ## Installation 33 | 34 | You will need the [tpope/fugitive][5] plugin installed and working for gitv to work. 35 | 36 | For Windows, use the `~\vimfiles` directory instead of `~/.vim` 37 | 38 | 39 | | Method | Instalation instructions | 40 | | -------------- | -------------------------------------------------------------------------------------------------------------- | 41 | | Manual | Merge the `autoload`, `doc`, `ftplugin`, `plugin`, and `syntax` folders into their respective `~/.vim` folders | 42 | | [NeoBundle][6] | Add `NeoBundle 'gregsexton/gitv'` to `.vimrc` | 43 | | [Pathogen][7] | Run `git clone https://github.com/gregsexton/gitv ~/.vim/bundle/gitv` | 44 | | [Plug][8] | Add `Plug 'gregsexton/gitv', {'on': ['Gitv']}` to `.vimrc` | 45 | | [Vundle][9] | Add `Plugin 'gregsexton/gitv'` to `.vimrc` | 46 | 47 | ### Compatibility 48 | 49 | gitv was developed against Vim 7.3 and later 8.0 but earlier versions of Vim 50 | should work. Vim 7.2+ is recommended as it ships with syntax highlighting for 51 | many Git file types. Vim 7.3+ is recommended for UTF-8 support. 52 | 53 | gitv now has basic neovim support. 54 | 55 | ## Purpose 56 | 57 | gitv is a 'gitk clone' plugin for the text editor Vim. The goal is to give you 58 | a similar set of functionality as a repository viewer. Using this plugin you 59 | can view a repository's history including branching and merging, you can see 60 | which commits refs point to. You can quickly and easily view what changed to 61 | which files and when. You can perform arbitrary diffs (using Vim's excellent 62 | built in diff functionality) and you can easily check out whole commits and 63 | branches or just individual files if need be. 64 | 65 | Throw in the fact that it is running in Vim and you get for free: the ability 66 | to move over repository history quickly and precisely using Vim's built in 67 | movement operators. You get excellent code syntax highlighting due to Vim's 68 | built in ability. You can open up all sorts of repository views in multiple 69 | windows and position them exactly how you like. You can take advantage of Vim's 70 | registers to copy multiple fragments of code from previous commits. The list 71 | goes on. 72 | 73 | ## Links 74 | 75 | Click [here][10] for help on our official [matrix.org][11] server. 76 | 77 | Future changes are viewable in [the roadmap][12]. 78 | 79 | A tentative release schedule is available in [the milestone view][13]. 80 | 81 | You can download stable release versions (and vote for gitv!) at 82 | [gitv’s page][1] on `vim.org`. 83 | 84 | ## Screenshots 85 | 86 | ### commit preview 87 | 88 | ![gitv commit preview](http://raw.github.com/gregsexton/gitv/master/img/gitv-commit.png) 89 | 90 | ### diff splitting 91 | 92 | ![gitv diffsplit](http://raw.github.com/gregsexton/gitv/master/img/gitv-diffsplit.png) 93 | 94 | ### diff stat-ing 95 | 96 | ![gitv diffstat](http://raw.github.com/gregsexton/gitv/master/img/gitv-diffstat.png) 97 | 98 | ### interactive bisecting 99 | 100 | ![gitv interactive bisecting](http://raw.github.com/gregsexton/gitv/master/img/gitv-bisecting.png) 101 | 102 | ### interactive rebasing 103 | 104 | ![gitv interactive rebasing](http://raw.github.com/gregsexton/gitv/master/img/gitv-rebasing.png) 105 | 106 | [1]: http://www.vim.org/scripts/script.php?script_id=3574 107 | [2]: https://github.com/gregsexton/gitv/issues 108 | [3]: https://github.com/gregsexton/gitv/pulls 109 | [4]: https://github.com/gregsexton/gitv/releases/tag/v1.3.1 110 | [5]: https://github.com/tpope/vim-fugitive 111 | [6]: https://github.com/Shougo/neobundle.vim 112 | [7]: https://github.com/tpope/vim-pathogen 113 | [8]: https://github.com/junegunn/vim-plug 114 | [9]: https://github.com/gmarik/vundle 115 | [10]: https://riot.im/app/#/room/#gitv:matrix.org 116 | [11]: http://matrix.org/ 117 | [12]: https://github.com/gregsexton/gitv/blob/master/ROADMAP.md 118 | [13]: https://github.com/gregsexton/gitv/milestones 119 | [15]: http://www.vim.org/scripts/script.php?script_id=3574 120 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | This document describes upcoming releases and planned features/enhancements. 4 | I no longer maintain this project, so this roadmap will probably not reflect the plan of future maintainers. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 38 | 39 | 40 | 41 | 42 | 50 | 51 | 52 | 53 | 54 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 79 | 80 | 81 | 82 | 83 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
VersionGoalsFeatures
1.3.1 (complete)Incorporate new features present in other branch viewers and fix long standing bugs 17 |
    18 |
  • Implement bisecting directly inside the plugin
  • 19 |
  • Implement rebasing directly inside the plugin
  • 20 |
  • Add robust key remapping
  • 21 |
22 |
1.4 (stable, pending testing)Improve stability of new features and incorporate community feedback 28 |
    29 |
  • Fixes for broken/duplicate bindings
  • 30 |
  • More binding name consistency
  • 31 |
  • Better rebasing/bisecting documentation
  • 32 |
  • Better bisecting UX ("next" only targets cursor on visual selection)
  • 33 |
  • Fixed custom split directions breaking certain functions (split direction is hardcoded until 1.4.1 or later) - thanks to @synic
  • 34 |
  • Added helper to get debugging information
  • 35 |
  • Added rudamentary neovim support - thanks to the Neovim team
  • 36 |
37 |
1.4.1Improve window system 43 |
    44 |
  • Reworked window creation and switching system
  • 45 |
  • Configurable window layout
  • 46 |
  • Reduced layout mangling in browser mode
  • 47 |
  • Stable preview window functionality
  • 48 |
49 |
1.4.2Improve command running and output 55 |
    56 |
  • Reworked git command running system
  • 57 |
  • Improved error output
  • 58 |
  • New informational output system
  • 59 |
  • Better compatibility with neovim's new command running system
  • 60 |
61 |
1.5 (stable)Improve stability of the reworked features and incorporate community feedbackPending community feedback
1.5.1Improve customizability and function of the preview window 72 |
    73 |
  • Reworked preview window creation system
  • 74 |
  • Customizable preview formatting
  • 75 |
  • More kinds of preview window output
  • 76 |
  • Preview window binding and settings system
  • 77 |
78 |
1.5.2Improve customizability of log formatting 84 |
    85 |
  • Reworked ref/commit retrieval system
  • 86 |
  • Reworked log format parsing system
  • 87 |
88 |
1.6 (stable)Improve stability of new customizable systems and incorporate community feedbackPending community feedback
97 | -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | - [ ] File browser mode 2 | - [ ] Rebasing mode 3 | - [ ] Bisecting mode 4 | - [ ] With custom bindings 5 | -------------------------------------------------------------------------------- /addon-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "fugitive": {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /autoload/gitv/util/line.vim: -------------------------------------------------------------------------------- 1 | "AUTHOR: Greg Sexton 2 | "MAINTAINER: Roger Bongers 3 | "WEBSITE: http://www.gregsexton.org/portfolio/gitv/ 4 | "LICENSE: Same terms as Vim itself (see :help license). 5 | " Gitv line-related utility functions 6 | 7 | if exists('g:autoloaded_gitv_util_line') 8 | finish 9 | endif 10 | let g:autoloaded_gitv_util_line = 1 11 | 12 | fu! gitv#util#line#sha(lineNumber) "{{{ 13 | let l = getline(a:lineNumber) 14 | let sha = matchstr(l, "\\[\\zs[0-9a-f]\\{7,40}\\ze\\]$") 15 | return sha 16 | endf "}}} 17 | 18 | fu! gitv#util#line#refs(line) "{{{ 19 | let l = getline(a:line) 20 | let refstr = matchstr(l, "^\\(\\(|\\|\\/\\|\\\\\\|\\*\\)\\s\\?\\)*\\s\\+(\\zs.\\{-}\\ze)") 21 | let refs = split(refstr, ', \| -> ') 22 | return refs 23 | endf "}}} 24 | 25 | " vim:set et sw=4 ts=4 fdm=marker: 26 | -------------------------------------------------------------------------------- /doc/gitv.txt: -------------------------------------------------------------------------------- 1 | gitv -- gitk for vim. 2 | 3 | AUTHOR: Greg Sexton *gitv-author* 4 | MAINTAINER: Roger Bongers 5 | WEBSITE: http://www.gregsexton.org/portfolio/gitv/ 6 | LICENSE: Same terms as Vim itself (see :help license). 7 | NOTES: Much of the credit for gitv goes to Tim Pope and the fugitive plugin 8 | where this plugin either uses functionality directly or was inspired 9 | heavily. 10 | 11 | gitv *gitv* 12 | 13 | 1. Introduction |gitv-introduction| 14 | 2. Installation |gitv-installation| 15 | 3. Usage |gitv-usage| 16 | 4. Configuration Options |gitv-config-options| 17 | 5. Changelog |gitv-changelog| 18 | 6. Misc |gitv-misc| 19 | 20 | ============================================================================== 21 | 1. Introduction *gitv-introduction* 22 | 23 | |gitv| is a 'gitk clone' plugin for the text editor Vim. The goal is to give 24 | you a similar set of functionality as a repository viewer. Using this plugin 25 | you can view a repository's history including branching and merging, you can 26 | see which commits refs point to. You can quickly and easily view what changed 27 | to which files and when. You can perform arbitrary diffs (using Vim's 28 | excellent built in diff functionality) and you can easily check out whole 29 | commits and branches or just individual files if need be. 30 | 31 | Throw in the fact that it is running in Vim and you get for free: the ability 32 | to move over repository history quickly and precisely using Vim's built in 33 | movement operators. You get excellent code syntax highlighting due to Vim's 34 | built in ability. You can open up all sorts of repository views in multiple 35 | windows and position them exactly how you like. You can take advantage of 36 | Vim's registers to copy multiple fragments of code from previous commits. The 37 | list goes on. 38 | 39 | This plugin is an extension of the |fugitive| plugin. 40 | 41 | I hope you like it! 42 | 43 | ============================================================================== 44 | 2. Installation *gitv-installation* 45 | 46 | Install in ~/.vim, or in ~\vimfiles if you're on Windows. This plugin should 47 | be fully pathogen compatible if you want to install it this way. 48 | 49 | |gitv| was developed against Vim 7.3 but earlier versions of Vim should work. 50 | Vim 7.2+ is recommended as it ships with syntax highlighting for many Git file 51 | types. You will also need the |fugitive| plugin installed and working for 52 | |gitv| to work. 53 | 54 | ============================================================================== 55 | 3. Usage *gitv-usage* 56 | 57 | |gitv| defines the following command. 58 | 59 | :[range]Gitv[!] [args] 60 | 61 | Invoking this command on a buffer that belongs to a git 62 | repository causes the gitv browser to open. '!' causes gitv 63 | to open in file mode rather than browser mode. Any [args] 64 | supplied are passed on to the gitv viewer and can be used to 65 | narrow the commits that are shown. If this command is run 66 | on a buffer not belonging to a git repository a message 67 | stating 'Not a git repository.' is displayed. 68 | 69 | When used with a [range] this has no effect in browser mode. 70 | In file mode it narrows the commits shown to only those 71 | affecting lines in the range. See section 3.8 for more 72 | details. 73 | 74 | The following abbreviation is also defined so that you can type :gitv without 75 | capitalisation. The abbreviation is defined in such a way that this 76 | substitution is only performed when 'gitv' is the first word on the command 77 | line. 78 | > 79 | cabbrev gitv Gitv 80 | < 81 | 82 | 3.1 Browser mode 83 | 84 | |gitv| has two distinct modes. Browser mode and file mode. The browser mode is 85 | opened in a new tab and allows the repository history to be viewed for all 86 | files. This is activated by running :Gitv without a '!'. 87 | 88 | In this mode you can view the entire repository history and see which files 89 | were changed with each commit. This mode tries to closely resemble gitk. 90 | 91 | 3.2 File mode 92 | 93 | File mode is opened in a |preview-window| above the buffer you are currently in. 94 | This view is tailored to act on the buffer that :Gitv! was run from, the 95 | "focused" file. Whilst in this mode all actions performed are specific to the 96 | single focused file. The browser only shows commits where the focused file 97 | changed. Selecting a commit, views the focused file as it was in that commit. 98 | Performing a check out only checks the focused file out as it was in the 99 | commit. And so on. See 3.4 for the differences. 100 | 101 | 3.3 Arguments 102 | 103 | You can pass arguments to the :Gitv command. These allow you to filter and 104 | narrow the commits shown. Essentially, gitv is a glorified 'git log' wrapper 105 | and so any flag that 'git log' accepts so will gitv. gitv does not inspect the 106 | arguments passed on to 'git log' and so may not work if they don't make sense. 107 | 108 | Without any arguments gitv behaves just like gitk and git log without 109 | arguments. It will display the commit history for the currently checked out 110 | branch. 111 | 112 | Gitv will also accept and autocomplete filenames. Only filenames at the end of 113 | the command are supported, as in 'git log'. 114 | 115 | Here are some particularly useful examples of arguments that could be 116 | passed to :Gitv. For more info see 'git help log' and in particular the 117 | section: "Commit Limiting". 118 | 119 | Flag Description ~ 120 | 121 | --all View repository history for all refs. 122 | 123 | --bisect If a bisect is started, show only the remaining commits. 124 | 125 | .. Show only commits between the named two commits. When 126 | either or is omitted, it defaults to 127 | HEAD, i.e. the tip of the current branch. For a more 128 | complete list of ways to spell and , 129 | see gitrevisions(7). 130 | 131 | --merges View only merge commits. 132 | 133 | -S Look for differences that introduce or remove an 134 | instance of . Note that this is different than 135 | the string simply appearing in diff output; see the 136 | pickaxe entry in gitdiffcore(7) for more details. 137 | 138 | -G Look for differences whose added or removed line 139 | matches the given . 140 | 141 | 3.4 Key mappings. *gitv-browser-mappings* 142 | 143 | This is a list of key mappings that will work only in the gitv browser window. 144 | Where appropriate the differences in action is described for the two gitv 145 | modes. 146 | 147 | Mode Map Description~ 148 | 149 | normal Opens a commit. In browser mode this will show the 150 | commit header information including the commit 151 | message. It will also display a full diff showing all the 152 | changes to files. 153 | 154 | Tip: if you press on anything sensible you can 155 | view the expected output. For example pressing on 156 | the line beginning a file diff, it will display the 157 | diff using Vim's built in diff viewing capability. 158 | Pressing on the tree sha will list all the files 159 | in the commit and pressing on one of these will 160 | show that file as it was in the commit. And so on. 161 | 162 | Pressing on the line "-- Load More --" will load 163 | |g:Gitv_CommitStep| more commits. 164 | 165 | In file mode this will open the focused file as it was 166 | in the currently selected diff. This allows you to 167 | easily go back in time and look at the focused file. 168 | 169 | Pressing on the top line in file mode opens the 170 | current working copy of the focused file. 171 | 172 | normal In either browser or file mode, this moves down to 173 | J the next commit and opens it. This allows you to 174 | quickly move from commit to commit and view either 175 | the commit details or the file itself, depending on 176 | the mode, in one action. 177 | 178 | normal The same as but move back a commit. 179 | K 180 | 181 | normal o The same as but opens in a new |split|. 182 | 183 | 184 | normal O The same as but opens in a new |tab|. 185 | 186 | 187 | normal s The same as but opens in a new |vsplit|. 188 | 189 | 190 | normal gw Switch between the preview window and browser. 191 | 192 | normal i This performs the same thing as in browser mode. 193 | In file mode it opens the commit details rather than 194 | the focused file. (NOTE: many terminal emulators do 195 | not support ). 196 | 197 | normal q Quits gitv. In browser mode this will close the entire 198 | tab. In file mode this closes only the |preview-window|. 199 | Note: in browser mode this will close the tab 200 | regardless of any windows you may have opened as well 201 | as the gitv windows. 202 | 203 | normal a Switches the '--all' argument and updates the browser 204 | window. 205 | 206 | normal u Forces an update of the content of the browser window. 207 | 208 | normal co Performs a 'git checkout' of the commit the cursor is 209 | on. In both modes this will present you with a choice 210 | of whether you would like to checkout the sha or any 211 | ref that might point to this commit. 212 | 213 | File mode differs in that it doesn't check out the 214 | entire commit but just the focused file in that 215 | commit. 216 | 217 | Tip: in gVim this will present you with a pop up dialog. 218 | You can make this a text choice by performing ':set 219 | guioptions+=c.' 220 | 221 | normal D Performs a diff using Vim's built in diff viewing 222 | capabilities. This does nothing in browser mode. In 223 | file mode it will diff the current file with the 224 | focused file in the commit under the cursor. 225 | 226 | visual D In visual mode this performs a diff against the file 227 | in the commit at the top of the selection against the 228 | file in the commit at the bottom of the selection. The 229 | newest file is always on the right. 230 | 231 | normal S Open a diffstat of everything that has changed since 232 | the commit under the cursor, in both browse and 233 | normal mode. 234 | 235 | visual S In visual mode this works in exactly the same way as 236 | normal mode. However, it only shows what has changed 237 | in the range of commits that are highlighted. 238 | 239 | visual m Merges the commit in either the top or bottom line of 240 | the selection into the commit specified by the other 241 | end of the selection. This uses prompts to guide you 242 | through the merge process and should be fairly 243 | intuitive. By default, it will also ask for 244 | confirmation if you wish to perform a fast-forward 245 | merge or not. If you have defined g:Gitv_MergeArgs, 246 | you will not be prompted to fast forward. 247 | 248 | gitv does not allow you to merge arbitrary commits. 249 | The lines at the top and bottom of the selection 250 | must contain refs in the form of tags, remotes or 251 | local branches. 252 | 253 | visual cp Cherry-Picks the selected commits to the active 254 | branch. 255 | 256 | normal cp Same as visual cp 257 | 258 | visual rb Reset (mixed) the active branch to the top of the 259 | selected commits. 260 | 261 | normal rb Same as visual rb 262 | 263 | visual rbs Reset (soft) the active branch to the top of the 264 | selected commits. 265 | 266 | normal rbs Same as visual rbs 267 | 268 | visual rbh Reset (hard) the active branch to the top of the 269 | selected commits. 270 | 271 | normal rbh Same as visual rbh 272 | 273 | normal rev Revert the commit. If the commit is a merge, selects 274 | the first parent for reverting to. 275 | 276 | visual rev Same as rev, but revert a range of commits. If a 277 | merge is within the range, fails by necessity. 278 | 279 | visual d Delete a branch or tag on the selected line. You will 280 | be asked which branch/tag to use. 281 | 282 | normal d Same as visual d 283 | 284 | normal m Merges the first ref found on the current line in to 285 | the branch currently pointed to by HEAD. By default, 286 | this will prompt you if you wish to use a fast-forward 287 | merge. If you have defined g:Gitv_MergeArgs, you 288 | will not be prompted to fast forward. 289 | 290 | normal git Enters command mode with ":Git " already typed for 291 | you. Just a convenient shortcut for executing git 292 | commands and watching them affect the repository. 293 | 294 | normal yc Copy the sha on the current line in either mode. 295 | This also copies it to the '+' register so that it 296 | can be pasted externally. If there is no commit on 297 | the current line the behaviour (although harmless) 298 | is undefined. 299 | 300 | Here is a list of extra key mappings that can be used to efficiently move 301 | around a repository history in the browser window. 302 | 303 | Mode Map Description~ 304 | 305 | normal x Jumps the cursor forward to the next branching point 306 | in the history. 307 | 308 | normal X Jumps the cursor backward to the previous branching 309 | point in the history. 310 | 311 | normal r Jumps the cursor forward to the next ref in the 312 | history. 313 | 314 | normal R Jumps the cursor backward to the previous ref in the 315 | history. 316 | 317 | normal P Jumps the cursor to the commit referenced by HEAD. 318 | 319 | normal p Jumps the cursor to the commit which is the parent of 320 | current line. When on a merge commit, a |count| 321 | may be used to choose a parent other than the first. 322 | 323 | Here is a list of mappings for rebasing. 324 | 325 | Mode Map Description~ 326 | 327 | normal grr Rebase the current HEAD onto the ref or commit under 328 | the cursor. A choice will be given of multiple refs. 329 | 330 | normal grR Mark the commit under the line for reword. 331 | 332 | normal grE Mark the commit under the line for edit. 333 | 334 | normal grS Mark the commit under the line for squash. 335 | 336 | normal grF Mark the commit under the line for fixup. 337 | 338 | normal grX Mark the commit under the line for exec. A command must 339 | be entered to be executed. The command will be 340 | executed after performing an instruction on the 341 | commit. By default, the commit will be picked. 342 | 343 | normal grD Mark the commit under the line for dropping. 344 | 345 | normal grP Mark the commit under the line for picking. Since this 346 | is the default instruction, if every commit is marked 347 | for picking, no commits will be considered marked. 348 | This also removes exec commands from marking commits 349 | with grX. 350 | 351 | normal grs Start or stop (disable) an interactive rebase. 352 | Interactive rebasing is only available in browser mode. 353 | Merge commits are preserved. 354 | If the rebase has already been started, merely enables 355 | rebase mode. 356 | If starting the rebase without instructions, it begins 357 | at the commit before the commit under the cursor in 358 | edit mode. This is a limitation of interactive 359 | rebasing. 360 | If any commits have been marked for an instruction, 361 | automatically runs the interactive rebase. 362 | If enabled in browser mode with files specified, will 363 | drop the file names while the rebase is in progress. 364 | 365 | normal gra Abort the current rebase. 366 | 367 | normal grn Go to the next commit in the current rebase (skip). 368 | Accepts a count. 369 | 370 | normal grc Continue through the rebase until input is needed. 371 | 372 | visual grR, Same as normal mode, but marks every commit. 373 | grE, 374 | grS, 375 | grF, 376 | grX, 377 | grD, 378 | grP 379 | 380 | visual grs, Same as normal mode, but will attempt to rebase from 381 | grr a ref at the top or bottom to the other end of the 382 | selection. 383 | 384 | Here is a list of mappings for bisecting. 385 | 386 | Mode Map Description~ 387 | 388 | normal gbs Begin the bisecting process. If the bisecting process 389 | has been started elsewhere, merely enables bisect mode. 390 | If the bisecting process has already been enabled, 391 | disables bisecting mode. 392 | Works in both modes. 393 | If file names have been specified, only bisects the 394 | selected files. 395 | Mnemonic- git bisect start/git bisect stop. 396 | 397 | visual gbs Same as normal mode, but if the bisecting process has 398 | not yet been started, marks the first commit selected 399 | as bad and the last commit selected as good. 400 | 401 | normal gbg Mark the commit under the cursor as good. 402 | 403 | visual gbg Mark selected commits as good. 404 | 405 | normal gbb Mark the commit under the cursor as bad. 406 | 407 | visual gbb Mark selected commits as bad. 408 | 409 | normal gbn Skip the current commit. Accepts a count. If a count 410 | is given, skips the number of times specified. 411 | Mnemonic- git bisect next. 412 | 413 | visual gbn Skip the selected range of commits. 414 | 415 | normal gbr End the bisect process. Mnemonic- git bisect reset. 416 | 417 | normal gbl Save the current bisect history to a file. Edit the log 418 | file to correct mistakes and replay later. 419 | Mnemonic- git bisect log. 420 | 421 | normal gbp Replay a saved log file. 422 | 423 | 3.5 Commands 424 | 425 | Running the |:Git| command in the commit browser window, in either mode, will 426 | cause the |:Git| command to be run as expected but the commit history will 427 | automatically update to reflect any changes too. 428 | 429 | 3.6 Windows 430 | 431 | In browser mode, two windows are opened initially. The "browser window" that 432 | displays the commit history and the "preview window" that shows the currently 433 | selected commit. 434 | 435 | In file mode, a |preview-window| is opened above the current file. This is a 436 | browser window filtered to show commits only affecting the focused file. The 437 | window holding the focused file acts as the preview window in this mode. 438 | 439 | NOTE: In both modes the buffer in the preview window is wiped after replacing 440 | it. This is to stop the quick build up of fugitive buffers. A buffer will not 441 | be wiped if it contains unsaved changes. Buffers are not wiped when opening a 442 | commit in a split, vsplit or a new tab. 443 | 444 | When opening a commit, the window it will be opened in is determined by simple 445 | rules. If in browser mode and the layout is vertical it will open in the 446 | window to the immediate right (exactly as if you performed l). If in a 447 | horizontal layout, it will be opened in the window immediately below (exactly 448 | as if you performed j). If in file mode, it will open exactly like 449 | browser mode split horizontally. NOTE: It is for this reason that you should 450 | not move the browser window as it will cause commits to be opened in 451 | unexpected places. 452 | 453 | 3.7 Folding 454 | 455 | Folding of branches is supported in the browser window. Initially all folds 456 | are always open and will open on a reload. You can collapse any branch by 457 | using Vim's built in fold operators. See |folding| for more details. 458 | 459 | 3.8 Using with a range of lines 460 | 461 | If a range is passed to the :Gitv command it will have no effect in browser 462 | mode. In file mode however, only commits that affect at least one line in the 463 | range will be displayed. This is useful, for example, to view the commit 464 | history for a function and all of the changes made to it. 465 | 466 | If a range of less than two lines is used then gitv opens in regular file 467 | mode. 468 | 469 | Selecting a commit by pressing on it will show the file as it was in that 470 | commit with all lines not in the range folded away. This allows you to quickly 471 | and easily move from commit to commit viewing the evolution of the range of 472 | lines. Pressing will show you the commit details so that you can see 473 | any other changes it may have made. (NOTE: many terminal emulators do not 474 | support ). 475 | 476 | The range that Git looks at is specified by the first and last line in the 477 | range passed to :Gitv. These lines are escaped appropriately and passed to Git 478 | to use as a regular expression. If you wish to modify either regex delimiter 479 | this can be done by pressing on the appropriate line near the top of the 480 | file mode buffer. Vim will prompt you to modify the regex. If you press enter 481 | without modification, nothing happens. If you modify the regex (Git supports 482 | POSIX regular expressions) gitv will automatically update. 483 | 484 | You may also pass arguments to the :Gitv! command when using a range. This 485 | filters the commits that are to be compared for changes in the range. For 486 | example you could pass the name of a branch to only look at changes in commits 487 | on that branch. 488 | 489 | Note: This feature requires the bash shell to be installed on your system and 490 | in your path. This should already be the case for the majority of users. 491 | 492 | Note: Using a range searches back through the commit history looking at each 493 | section of lines for where changes occurred. It will only look at the last 494 | g:Gitv_CommitStep number, if you don't get any matches you could try pressing 495 | on the 'load more' line. This command may be slow if you choose a large 496 | range and have a large g:Gitv_CommitStep. 497 | 498 | 3.9 Bisecting *gitv-bisecting* 499 | 500 | gitv provides several bisecting instructions that essentially send it into a 501 | separate mode. It is intended that this mode can be enabled/disabled at will 502 | so as to use gitv as normal if desired. This also allows entering into the 503 | bisecting mode at any time. Since gitv is a visual branch manager, bisecting 504 | mode offers its most useful capabilities via visual selection. 505 | 506 | When gitv is in bisecting mode, only the range of commits being bisected is 507 | shown. This is why capabilities are given to disable bisecting mode and 508 | explore branches as normal while bisecting. This is a wrapper around the 509 | `git log --bisect` command. 510 | 511 | When the repository is not in bisecting mode, pressing `gbs` or the keybinding 512 | to `bisectStart` will start the bisecting process. It is recommended that for 513 | small ranges of commits, you start the bisecting process using visual mode. 514 | 515 | When the repository is in visual mode and gitv is not, pressing `gbs` will 516 | only put gitv into bisecting mode. 517 | 518 | However bisecting mode has been enabled, it can always be disabled again using 519 | `gbs` and enabled again in the same way. It does not keeep stateful 520 | information about the bisecting process. 521 | 522 | You should be able to see which commits have been marked as good, bad, or to 523 | skip via the output from the `--bisect` command. You should also see the 524 | current commit marked with `HEAD`. If you need to perform operations on the 525 | current head, you can jump to it via `P` key or the keybinding to `head`. 526 | 527 | `gbr` will call `git bisect --reset` and stop the bisecting process, as 528 | opposed to just disabling it. 529 | 530 | 3.10 Rebasing *gitv-rebasing* 531 | 532 | Similar to bisecting, rebasing in gitv provides a separate mode which can be 533 | easily toggled on and off as to jump into the rebasing process at any time. It 534 | also provides useful visual commands. 535 | 536 | There are three ways to start a rebase with gitv if rebasing is not already in 537 | process in the repository. The most simple, non interactive way, is to use 538 | `grr`. This is the same as calling `git rebase (ref)` or 539 | `git rebase (ref1) (ref2)` for a range. 540 | 541 | The rest of the ways to start rebasing are wrappers around the 542 | `git rebase --interactive` command, which allows performing special 543 | instructions on commits. 544 | 545 | The first way to interactively rebase is to call `gbs` when the cursor is on 546 | the desired ref or commit to rebase without doing anything else first. This 547 | opens up the file normally used for entering in `--interactive` instructions. 548 | After you enter in your instructions and save, rebasing does not continue 549 | automatically. This is to allow you to re-edit your instructions via 550 | selecting `Edit rebase todo` in the branch viewer. You must use `grc` or the 551 | keybinding to `rebaseContinue` to confirm you wish to proceed. You may re-edit 552 | instructions at any time during the rebasing procedure. 553 | 554 | In case it is useful to view the branch history structure while editing rebase 555 | instructions, a third method is provided for entering rebase mode. By staging 556 | instructions using the `grR`, `grE`, `grS`, `grF`, `grX`, `grD`, and `grP` 557 | commands or custom bindings, you can skip editing the initial todo file. 558 | However, you must still press `grs` on the ref or commit you wish to begin 559 | rebasing on. Be advised that after you press `grs`, you will not get another 560 | chance to confirm with `grc`. However, you can still edit the todo at any time 561 | during the rebasing process. Staged instructions can be cleared using `grP`. 562 | 563 | If you are already in the process of an interactive rebase and you enter gitv, 564 | `grs` will enable rebasing mode instead of starting it. 565 | 566 | Once rebasing mode is enabled, `grc` may do one of several things. If the 567 | rebasing instruction would normally trigger a commit, the fugitive commit 568 | window will automatically open. If you are not sure why the window has opened, 569 | please view the comments in the commit window. If the window is closed, it can 570 | always be reopened with `Gcommit`. gitv may also do nothing. This indicates 571 | that you have a chance to edit the commit. 572 | 573 | You can continue with the rebase process at any interactive instruction with 574 | `grc` whenever you are ready. When the rebasing ends, gitv will automatically 575 | return to normal mode. 576 | 577 | You may cancel the rebasing process at any time with `gra` or the 578 | `git rebase --abort` command. 579 | 580 | If you git into an undesirable state after rebasing, you may use `git reflog` 581 | to recover. 582 | 583 | ============================================================================== 584 | 4. Configuration Options *gitv-config-options* 585 | 586 | You can set the following options in your .vimrc to override the values used 587 | by |gitv|. The defaults are shown. 588 | 589 | 4.1 Commit Step 590 | 591 | This is the number of commits to show each time. When pressing on 592 | "-- Load More --", the number of extra commits loaded is g:Gitv_CommitStep. 593 | The default is a screen's worth of lines. This should be set to an integer 594 | number. Setting this to a value _really_ high will load the entire repo in one 595 | go. 596 | > 597 | g:Gitv_CommitStep = &lines 598 | < 599 | 600 | 4.2 Open Horizontal 601 | 602 | This is the default layout to use in browser mode. If set to 0 then browser 603 | mode will open in a vertical split. If set to 1 then browser mode will open in 604 | a horizontal split. If set to 'auto' then browser mode will open in a vertical 605 | split unless the content fits better in a horizontal split, in which case it 606 | will open horizontally. 607 | 608 | The commit browser width and height is automatically sized to best fit the 609 | content in all modes and settings. 610 | > 611 | g:Gitv_OpenHorizontal = 0 612 | < 613 | 614 | 4.3 Git Executable 615 | 616 | Gitv uses the same executable as fugitive. 617 | 618 | 4.4 Wipe All on Close 619 | 620 | This option should be set to either 0 (to disable) or 1 (to enable). If set to 621 | 1 then on closing the browser mode by using the q key all buffers displayed in 622 | a window in the tab will be wiped before the tab is closed. This option allows 623 | you to limit the number of fugitive buffers that accumulate in the use of gitv. 624 | 625 | NOTE: This will not wipe any buffer with unsaved content. It will however 626 | mercilessly wipe all buffers in the tab regardless of the file they hold. 627 | > 628 | g:Gitv_WipeAllOnClose = 0 629 | < 630 | 631 | 4.5 Wrap Lines 632 | 633 | If set to 1 then line wrapping is enabled. This is useful if you have 634 | occasional very long commit messages. 635 | > 636 | g:Gitv_WrapLines = 0 637 | < 638 | 639 | 4.6 Truncate Commit Subjects 640 | 641 | If set to 1 then commit subject truncation is enabled. This will truncate 642 | commit subjects, where necessary, so that the whole line will fit in one 643 | screen width. If this is set, then automatically switching to a horizontal 644 | layout will no longer work as commits will be truncated to always fit in a 645 | vertical split. NOTE: It is possible that this can truncate any refs pointing 646 | at a commit. In this situation it will not be possible to check out any of 647 | these refs. This is due to gitv being unable to recognise that they are refs. 648 | > 649 | g:Gitv_TruncateCommitSubjects = 0 650 | < 651 | 652 | 4.7 Open Preview On Launch 653 | 654 | If set to 1 then the preview window is displayed when launching gitv in 655 | browser mode. If set to 0 then no preview window is displayed until a commit 656 | is opened. This option has no effect in file mode. 657 | > 658 | g:Gitv_OpenPreviewOnLaunch = 1 659 | < 660 | 661 | 4.8 Prompt to Delete Merge Branch 662 | 663 | If set to 1 then, when performing a merge using gitv, you will be prompted if 664 | you wish to delete the topic branch. If you often merge a branch into another 665 | and rarely wish to delete branches, set this to 0. The default is 0. 666 | > 667 | let g:Gitv_PromptToDeleteMergeBranch = 0 668 | < 669 | 4.9 Custom merge arguments 670 | 671 | The purpose of this option is to customize the merging behaviour. To use the 672 | default merge behaviour, unset this variable. To not confirm for fast forward, 673 | set this to an empty string. By default, this is undefined. 674 | > 675 | unset! g:Gitv_MergeArgs 676 | < 677 | 678 | Example: if you wish to use a different merge strategy, you can use this. 679 | > 680 | fu! MergeOurs() 681 | let g:Gitv_MergeArgs = '--strategy=ours' 682 | " Escape put in because of :normal quirks with space 683 | execute "normal \ m" 684 | " Put whatever your default is here 685 | unlet! g:Gitv_MergeArgs 686 | enfu 687 | autocmd FileType gitv nno Mo :call MergeOurs() 688 | < 689 | 4.10 Do not map the control key 690 | 691 | If this is set then no mappings that use the control key will be created. The 692 | control key mappings are all alternatives and so can be disabled without 693 | losing functionality. The default is to create the mappings. 694 | > 695 | let g:Gitv_DoNotMapCtrlKey = 1 696 | > 697 | 698 | 4.11 Do not log bisect actions 699 | 700 | If this is set then no bisect mappings will log their actions. This will 701 | prevent the need to press enter after every bisect action, but could cause 702 | confusion. Errors will still be shown. 703 | > 704 | let g:Gitv_QuietBisect = 1 705 | 706 | 4.12 Custom key mappings *gitv-custom-browser-mappings* 707 | 708 | Set custom key mappings which will overwrite the defaults. The following would 709 | result in bindings equivalent to the defaults. 710 | > 711 | let g:Gitv_CustomMappings = { 712 | \'quit': 'q', 713 | \'update': 'u', 714 | \'toggleAll': 'a', 715 | \'nextBranch': 'x', 716 | \'prevBranch': 'X', 717 | \'nextRef': 'r', 718 | \'prevRef': 'R', 719 | \'editCommit': [ 720 | \'', { 'keys': '', 'prefix', '' } 721 | \], 722 | \'prevCommit': 'J', 723 | \'nextCommit': 'K', 724 | \'splitCommit': 'o', 725 | \'vertSplitCommit': 's', 726 | \'tabeCommit': 'O', 727 | \'editCommitDetails': 'i', 728 | \'diff': 'D', 729 | \'vdiff': 'D', 730 | \'stat': 'S', 731 | \'vstat': 'S', 732 | \'checkout': 'co', 733 | \'merge': 'm', 734 | \'vmerge': 'm', 735 | \'cherryPick': 'cp', 736 | \'vcherryPick': 'cp', 737 | \'reset': 'rb', 738 | \'vreset': 'rb', 739 | \'resetSoft': 'rbs', 740 | \'vresetSoft': 'rbs', 741 | \'resetHard': 'rbh', 742 | \'vresetHard': 'rbh', 743 | \'revert': 'rev', 744 | \'vrevert': 'rev', 745 | \'delete': 'd', 746 | \'vdelete': 'd', 747 | \'rebase': 'grr', 748 | \'vrebase': 'grr', 749 | \'rebasePick': 'grP', 750 | \'vrebasePick': 'grP', 751 | \'rebaseReword': 'grR', 752 | \'vrebaseReword': 'grR', 753 | \'rebaseMarkEdit': 'grF', 754 | \'vrebaseMarkEdit': 'grF', 755 | \'rebaseSquash': 'grF', 756 | \'vrebaseSquash': 'grF', 757 | \'rebaseFixup': 'grF', 758 | \'vrebaseFixup': 'grF', 759 | \'rebaseExec': 'grF', 760 | \'vrebaseExec': 'grF', 761 | \'rebaseDrop': 'grF', 762 | \'vrebaseDrop': 'grF', 763 | \'rebaseAbort': 'gra', 764 | \'rebaseToggle': 'grs', 765 | \'vrebaseToggle': 'grs', 766 | \'rebaseSkip': 'grn', 767 | \'rebaseContinue': 'grc', 768 | \'rebaseEdit': 'gre', 769 | \'bisectStart': 'gbs', 770 | \'vbisectStart': 'gbs', 771 | \'bisectGood': 'gbg', 772 | \'vbisectGood': 'gbg', 773 | \'bisectBad': 'gbb', 774 | \'vbisectBad': 'gbb', 775 | \'bisectSkip': 'gbn', 776 | \'vbisectSkip': 'gbn', 777 | \'bisectReset': 'gbr', 778 | \'bisectLog': 'gbl', 779 | \'bisectReplay': 'gbp', 780 | \'head': 'P', 781 | \'parent': 'p', 782 | \'toggleWindow': 'gw', 783 | \'git': 'git', 784 | \'yank': 'yc' 785 | \} 786 | < 787 | 788 | Bindings that use the ctrl modifier are kept internal and disabled when 789 | g:Gitv_DoNotMapCtrlKey is set. 790 | 791 | 4.13 Change The Behaviour Of The Preview Window 792 | 793 | Set custom options for the preview window. These options are passed directly 794 | to |git-show|. 795 | > 796 | let g:Gitv_PreviewOptions = '--name-only' 797 | < 798 | 799 | ============================================================================== 800 | 5. Changelog *gitv-changelog* 801 | 802 | 1.1 Nodes displaying local changes are inserted above HEAD ref. 803 | Added range feature. 804 | Merging branches is possible in gitv. 805 | strwidth bugfix (thanks Adam Reeve). 806 | Prevent bdelete errors (thanks pydave). 807 | 808 | 1.0 First release. I hope you enjoy gitv! 809 | 810 | ============================================================================== 811 | 6. Misc *gitv-misc* 812 | 813 | 6.1 Tips and tricks 814 | 815 | I use the following mappings to make working with |gitv| easier. 816 | > 817 | nmap gv :Gitv --all 818 | nmap gV :Gitv! --all 819 | vmap gV :Gitv! --all 820 | < 821 | The vmap equivalent for file mode is to make dealing with ranges easier. It 822 | allows you to visually select a range of lines to view the commit history for 823 | that section of the file. 824 | 825 | I highly recommend adding to your vimrc: 826 | > 827 | set lazyredraw 828 | < 829 | 830 | This stops Vim from redrawing the screen during complex operations and results 831 | in much smoother looking plugins. 832 | 833 | 6.2 API 834 | 835 | You can grab the current line's hash programatically with 836 | > 837 | gitv#util#line#sha(line('.')) 838 | < 839 | You can grab the ref with 840 | > 841 | gitv#util#line#refs('.') 842 | < 843 | 844 | The function: 'Gitv_OpenGitCommand(command, windowCmd)' is provided to allow 845 | the more advanced user to create their own commands. This function will 846 | execute the git command provided in the new window created using windowCmd. 847 | 848 | By using this function you get for free: the buffer set up for read only git 849 | output, including syntax highlighting and many other tailored options. You 850 | also get mappings for 'u' to update the output and 'q' to easily close the 851 | window. 852 | 853 | Here is an example of getting diff output both cached and not, in a vertical 854 | and horizontal split respectively. 855 | > 856 | call Gitv_OpenGitCommand("diff --no-color --cached", 'vnew') 857 | call Gitv_OpenGitCommand("diff --no-color", 'new') 858 | 859 | Use this command at your own risk. gitv currently makes certain assumptions 860 | about window layout, and custom layouts could break certain functions. 861 | 862 | 6.3 Bugs, issues, features and contributing. 863 | 864 | Bugs, suggestions, pull requests and patches are all very welcome. If you find 865 | issues with |gitv| please add them to the issues page on the github project. 866 | Anything else, feel free to email me at r.l.bongers@gmail.com 867 | 868 | Please run `Gitv_GetDebugInfo()` and post your clipboard output for bugs. 869 | 870 | vim:tw=78:ts=8:ft=help:norl: 871 | -------------------------------------------------------------------------------- /doc/tags: -------------------------------------------------------------------------------- 1 | gitv gitv.txt /*gitv* 2 | gitv-author gitv.txt /*gitv-author* 3 | gitv-bisecting gitv.txt /*gitv-bisecting* 4 | gitv-browser-mappings gitv.txt /*gitv-browser-mappings* 5 | gitv-changelog gitv.txt /*gitv-changelog* 6 | gitv-config-options gitv.txt /*gitv-config-options* 7 | gitv-custom-browser-mappings gitv.txt /*gitv-custom-browser-mappings* 8 | gitv-installation gitv.txt /*gitv-installation* 9 | gitv-introduction gitv.txt /*gitv-introduction* 10 | gitv-misc gitv.txt /*gitv-misc* 11 | gitv-rebasing gitv.txt /*gitv-rebasing* 12 | gitv-usage gitv.txt /*gitv-usage* 13 | -------------------------------------------------------------------------------- /ftplugin/gitv.vim: -------------------------------------------------------------------------------- 1 | "AUTHOR: Greg Sexton 2 | "MAINTAINER: Roger Bongers 3 | "WEBSITE: http://www.gregsexton.org/portfolio/gitv/ 4 | "LICENSE: Same terms as Vim itself (see :help license). 5 | "NOTES: Much of the credit for gitv goes to Tim Pope and the fugitive plugin 6 | " where this plugin either uses functionality directly or was inspired heavily. 7 | 8 | "enabling these next lines breaks settings when reloading the buffer 9 | "if exists("b:did_ftplugin") | finish | endif 10 | "let b:did_ftplugin = 1 11 | 12 | let s:cpo_save = &cpo 13 | set cpo&vim 14 | 15 | setlocal fdm=expr 16 | 17 | fu! Foldlevelforbranch() "{{{ 18 | let line = getline(v:lnum) 19 | 20 | if line == "-- Load More --" 21 | return 0 22 | endif 23 | if line =~ "^-- \\[.*\\] --$" 24 | return 0 25 | endif 26 | 27 | let line = substitute(line, "\\s", "", "g") 28 | let level = match(line, "*") + 1 29 | return level == 0 ? -1 : level 30 | endfu "}}} 31 | setlocal foldexpr=Foldlevelforbranch() 32 | 33 | fu! BranchFoldText() "{{{ 34 | "get first non-blank line 35 | let fs = v:foldstart 36 | while getline(fs) =~ '^\s*$' | let fs = nextnonblank(fs + 1) 37 | endwhile 38 | if fs > v:foldend 39 | let line = getline(v:foldstart) 40 | else 41 | let line = getline(fs) 42 | endif 43 | return line 44 | endf "}}} 45 | setlocal foldtext=BranchFoldText() 46 | setlocal foldlevel=99 47 | setlocal nosplitbelow 48 | setlocal nosplitright 49 | 50 | let &cpo = s:cpo_save 51 | unlet s:cpo_save 52 | -------------------------------------------------------------------------------- /img/gitv-bisecting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gregsexton/gitv/a73599c34202709eaa7da78f4fe32b97c6ef83f8/img/gitv-bisecting.png -------------------------------------------------------------------------------- /img/gitv-commit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gregsexton/gitv/a73599c34202709eaa7da78f4fe32b97c6ef83f8/img/gitv-commit.png -------------------------------------------------------------------------------- /img/gitv-diffsplit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gregsexton/gitv/a73599c34202709eaa7da78f4fe32b97c6ef83f8/img/gitv-diffsplit.png -------------------------------------------------------------------------------- /img/gitv-diffstat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gregsexton/gitv/a73599c34202709eaa7da78f4fe32b97c6ef83f8/img/gitv-diffstat.png -------------------------------------------------------------------------------- /img/gitv-rebasing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gregsexton/gitv/a73599c34202709eaa7da78f4fe32b97c6ef83f8/img/gitv-rebasing.png -------------------------------------------------------------------------------- /plugin/gitv.vim: -------------------------------------------------------------------------------- 1 | "AUTHOR: Greg Sexton 2 | "MAINTAINER: Roger Bongers 3 | "WEBSITE: http://www.gregsexton.org/portfolio/gitv/ 4 | "LICENSE: Same terms as Vim itself (see :help license). 5 | "NOTES: Much of the credit for gitv goes to Tim Pope and the fugitive plugin 6 | " where this plugin either uses functionality directly or was inspired heavily. 7 | 8 | if exists("g:loaded_gitv") || v:version < 700 9 | finish 10 | endif 11 | let g:loaded_gitv = 1 12 | 13 | let s:savecpo = &cpo 14 | set cpo&vim 15 | 16 | "configurable options: 17 | "g:Gitv_CommitStep - int 18 | "g:Gitv_OpenHorizontal - {0,1,'AUTO'} 19 | "g:Gitv_WipeAllOnClose - int 20 | "g:Gitv_WrapLines - {0,1} 21 | "g:Gitv_TruncateCommitSubjects - {0,1} 22 | "g:Gitv_OpenPreviewOnLaunch - {0,1} 23 | "g:Gitv_PromptToDeleteMergeBranch - {0,1} 24 | 25 | if !exists("g:Gitv_CommitStep") 26 | let g:Gitv_CommitStep = &lines 27 | endif 28 | 29 | if !exists('g:Gitv_WipeAllOnClose') 30 | let g:Gitv_WipeAllOnClose = 0 "default for safety 31 | endif 32 | 33 | if !exists('g:Gitv_WrapLines') 34 | let g:Gitv_WrapLines = 0 35 | endif 36 | 37 | if !exists('g:Gitv_TruncateCommitSubjects') 38 | let g:Gitv_TruncateCommitSubjects = 0 39 | endif 40 | 41 | if !exists('g:Gitv_OpenPreviewOnLaunch') 42 | let g:Gitv_OpenPreviewOnLaunch = 1 43 | endif 44 | 45 | if !exists('g:Gitv_PromptToDeleteMergeBranch') 46 | let g:Gitv_PromptToDeleteMergeBranch = 0 47 | endif 48 | 49 | if !exists('g:Gitv_CustomMappings') 50 | let g:Gitv_CustomMappings = {} 51 | endif 52 | 53 | if !exists('g:Gitv_DisableShellEscape') 54 | let g:Gitv_DisableShellEscape = 0 55 | endif 56 | 57 | if !exists('g:Gitv_DoNotMapCtrlKey') 58 | let g:Gitv_DoNotMapCtrlKey = 0 59 | endif 60 | 61 | if !exists('g:Gitv_PreviewOptions') 62 | let g:Gitv_PreviewOptions = '' 63 | endif 64 | 65 | if !exists('g:Gitv_QuietBisect') 66 | let g:Gitv_QuietBisect = 0 67 | endif 68 | 69 | " create a temporary file for working 70 | let s:workingFile = tempname() 71 | 72 | "this counts up each time gitv is opened to ensure a unique file name 73 | let g:Gitv_InstanceCounter = 0 74 | 75 | let s:localUncommitedMsg = 'Local uncommitted changes, not checked in to index.' 76 | let s:localCommitedMsg = 'Local changes checked in to index but not committed.' 77 | 78 | let s:pendingRebaseMsg = 'View pending rebase instructions' 79 | let s:rebaseMsg = 'Edit rebase todo' 80 | 81 | command! -nargs=* -range -bang -complete=custom,s:CompleteGitv Gitv call s:OpenGitv(s:EscapeGitvArgs(), 0, , ) 82 | cabbrev gitv =(getcmdtype()==':' && getcmdpos()==1 ? 'Gitv' : 'gitv') 83 | 84 | "Public API:"{{{ 85 | fu! Gitv_OpenGitCommand(command, windowCmd, ...) "{{{ 86 | "returns 1 if command succeeded with output 87 | "optional arg is a flag, if present runs command verbatim 88 | 89 | "this function is not limited to script scope as is useful for running other commands. 90 | "e.g call Gitv_OpenGitCommand("diff --no-color", 'vnew') is useful for getting an overall git diff. 91 | 92 | let [result, finalCmd] = s:RunGitCommand(a:command, a:0) 93 | 94 | if type(result) == type(0) 95 | return 0 96 | endif 97 | if type(result) == type("") && result == "" 98 | echom "No output." 99 | return 0 100 | else 101 | if a:windowCmd == '' 102 | silent setlocal modifiable 103 | silent setlocal noreadonly 104 | 1,$ d _ 105 | else 106 | let goBackTo = winnr() 107 | let dir = fugitive#repo().dir() 108 | let workingDir = fugitive#repo().tree() 109 | let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' 110 | let bufferDir = getcwd() 111 | let tempSplitBelow = &splitbelow 112 | let tempSplitRight = &splitright 113 | try 114 | set nosplitbelow 115 | set nosplitright 116 | execute cd.'`=workingDir`' 117 | exec a:windowCmd 118 | let newWindow = winnr() 119 | finally 120 | exec goBackTo . 'wincmd w' 121 | execute cd.'`=bufferDir`' 122 | if exists('newWindow') 123 | exec newWindow . 'wincmd w' 124 | endif 125 | exec 'set '. (tempSplitBelow ? '' : 'no') . 'splitbelow' 126 | exec 'set '. (tempSplitRight ? '' : 'no') . 'splitright' 127 | endtry 128 | endif 129 | if !(&modifiable) 130 | return 0 131 | endif 132 | let b:Git_Command = finalCmd 133 | silent setlocal ft=git 134 | silent setlocal buftype=nofile 135 | silent setlocal nobuflisted 136 | silent setlocal noswapfile 137 | silent setlocal bufhidden=wipe 138 | silent setlocal nonumber 139 | if exists('+relativenumber') 140 | silent setlocal norelativenumber 141 | endif 142 | if g:Gitv_WrapLines 143 | silent setlocal wrap 144 | else 145 | silent setlocal nowrap 146 | endif 147 | silent setlocal fdm=syntax 148 | nnoremap q :q! 149 | nnoremap u :if exists('b:Git_Command')call Gitv_OpenGitCommand(b:Git_Command, '', 1)endif 150 | call append(0, split(result, '\n')) "system converts eols to \n regardless of os. 151 | silent setlocal nomodifiable 152 | silent setlocal readonly 153 | 1 154 | return 1 155 | endif 156 | endf "}}} 157 | fu! Gitv_GetDebugInfo() "{{{ 158 | if has('clipboard') 159 | redir @+ 160 | endif 161 | 162 | echo '## Gitv debug output' 163 | echo '' 164 | 165 | echo '### Basic information' 166 | echo '' 167 | echo '```' 168 | echo 'Version: 1.3.1.22' 169 | echo '' 170 | echo strftime('%c') 171 | echo '```' 172 | echo '' 173 | 174 | echo '### Settings' 175 | echo '' 176 | echo '```' 177 | set 178 | echo '```' 179 | echo '' 180 | 181 | echo '### Vim version' 182 | echo '' 183 | echo '```' 184 | version 185 | echo '```' 186 | echo '' 187 | 188 | echo '### Git output' 189 | echo '' 190 | echo '```' 191 | 192 | try 193 | echo '$ git --version' 194 | echo s:RunCommandRelativeToGitRepo('git --version')[0] 195 | echo '$ git status' 196 | echo s:RunCommandRelativeToGitRepo('git status')[0] 197 | echo '$ git remote --verbose' 198 | echo s:RunCommandRelativeToGitRepo('git remote --verbose')[0] 199 | catch 200 | echo 'Gitv_GetDebugInfo exception:' 201 | echo v:exception 202 | endtry 203 | 204 | echo '```' 205 | echo '' 206 | 207 | echo '### Fugitive version' 208 | echo '' 209 | echo '```' 210 | 211 | try 212 | let filename = s:GetFugitiveInfo()[1] 213 | " Make sure redirection is still set up 214 | if has('clipboard') 215 | redir @+ 216 | endif 217 | for line in readfile(filename) 218 | if line =~ 'Version:' || line =~ 'GetLatestVimScripts:' 219 | echo line 220 | endif 221 | endfor 222 | catch 223 | echo 'Gitv_GetDebugInfo exception:' 224 | echo v:exception 225 | endtry 226 | 227 | echo '```' 228 | echo '' 229 | 230 | redir END 231 | 232 | if has('clipboard') 233 | echo 'Debug information has been copied to your clipboard. Please attach this information to your submitted issue.' 234 | else 235 | echo 'Please copy the contents above and add them to your submitted issue.' 236 | endif 237 | endf "}}} }}} 238 | "General Git Functions: "{{{ 239 | fu! s:RunGitCommand(command, verbatim) "{{{ 240 | "if verbatim returns result of system command, else 241 | "switches to the buffer repository before running the command and switches back after. 242 | if !a:verbatim 243 | "switches to the buffer repository before running the command and switches back after. 244 | let cmd = fugitive#repo().git_command() .' '. a:command 245 | let [result, finalCmd] = s:RunCommandRelativeToGitRepo(cmd) 246 | else 247 | let result = system(a:command) 248 | let finalCmd = a:command 249 | endif 250 | return [result, finalCmd] 251 | endfu "}}} 252 | fu! s:RunCommandRelativeToGitRepo(command) abort "{{{ 253 | " Runs the command verbatim but first changing to the root git dir. 254 | " Input commands should include a --git-dir argument to git (see 255 | " fugitive#repo().git_command()). 256 | let workingDir = fugitive#repo().tree() 257 | 258 | let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' 259 | let bufferDir = getcwd() 260 | try 261 | execute cd.'`=workingDir`' 262 | let result = system(a:command) 263 | finally 264 | execute cd.'`=bufferDir`' 265 | endtry 266 | return [result, a:command] 267 | endfu "}}} }}} 268 | "Open And Update Gitv:"{{{ 269 | fu! s:SanitizeReservedArgs(extraArgs) "{{{ 270 | let sanitizedArgs = a:extraArgs 271 | if sanitizedArgs[0] =~ "[\"']" && sanitizedArgs[:-1] =~ "[\"']" 272 | let sanitizedArgs = sanitizedArgs[1:-2] 273 | endif 274 | " store bisect 275 | if match(sanitizedArgs, ' --bisect') >= 0 276 | let sanitizedArgs = substitute(sanitizedArgs, ' --bisect', '', 'g') 277 | if s:BisectHasStarted() 278 | let b:Gitv_Bisecting = 1 279 | endif 280 | endif 281 | " store files 282 | let selectedFiles = [] 283 | let splitArgs = split(sanitizedArgs, ' ') 284 | let index = len(splitArgs) 285 | let root = fugitive#repo().tree().'/' 286 | while index 287 | let index -= 1 288 | let path = splitArgs[index] 289 | if !empty(globpath('.', path)) 290 | " transform the path relative to the git directory 291 | let path = fnamemodify(path, ':p') 292 | let splitPath = split(path, root) 293 | if splitPath[0] == path 294 | echoerr "Not in git repo:" path 295 | break 296 | endif 297 | " join the relroot back in case we split more than one off 298 | let path = join(splitPath, root) 299 | let selectedFiles += [path] 300 | else 301 | break 302 | endif 303 | endwhile 304 | let selectedFiles = sort(selectedFiles) 305 | return [join(splitArgs[0:-len(selectedFiles) - 1], ' '), join(selectedFiles, ' ')] 306 | endfu "}}} 307 | fu! s:ReapplyReservedArgs(extraArgs) "{{{ 308 | let options = a:extraArgs[0] 309 | if s:RebaseIsEnabled() 310 | return [options, ''] 311 | endif 312 | if s:BisectIsEnabled() 313 | if !s:IsBisectingCurrentFiles() 314 | echoerr "Not bisecting specified files." 315 | let b:Gitv_Bisecting = 0 316 | else 317 | let options .= " --bisect" 318 | let options = s:FilterArgs(options, ['--all', '--first-parent']) 319 | endif 320 | endif 321 | return [options, a:extraArgs[1]] 322 | endfu "}}} 323 | fu! s:EscapeGitvArgs(extraArgs) "{{{ 324 | " TODO: test whether shellescape is really needed on windows. 325 | if g:Gitv_DisableShellEscape == 0 326 | return shellescape(a:extraArgs) 327 | else 328 | return a:extraArgs 329 | endif 330 | endfu "}}} 331 | fu! s:OpenGitv(extraArgs, fileMode, rangeStart, rangeEnd) "{{{ 332 | if !exists('s:fugitiveSid') 333 | let s:fugitiveSid = s:GetFugitiveSid() 334 | endif 335 | let sanitizedArgs = s:SanitizeReservedArgs(a:extraArgs) 336 | let g:Gitv_InstanceCounter += 1 337 | if !s:IsCompatible() "this outputs specific errors 338 | return 339 | endif 340 | try 341 | if a:fileMode 342 | call s:OpenFileMode(sanitizedArgs, a:rangeStart, a:rangeEnd) 343 | else 344 | call s:OpenBrowserMode(sanitizedArgs) 345 | endif 346 | catch /not a git repository/ 347 | echom 'Not a git repository.' 348 | return 349 | endtry 350 | endf "}}} 351 | fu! s:IsCompatible() "{{{ 352 | if !exists('g:loaded_fugitive') 353 | echoerr "gitv requires the fugitive plugin to be installed." 354 | endif 355 | return exists('g:loaded_fugitive') 356 | endfu "}}} 357 | fu! s:CompleteGitv(arglead, cmdline, pos) "{{{ 358 | if match( a:arglead, '^-' ) >= 0 359 | return "\n--after\n--all-match\n--ancestry-path\n--author-date-order" 360 | \ . "\n--author=\n--author=\n--before=\n--bisect\n--boundary" 361 | \ . "\n--branches\n--cherry-mark\n--cherry-pick\n--committer=" 362 | \ . "\n--date-order\n--dense\n--exclude=\n--first-parent" 363 | \ . "\n--fixed-strings\n--follow\n--glob\n--grep-reflog" 364 | \ . "\n--grep=\n--max-age=\n--max-count=\n--merges\n--no-merges" 365 | \ . "\n--min-age=\n--min-parents=\n--not\n--pickaxe-all" 366 | \ . "\n--pickaxe-regex\n--regexp-ignore-case\n--remotes" 367 | \ . "\n--remove-empty\n--since=\n--skip\n--tags\n--topo-order" 368 | \ . "\n--until=\n--use-mailmap" 369 | else 370 | if match(a:arglead, '\/$') >= 0 371 | let paths = "\n".globpath(a:arglead, '*') 372 | else 373 | let paths = "\n".glob(a:arglead.'*') 374 | endif 375 | 376 | let refs = fugitive#repo().git_chomp('rev-parse', '--symbolic', '--branches', '--tags', '--remotes') 377 | let refs .= "\nHEAD\nFETCH_HEAD\nORIG_HEAD" 378 | 379 | " Complete ref names preceded by a ^ or anything followed by 2-3 dots 380 | let prefix = matchstr( a:arglead, '\v^(\^|.*\.\.\.?)' ) 381 | if prefix == '' 382 | return refs.paths 383 | else 384 | return substitute( refs, "\\v(^|\n)\\zs", prefix, 'g' ).paths 385 | endif 386 | endf "}}} 387 | fu! s:OpenBrowserMode(extraArgs) "{{{ 388 | "this throws an exception if not a git repo which is caught immediately 389 | silent Gtabedit HEAD: 390 | 391 | if s:IsHorizontal() 392 | let direction = 'new gitv'.'-'.g:Gitv_InstanceCounter 393 | else 394 | let direction = 'vnew gitv'.'-'.g:Gitv_InstanceCounter 395 | endif 396 | if !s:LoadGitv(direction, 0, g:Gitv_CommitStep, a:extraArgs, '', []) 397 | return 0 398 | endif 399 | call s:SetupBufferCommands(0) 400 | let b:Gitv_RebaseInstructions = {} 401 | "open the first commit 402 | if g:Gitv_OpenPreviewOnLaunch 403 | silent call s:OpenGitvCommit("Gedit", 0) 404 | else 405 | call s:MoveIntoPreviewAndExecute('bdelete', 0) 406 | endif 407 | endf "}}} 408 | fu! s:OpenFileMode(extraArgs, rangeStart, rangeEnd) "{{{ 409 | let relPath = fugitive#Path(@%, a:0 ? a:1 : '') 410 | pclose! 411 | let range = a:rangeStart != a:rangeEnd ? s:GetRegexRange(a:rangeStart, a:rangeEnd) : [] 412 | if !s:LoadGitv(&previewheight . "new gitv".'-'.g:Gitv_InstanceCounter, 0, g:Gitv_CommitStep, a:extraArgs, relPath, range) 413 | return 0 414 | endif 415 | set previewwindow 416 | set winfixheight 417 | let b:Gitv_FileMode = 1 418 | let b:Gitv_FileModeRelPath = relPath 419 | let b:Gitv_FileModeRange = range 420 | call s:SetupBufferCommands(1) 421 | endf "}}} 422 | fu! s:LoadGitv(direction, reload, commitCount, extraArgs, filePath, range) "{{{ 423 | call s:RebaseUpdate() 424 | call s:BisectUpdate() 425 | 426 | if a:reload 427 | let jumpTo = line('.') "this is for repositioning the cursor after reload 428 | endif 429 | 430 | "precondition: a:range should be of the form [a, b] or [] 431 | " where a,b are integers && a 0 573 | let extraArgs = s:ReapplyReservedArgs(['', '']) 574 | let git = fugitive#repo().git_command() 575 | let cmd = 'for hash in ' . join(a:hashes, " ") . '; ' 576 | let cmd .= "do " 577 | let cmd .= git.' log' 578 | let cmd .= extraArgs[0] 579 | let cmd .=' --no-color --decorate=full --pretty=format:"%d__START__ %s__SEP__%ar__SEP__%an__SEP__[%h]%n" --graph -1 ${hash}; ' 580 | let cmd .= 'done' 581 | let finalCmd = "bash -c " . shellescape(cmd) 582 | 583 | let [result, cmd] = s:RunCommandRelativeToGitRepo(finalCmd) 584 | return split(result, '\n') 585 | else 586 | return "" 587 | endif 588 | endfu "}}} 589 | fu! s:GetRegexRange(rangeStart, rangeEnd) "{{{ 590 | let rangeS = getline(a:rangeStart) 591 | let rangeS = escape(rangeS, '.^$*\/[]') 592 | let rangeS = matchstr(rangeS, '\v^\s*\zs.{-}\ze\s*$') "trim whitespace 593 | let rangeE = getline(a:rangeEnd) 594 | let rangeE = escape(rangeE, '.^$*\/[]') 595 | let rangeE = matchstr(rangeE, '\v^\s*\zs.{-}\ze\s*$') "trim whitespace 596 | let rangeS = rangeS =~ '^\s*$' ? '^[:blank:]*$' : rangeS 597 | let rangeE = rangeE =~ '^\s*$' ? '^[:blank:]*$' : rangeE 598 | return ['/'.rangeS.'/', '/'.rangeE.'/'] 599 | endfu "}}} }}} 600 | fu! s:SetupBuffer(commitCount, extraArgs, filePath, range) "{{{ 601 | silent set filetype=gitv 602 | let b:Gitv_CommitCount = a:commitCount 603 | let b:Gitv_ExtraArgs = a:extraArgs 604 | silent setlocal modifiable 605 | silent setlocal noreadonly 606 | silent %s/refs\/tags\//t:/ge 607 | silent %s/refs\/remotes\//r:/ge 608 | silent %s/refs\/heads\///ge 609 | call s:AddLoadMore() 610 | call s:AddLocalNodes(a:filePath) 611 | call s:AddFileModeSpecific(a:filePath, a:range, a:commitCount) 612 | call s:AddRebaseMessage(a:filePath) 613 | 614 | " run any autocmds the user may have defined to hook in here 615 | silent doautocmd User GitvSetupBuffer 616 | 617 | silent call s:InsertRebaseInstructions() 618 | silent %call s:Align("__SEP__", a:filePath) 619 | silent %s/\s\+$//e 620 | silent setlocal nomodifiable 621 | silent setlocal readonly 622 | silent setlocal cursorline 623 | endf "}}} 624 | fu! s:InsertRebaseInstructions() "{{{ 625 | if s:RebaseHasInstructions() 626 | for key in keys(b:Gitv_RebaseInstructions) 627 | let search = '__START__\ze.*\['.key.'\]$' 628 | let replace = ' ['.b:Gitv_RebaseInstructions[key].instruction 629 | if exists('b:Gitv_RebaseInstructions[key].cmd') 630 | let replace .= 'x' 631 | endif 632 | let replace .= ']' 633 | exec '%s/'.search.'/'.replace 634 | endfor 635 | endif 636 | %s/__START__// 637 | endf "}}} 638 | fu! s:CleanupRebasePreview() "{{{ 639 | if &syntax == 'gitrebase' 640 | bdelete 641 | endif 642 | endf "}}} 643 | fu! s:AddRebaseMessage(filePath) "{{{ 644 | if a:filePath != '' 645 | return 646 | endif 647 | if s:RebaseHasInstructions() 648 | call append(0, '= '.s:pendingRebaseMsg) 649 | elseif s:RebaseIsEnabled() 650 | call append(0, '= '.s:rebaseMsg) 651 | else 652 | " TODO: this will fail if there's no preview window 653 | call s:MoveIntoPreviewAndExecute('call s:CleanupRebasePreview()', 0) 654 | endif 655 | endf "}}} 656 | fu! s:AddLocalNodes(filePath) "{{{ 657 | let suffix = a:filePath == '' ? '' : ' -- '.a:filePath 658 | let gitCmd = "diff --no-color" . suffix 659 | let [result, cmd] = s:RunGitCommand(gitCmd, 0) 660 | let headLine = search('^\(\(|\|\/\|\\\|\*\)\s\?\)*\s*([^)]*HEAD', 'cnw') 661 | let headLine = headLine == 0 ? 1 : headLine 662 | if result != "" 663 | let line = s:AlignWithRefs(headLine, s:localUncommitedMsg) 664 | call append(headLine-1, substitute(line, '*', '=', '')) 665 | let headLine += 1 666 | endif 667 | let gitCmd = "diff --no-color --cached" . suffix 668 | let [result, cmd] = s:RunGitCommand(gitCmd, 0) 669 | if result != "" 670 | let line = s:AlignWithRefs(headLine, s:localCommitedMsg) 671 | call append(headLine-1, substitute(line, '*', '+', '')) 672 | endif 673 | endfu 674 | fu! s:AlignWithRefs(targetLine, targetStr) 675 | "returns the targetStr prefixed with enough whitespace to align with 676 | "the first asterisk on targetLine 677 | if a:targetLine == 0 678 | return '* '.a:targetStr 679 | endif 680 | let line = getline(a:targetLine) 681 | let idx = stridx(line, '(') 682 | if idx == -1 683 | return '* '.a:targetStr 684 | endif 685 | return strpart(line, 0, idx) . a:targetStr 686 | endfu "}}} 687 | fu! s:AddLoadMore() "{{{ 688 | call append(line('$'), '-- Load More --') 689 | endfu "}}} 690 | fu! s:AddFileModeSpecific(filePath, range, commitCount) "{{{ 691 | if a:filePath != '' 692 | call append(0, '-- ['.a:filePath.'] --') 693 | if a:range != [] 694 | call append(1, '-- Showing (up to '.a:commitCount.') commits affecting lines in the range:') 695 | call append(2, '-- ' . a:range[0]) 696 | call append(3, '-- ' . a:range[1]) 697 | endif 698 | endif 699 | endfu "}}} 700 | "Mapping: "{{{ 701 | fu! s:SetDefaultMappings() "{{{ 702 | " creates the script-scoped dictionary of mapping descriptors 703 | " the dictionary will optionally include ctrl based commands 704 | " sets s:defaultMappings to the dictionary 705 | let s:defaultMappings = {} 706 | 707 | " convenience 708 | let s:defaultMappings.quit = { 709 | \'cmd': ':call CloseGitv()', 'bindings': 'q' 710 | \} 711 | let s:defaultMappings.update = { 712 | \'cmd': ':call LoadGitv("", 1, b:Gitv_CommitCount, b:Gitv_ExtraArgs, GetRelativeFilePath(), GetRange())', 713 | \'bindings': 'u' 714 | \} 715 | let s:defaultMappings.toggleAll = { 716 | \'cmd': ':call LoadGitv("", 0, b:Gitv_CommitCount, ToggleArg(b:Gitv_ExtraArgs, "--all"), GetRelativeFilePath(), GetRange())', 717 | \'bindings': 'a' 718 | \} 719 | 720 | " movement 721 | let s:defaultMappings.nextBranch = { 722 | \'cmd': ':call JumpToBranch(0)', 723 | \'bindings': 'x' 724 | \} 725 | let s:defaultMappings.prevBranch = { 726 | \'cmd': ':call JumpToBranch(1)', 727 | \'bindings': 'X' 728 | \} 729 | let s:defaultMappings.nextRef = { 730 | \'cmd': ':call JumpToRef(0)', 731 | \'bindings': 'r' 732 | \} 733 | let s:defaultMappings.prevRef = { 734 | \'cmd': ':call JumpToRef(1)', 735 | \'bindings': 'R' 736 | \} 737 | let s:defaultMappings.head = { 738 | \'cmd': ':call JumpToHead()', 739 | \'bindings': 'P' 740 | \} 741 | let s:defaultMappings.parent = { 742 | \'cmd': ':call JumpToParent()', 743 | \'bindings': 'p' 744 | \} 745 | let s:defaultMappings.toggleWindow = { 746 | \'cmd': ':call SwitchBetweenWindows()', 747 | \'bindings': 'gw' 748 | \} 749 | 750 | " viewing commits 751 | let s:defaultMappings.editCommit = { 752 | \'cmd': ':call OpenGitvCommit("Gedit", 0)', 753 | \'bindings': [ 754 | \'', { 'keys': '', 'prefix': '' } 755 | \], 756 | \} 757 | " (gitv-*) are fuzzyfinder style keymappings 758 | let s:defaultMappings.splitCommit = { 759 | \'cmd': ':call OpenGitvCommit("Gsplit", 0)', 760 | \'bindings': 'o', 761 | \'permanentBindings': '(gitv-split)' 762 | \} 763 | let s:defaultMappings.tabeCommit = { 764 | \'cmd': ':call OpenGitvCommit("Gtabedit", 0)', 765 | \'bindings': 'O' , 766 | \'permanentBindings': '(gitv-tabedit)' 767 | \} 768 | let s:defaultMappings.vertSplitCommit = { 769 | \'cmd': ':call OpenGitvCommit("Gvsplit", 0)', 770 | \'bindings': 's', 771 | \'permanentBindings': '(gitv-vsplit)' 772 | \} 773 | let s:defaultMappings.prevCommit = { 774 | \'cmd': ':call JumpToCommit(0)', 775 | \'bindings': 'J', 776 | \'permanentBindings': '(gitv-previous-commit)' 777 | \} 778 | let s:defaultMappings.nextCommit = { 779 | \'cmd': ':call JumpToCommit(1)', 780 | \'bindings': 'K', 781 | \'permanentBindings': '(gitv-next-commit)' 782 | \} 783 | " force opening the fugitive buffer for the commit 784 | let s:defaultMappings.editCommitDetails = { 785 | \'cmd': ':call OpenGitvCommit("Gedit", 1)', 786 | \'bindings': 'i', 787 | \'permanentBindings': '(gitv-edit)' 788 | \} 789 | let s:defaultMappings.diff = { 790 | \'cmd': ':call DiffGitvCommit()', 791 | \'bindings': 'D' 792 | \} 793 | let s:defaultMappings.vdiff = { 794 | \'mapCmd': 'vnoremap', 795 | \'cmd': ':call DiffGitvCommit()', 796 | \'bindings': 'D' 797 | \} 798 | let s:defaultMappings.stat = { 799 | \'cmd': ':call StatGitvCommit()', 800 | \'bindings': 'S' 801 | \} 802 | let s:defaultMappings.vstat = { 803 | \'mapCmd': 'vnoremap', 804 | \'cmd': ':call StatGitvCommit()', 805 | \'bindings': 'S' 806 | \} 807 | 808 | " general git commands 809 | let s:defaultMappings.checkout = { 810 | \'cmd': ':call CheckOutGitvCommit()', 'bindings': 'co' 811 | \} 812 | let s:defaultMappings.merge = { 813 | \'cmd': ':call MergeToCurrent()', 'bindings': 'm' 814 | \} 815 | let s:defaultMappings.vmerge = { 816 | \'mapCmd': 'vnoremap', 817 | \'cmd': ':call MergeBranches()', 818 | \'bindings': 'm' 819 | \} 820 | let s:defaultMappings.cherryPick = { 821 | \'mapCmd': 'nmap', 822 | \'cmd': ':call CherryPick()', 823 | \'bindings': 'cp' 824 | \} 825 | let s:defaultMappings.vcherryPick = { 826 | \'mapCmd': 'vmap', 827 | \'cmd': ':call CherryPick()', 828 | \'bindings': 'cp' 829 | \} 830 | let s:defaultMappings.reset = { 831 | \'mapCmd': 'nmap', 832 | \'cmd': ':call ResetBranch("--mixed")', 833 | \'bindings': 'rb' 834 | \} 835 | let s:defaultMappings.vreset = { 836 | \'mapCmd': 'vmap', 837 | \'cmd': ':call ResetBranch("--mixed")', 838 | \'bindings': 'rb' 839 | \} 840 | let s:defaultMappings.resetSoft = { 841 | \'mapCmd': 'nmap', 842 | \'cmd': ':call ResetBranch("--soft")', 843 | \'bindings': 'rbs' 844 | \} 845 | let s:defaultMappings.vresetSoft = { 846 | \'mapCmd': 'vmap', 847 | \'cmd': ':call ResetBranch("--soft")', 848 | \'bindings': 'rbs' 849 | \} 850 | let s:defaultMappings.resetHard = { 851 | \'mapCmd': 'nmap', 852 | \'cmd': ':call ResetBranch("--hard")', 853 | \'bindings': 'rbh' 854 | \} 855 | let s:defaultMappings.vresetHard = { 856 | \'mapCmd': 'vmap', 857 | \'cmd': ':call ResetBranch("--hard")', 858 | \'bindings': 'rbh' 859 | \} 860 | let s:defaultMappings.revert = { 861 | \'mapCmd': 'nmap', 862 | \'cmd': ':call Revert()', 863 | \'bindings': 'rev' 864 | \} 865 | let s:defaultMappings.vrevert = { 866 | \'mapCmd': 'vmap', 867 | \'cmd': ':call Revert()', 868 | \'bindings': 'rev' 869 | \} 870 | let s:defaultMappings.delete = { 871 | \'mapCmd': 'nmap', 872 | \'cmd': ':call DeleteRef()', 873 | \'bindings': 'd' 874 | \} 875 | let s:defaultMappings.vdelete = { 876 | \'mapCmd': 'vmap', 877 | \'cmd': ':call DeleteRef()', 878 | \'bindings': 'd' 879 | \} 880 | 881 | " rebasing 882 | let s:defaultMappings.rebase = { 883 | \'cmd': ':call Rebase()', 884 | \'bindings': 'grr' 885 | \} 886 | let s:defaultMappings.vrebase = { 887 | \'mapCmd': 'vmap', 888 | \'cmd': ':call Rebase()', 889 | \'bindings': 'grr' 890 | \} 891 | let s:defaultMappings.rebasePick = { 892 | \'cmd': ':call RebaseSetInstruction("p")', 893 | \'bindings': 'grP' 894 | \} 895 | let s:defaultMappings.vrebasePick = { 896 | \'mapCmd': 'vmap', 897 | \'cmd': ':call RebaseSetInstruction("p")', 898 | \'bindings': 'grP' 899 | \} 900 | let s:defaultMappings.rebaseReword = { 901 | \'cmd': ':call RebaseSetInstruction("r")', 902 | \'bindings': 'grR' 903 | \} 904 | let s:defaultMappings.vrebaseReword = { 905 | \'mapCmd': 'vmap', 906 | \'cmd': ':call RebaseSetInstruction("r")', 907 | \'bindings': 'grR' 908 | \} 909 | let s:defaultMappings.rebaseMarkEdit = { 910 | \'cmd': ':call RebaseSetInstruction("e")', 911 | \'bindings': 'grE' 912 | \} 913 | let s:defaultMappings.vrebaseMarkEdit = { 914 | \'mapCmd': 'vmap', 915 | \'cmd': ':call RebaseSetInstruction("e")', 916 | \'bindings': 'grE' 917 | \} 918 | let s:defaultMappings.rebaseSquash = { 919 | \'cmd': ':call RebaseSetInstruction("s")', 920 | \'bindings': 'grS' 921 | \} 922 | let s:defaultMappings.vrebaseSquash = { 923 | \'mapCmd': 'vmap', 924 | \'cmd': ':call RebaseSetInstruction("s")', 925 | \'bindings': 'grS' 926 | \} 927 | let s:defaultMappings.rebaseFixup = { 928 | \'cmd': ':call RebaseSetInstruction("f")', 929 | \'bindings': 'grF' 930 | \} 931 | let s:defaultMappings.vrebaseFixup = { 932 | \'mapCmd': 'vmap', 933 | \'cmd': ':call RebaseSetInstruction("f")', 934 | \'bindings': 'grF' 935 | \} 936 | let s:defaultMappings.rebaseExec = { 937 | \'cmd': ':call RebaseSetInstruction("x")', 938 | \'bindings': 'grX' 939 | \} 940 | let s:defaultMappings.vrebaseExec = { 941 | \'mapCmd': 'vmap', 942 | \'cmd': ':call RebaseSetInstruction("x")', 943 | \'bindings': 'grX' 944 | \} 945 | let s:defaultMappings.rebaseDrop = { 946 | \'cmd': ':call RebaseSetInstruction("d")', 947 | \'bindings': 'grD' 948 | \} 949 | let s:defaultMappings.vrebaseDrop = { 950 | \'mapCmd': 'vmap', 951 | \'cmd': ':call RebaseSetInstruction("d")', 952 | \'bindings': 'grD' 953 | \} 954 | let s:defaultMappings.rebaseAbort = { 955 | \'cmd': ':call RebaseAbort()', 956 | \'bindings': 'gra' 957 | \} 958 | let s:defaultMappings.rebaseToggle = { 959 | \'cmd': ':call RebaseToggle()', 960 | \'bindings': 'grs' 961 | \} 962 | let s:defaultMappings.vrebaseToggle = { 963 | \'mapCmd': 'vmap', 964 | \'cmd': ':call RebaseToggle()', 965 | \'bindings': 'grs' 966 | \} 967 | let s:defaultMappings.rebaseSkip = { 968 | \'cmd': ':call RebaseSkip()', 969 | \'bindings': 'grn' 970 | \} 971 | let s:defaultMappings.rebaseContinue = { 972 | \'cmd': ':call RebaseContinue()', 973 | \'bindings': 'grc' 974 | \} 975 | 976 | " bisecting 977 | let s:defaultMappings.bisectStart = { 978 | \'mapCmd': 'nmap', 979 | \'cmd': ':call BisectStart("n")', 980 | \'bindings': 'gbs' 981 | \} 982 | let s:defaultMappings.vbisectStart = { 983 | \'mapCmd': 'vmap', 984 | \'cmd': ':call BisectStart("v")', 985 | \'bindings': 'gbs' 986 | \} 987 | let s:defaultMappings.bisectGood = { 988 | \'mapCmd': 'nmap', 989 | \'cmd': ':call BisectGoodBad("good")', 990 | \'bindings': 'gbg' 991 | \} 992 | let s:defaultMappings.vbisectGood = { 993 | \'mapCmd': 'vmap', 994 | \'cmd': ':call BisectGoodBad("good")', 995 | \'bindings': 'gbg' 996 | \} 997 | let s:defaultMappings.bisectBad = { 998 | \'mapCmd': 'nmap', 999 | \'cmd': ':call BisectGoodBad("bad")', 1000 | \'bindings': 'gbb' 1001 | \} 1002 | let s:defaultMappings.vbisectBad = { 1003 | \'mapCmd': 'vmap', 1004 | \'cmd': ':call BisectGoodBad("bad")', 1005 | \'bindings': 'gbb' 1006 | \} 1007 | let s:defaultMappings.bisectSkip = { 1008 | \'mapCmd': 'nmap', 1009 | \'cmd': ':call BisectSkip("n")', 1010 | \'bindings': 'gbn' 1011 | \} 1012 | let s:defaultMappings.vbisectSkip = { 1013 | \'mapCmd': 'vmap', 1014 | \'cmd': ':call BisectSkip("v")', 1015 | \'bindings': 'gbn' 1016 | \} 1017 | let s:defaultMappings.bisectReset = { 1018 | \'mapCmd': 'nmap', 1019 | \'cmd': ':call BisectReset()', 1020 | \'bindings': 'gbr' 1021 | \} 1022 | let s:defaultMappings.bisectLog = { 1023 | \'mapCmd': 'nmap', 1024 | \'cmd': ':call BisectLog()', 1025 | \'bindings': 'gbl' 1026 | \} 1027 | let s:defaultMappings.bisectReplay = { 1028 | \'mapCmd': 'nmap', 1029 | \'cmd': ':call BisectReplay()', 1030 | \'bindings': 'gbp' 1031 | \} 1032 | 1033 | " misc 1034 | let s:defaultMappings.git = { 1035 | \'mapOpts': '', 1036 | \'cmd': ':Git', 1037 | \'bindings': 'git' 1038 | \} 1039 | " yank the commit hash 1040 | if has('mac') || !has('unix') || has('xterm_clipboard') 1041 | let s:defaultMappings.yank = { 1042 | \'cmd': "m'$F[w\"+yw`'", 1043 | \'bindings': 'yc' 1044 | \} 1045 | else 1046 | let s:defaultMappings.yank = { 1047 | \'cmd': "m'$F[wyw`'", 1048 | \'bindings': 'yc' 1049 | \} 1050 | endif 1051 | 1052 | " bindings which use ctrl 1053 | if g:Gitv_DoNotMapCtrlKey != 1 1054 | let s:defaultMappings.ctrlPreviousCommit = { 1055 | \'mapCmd': 'nmap', 1056 | \'cmd': '(gitv-previous-commit)', 1057 | \'preventCustomBindings': 1, 1058 | \'bindings': '' 1059 | \} 1060 | let s:defaultMappings.ctrlNextCommit = { 1061 | \'mapCmd': 'nmap', 1062 | \'cmd': '(gitv-next-commit)', 1063 | \'preventCustomBindings': 1, 1064 | \'bindings': '' 1065 | \} 1066 | let s:defaultMappings.ctrlEdit = { 1067 | \'mapCmd': 'nmap', 1068 | \'cmd': '(gitv-edit)', 1069 | \'preventCustomBindings': 1, 1070 | \'bindings': '' 1071 | \} 1072 | let s:defaultMappings.ctrlSplit = { 1073 | \'mapCmd': 'nmap', 1074 | \'cmd': '(gitv-split)', 1075 | \'preventCustomBindings': 1, 1076 | \'bindings': '' 1077 | \} 1078 | let s:defaultMappings.ctrlVsplit = { 1079 | \'mapCmd': 'nmap', 1080 | \'cmd': '(gitv-vsplit)', 1081 | \'preventCustomBindings': 1, 1082 | \'bindings': '' 1083 | \} 1084 | let s:defaultMappings.ctrlTabe = { 1085 | \'mapCmd': 'nmap', 1086 | \'cmd': '(gitv-tabedit)', 1087 | \'preventCustomBindings': 1, 1088 | \'bindings': '' 1089 | \} 1090 | endif 1091 | endf "}}} 1092 | fu! s:NormalCmd(mapId, mappings) "{{{ 1093 | " Normal commands can sometimes be invoked after git commands with output 1094 | " This means we may have to move out of the terminal to execute them 1095 | let terminalStatus = s:MoveOutOfNvimTerminal() 1096 | let bindings = s:GetBindings(a:mapId, a:mappings) 1097 | exec 'normal '.bindings[0].keys 1098 | call s:MoveIntoNvimTerminal(terminalStatus) 1099 | endfu "}}} 1100 | fu! s:TransformBindings(bindings) "{{{ 1101 | " a:bindings can be a string or list of (in)complete binding descriptors 1102 | " a list of complete binding descriptors will be returned 1103 | " a complete binding object is a binding object with all possible fields 1104 | if type(a:bindings) != 3 " list 1105 | let bindings = [a:bindings] 1106 | else 1107 | let bindings = a:bindings 1108 | endif 1109 | let newBindings = [] 1110 | for binding in bindings 1111 | if type(binding) != 4 " dictionary 1112 | let newBinding = { 'keys': binding } 1113 | else 1114 | let newBinding = binding 1115 | endif 1116 | if !exists('newBinding.prefix') 1117 | let newBinding.prefix = '' 1118 | endif 1119 | call add(newBindings, newBinding) 1120 | unlet binding 1121 | endfor 1122 | return newBindings 1123 | endf "}}} 1124 | fu! s:GetBindings(mapId, mappings) "{{{ 1125 | " returns a list of complete binding objects based on customs/defaults 1126 | " does not return custom bindings for descriptors with preventCustomBindings 1127 | " always includes permanentBindings for an object 1128 | let defaults = a:mappings[a:mapId] 1129 | if exists('defaults.permanentBindings') 1130 | let permanentBindings = s:TransformBindings(defaults.permanentBindings) 1131 | else 1132 | let permanentBindings = [] 1133 | endif 1134 | if !exists('g:Gitv_CustomMappings[a:mapId]') 1135 | let bindings = defaults.bindings 1136 | else 1137 | if exists('defaults.preventCustomBindings') 1138 | let bindings = defaults.bindings 1139 | else 1140 | let bindings = g:Gitv_CustomMappings[a:mapId] 1141 | endif 1142 | endif 1143 | return s:TransformBindings(bindings) + permanentBindings 1144 | endf "}}} 1145 | fu! s:GetMapCmd(mapId, mappings) "{{{ 1146 | " gets the map command from the dictionary of defaults 1147 | " if it does not exist, returns 'nnoremap' 1148 | let defaults = a:mappings[a:mapId] 1149 | if !exists('defaults.mapCmd') 1150 | return 'nnoremap' 1151 | endif 1152 | return defaults.mapCmd 1153 | endf "}}} 1154 | fu! s:GetMapOpts(mapId, mappings) "{{{ 1155 | " gets the map options from the dictionary of defaults 1156 | " if it does not exist, returns ' ' 1157 | let defaults = a:mappings[a:mapId] 1158 | if !exists('defaults.mapOpts') 1159 | return ' ' 1160 | endif 1161 | return defaults.mapOpts 1162 | endf "}}} 1163 | fu! s:ApplyMapping(descriptor) "{{{ 1164 | " executes a map descriptor to apply the mappings 1165 | let prefix = a:descriptor.mapCmd . ' ' . a:descriptor.mapOpts . ' ' 1166 | let suffix = a:descriptor.cmd 1167 | for binding in a:descriptor.bindings 1168 | let cmd = prefix . binding.prefix . binding.keys . ' ' . suffix 1169 | exec cmd 1170 | endfor 1171 | endf "}}} 1172 | fu! s:GetMapDescriptor(mapId, mappings) "{{{ 1173 | " builds a complete map descriptor 1174 | " a complete map descriptor has all possible fields 1175 | if !exists('a:mappings[a:mapId]') 1176 | return 0 1177 | endif 1178 | let descriptor={ 1179 | \'mapCmd': s:GetMapCmd(a:mapId, a:mappings), 1180 | \'mapOpts': s:GetMapOpts(a:mapId, a:mappings), 1181 | \'cmd': a:mappings[a:mapId].cmd, 1182 | \'bindings': s:GetBindings(a:mapId, a:mappings) 1183 | \} 1184 | return descriptor 1185 | endf "}}} 1186 | fu! s:SetupMapping(mapId, mappings) "{{{ 1187 | " sets up a single mapping using defaults or custom descriptors 1188 | let mapping = s:GetMapDescriptor(a:mapId, a:mappings) 1189 | if type(mapping) != 4 " dictionary 1190 | echoerr "Invalid mapping: ".a:mapId 1191 | else 1192 | call s:ApplyMapping(mapping) 1193 | endif 1194 | endf "}}} 1195 | fu! s:SetupBackgroundMapping(mapId, binding) "{{{ 1196 | endf "}}} 1197 | fu! s:SetupMappings() "{{{ 1198 | call s:SetDefaultMappings() 1199 | "operations 1200 | for mapId in keys(s:defaultMappings) 1201 | call s:SetupMapping(mapId, s:defaultMappings) 1202 | endfor 1203 | endf "}}} }}} 1204 | fu! s:SetupBufferCommands(fileMode) "{{{ 1205 | exec 'silent command! -buffer -nargs=* -complete=customlist,'.s:fugitiveSid.'_GitComplete Git call MoveIntoPreviewAndExecute("unsilent Git ",1)| call NormalCmd("update", s:defaultMappings)' 1206 | endfu "}}} 1207 | fu! s:ResizeWindow(fileMode) "{{{ 1208 | if a:fileMode "window height determined by &previewheight 1209 | return 1210 | endif 1211 | if !s:IsHorizontal() 1212 | "size window based on longest line 1213 | let longest = max(map(range(1, line('$')), "virtcol([v:val, '$'])")) 1214 | if longest > &columns/2 1215 | "potentially auto change to horizontal 1216 | if s:AutoHorizontal() 1217 | "switching to horizontal 1218 | let b:Gitv_AutoHorizontal=1 1219 | wincmd K 1220 | call s:ResizeWindow(a:fileMode) 1221 | return 1222 | else 1223 | let longest = &columns/2 1224 | endif 1225 | endif 1226 | exec "vertical resize " . longest 1227 | else 1228 | "size window based on num lines 1229 | call s:ResizeHorizontal() 1230 | endif 1231 | endf "}}} }}} 1232 | "Utilities:"{{{ 1233 | " nvim sometimes opens a new window to execute commands. 1234 | " These utilities allow for temporarily exiting and re-entering the terminal. 1235 | " The terminal should ultimately be re-focused immediately after executing some command in gitv. 1236 | " This will show the output to the user. 1237 | fu! s:MoveOutOfNvimTerminal() "{{{ 1238 | let terminalIsOpen = has('nvim') && &buftype == 'terminal' 1239 | if terminalIsOpen | tabn | endif 1240 | return terminalIsOpen 1241 | endf "}}} 1242 | fu! s:MoveIntoNvimTerminal(terminalIsOpen) "{{{ 1243 | if a:terminalIsOpen | tabp | endif 1244 | endf "}}} 1245 | fu! s:GetParentSha(sha, parentNum) "{{{ 1246 | if a:parentNum < 1 1247 | return 1248 | endif 1249 | let hashCmd = "git log -n1 --pretty=format:%p " . a:sha 1250 | let [result,cmd] = s:RunGitCommand(hashCmd, 1) 1251 | let parents=split(result, ' ') 1252 | if a:parentNum > len(parents) 1253 | return 1254 | endif 1255 | return parents[a:parentNum-1] 1256 | endf "}}} 1257 | fu! s:GetConfirmString(list, ...) "{{{ {{{ 1258 | "returns a string to be used with confirm out of the choices in a:list 1259 | "any extra arguments are appended to the list of choices 1260 | "attempts to assign unique shortcut keys to every choice 1261 | "NOTE: choices must not be single letters and duplicates will be removed. 1262 | let totalList = a:list + a:000 1263 | let G = s:ConfirmStringBipartiteGraph(totalList) 1264 | let matches = s:MaxBipartiteMatching(G) 1265 | let choices = [] 1266 | for choice in totalList 1267 | let shortcutChar = get(matches, choice, '') 1268 | if shortcutChar != '' 1269 | call add(choices, substitute(choice, '\c'.shortcutChar, '\&\0', '')) 1270 | endif 1271 | endfor 1272 | return join(choices, "\n") 1273 | endfu "}}} 1274 | "Max Bipartite Matching Functions: "{{{ 1275 | let s:SOURCE_NODE = '__SOURCE__' 1276 | let s:SINK_NODE = '__SINK__' 1277 | fu! s:ConfirmStringBipartiteGraph(list) "{{{ 1278 | let G = {} 1279 | let G[s:SOURCE_NODE] = {} 1280 | for word in a:list 1281 | let G[word] = {} 1282 | let G[s:SOURCE_NODE][word] = 1 1283 | for i in range(len(word)) 1284 | let char = tolower(word[i]) 1285 | let G[word][char] = 1 1286 | if !has_key(G, char) | let G[char] = {} | endif 1287 | let G[char][s:SINK_NODE] = 1 1288 | endfor 1289 | endfor 1290 | return G 1291 | endfu "}}} 1292 | fu! s:MaxBipartiteMatching(G) "{{{ 1293 | let f = s:InitialiseFlow(a:G) 1294 | let path = s:GetPathInResidual(a:G, f, s:SOURCE_NODE, s:SINK_NODE) 1295 | while path != [] 1296 | let pathCost = 100000 "max path cost should be 1 so this is effectively infinite 1297 | for [u, v] in s:Partition(path) 1298 | let pathCost = min([pathCost, s:GetEdge(a:G, u, v) - s:GetEdge(f, u, v)]) 1299 | endfor 1300 | for [u, v] in s:Partition(path) 1301 | let f[u][v] = s:GetEdge(f, u, v) + pathCost 1302 | let f[v][u] = -s:GetEdge(f, u, v) 1303 | endfor 1304 | let path = s:GetPathInResidual(a:G, f, s:SOURCE_NODE, s:SINK_NODE) 1305 | endwhile 1306 | "f holds max flow for each edge, due to construction: include edge iff flow is 1 1307 | let returnDict = {} 1308 | for n1 in keys(f) 1309 | for [n2, val] in items(f[n1]) 1310 | if val == 1 1311 | let returnDict[n1] = n2 1312 | endif 1313 | endfor 1314 | endfor 1315 | return returnDict 1316 | endfu "}}} 1317 | fu! s:InitialiseFlow(G) "{{{ 1318 | let f = {} 1319 | for u in keys(a:G) 1320 | let f[u] = {} 1321 | for v in keys(a:G[u]) 1322 | let f[u][v] = 0 1323 | if !has_key(f, v) | let f[v] = {} | endif 1324 | let f[v][u] = 0 1325 | endfor 1326 | endfor 1327 | return f 1328 | endfu "}}} 1329 | fu! s:GetPathInResidual(G, f, s, t) "{{{ 1330 | "setup residual network 1331 | let Gf = deepcopy(a:f, 1) 1332 | for u in keys(a:f) 1333 | for v in keys(a:f[u]) 1334 | let Gf[u][v] = s:GetEdge(a:G, u, v) - a:f[u][v] 1335 | endfor 1336 | endfor 1337 | return s:BFS(Gf, a:s, a:t) 1338 | endfu "}}} 1339 | fu! s:Partition(path) "{{{ 1340 | "returns a list of [u,v] for the path 1341 | if len(a:path) < 2 | return a:path | endif 1342 | let parts = [] 1343 | for i in range(len(a:path)-1) 1344 | let parts = add(parts, [a:path[i], a:path[i+1]]) 1345 | endfor 1346 | return parts 1347 | endfu "}}} 1348 | fu! s:BFS(G, s, t) "{{{ 1349 | "BFS for t from s -- returns path 1350 | return s:BFSHelp(a:G, a:s, a:t, [], [], {}) 1351 | endfu "}}} 1352 | fu! s:BFSHelp(G, s, t, q, acc, visited) "{{{ 1353 | if a:s == a:t 1354 | return a:acc + [a:t] 1355 | endif 1356 | let a:visited[a:s] = 1 1357 | let children = s:GetEdges(a:G, a:s) 1358 | call filter(children, '!get(a:visited, v:val, 0)') 1359 | if empty(a:q) && empty(children) | return [] | endif 1360 | 1361 | let newq = empty(children) ? a:q : a:q + [[a:acc+[a:s], children]] 1362 | let newAcc = a:acc 1363 | if type(newq[0]) == type([]) 1364 | let newAcc = newq[0][0] 1365 | let newq = newq[0][1] + newq[1:] 1366 | endif 1367 | return s:BFSHelp(a:G, newq[0], a:t, newq[1:], newAcc, a:visited) 1368 | endfu "}}} 1369 | fu! s:GetEdge(G, u, v) "{{{ 1370 | "returns 0 if edge does not exist 1371 | return get(get(a:G, a:u, {}), a:v, 0) 1372 | endfu "}}} 1373 | fu! s:GetEdges(G, u) "{{{ 1374 | let e = [] 1375 | for k in keys(get(a:G, a:u, {})) 1376 | let e += a:G[a:u][k] > 0 ? [k] : [] 1377 | endfor 1378 | return e 1379 | endfu "}}} }}} }}} 1380 | fu! s:RecordBufferExecAndWipe(cmd, wipe) "{{{ 1381 | "this should be used to replace the buffer in a window 1382 | let buf = bufnr('%') 1383 | exec a:cmd 1384 | if a:wipe 1385 | "safe guard against wiping out buffer you're in 1386 | if bufnr('%') != buf && bufexists(buf) 1387 | " ignore errors from bdelete -- the user won't care if it's 1388 | " already deleted 1389 | exec 'silent! bdelete ' . buf 1390 | endif 1391 | endif 1392 | endfu "}}} 1393 | fu! s:SwitchBetweenWindows() "{{{ 1394 | let currentType = &filetype 1395 | if currentType == 'gitv' 1396 | if s:IsFileMode() 1397 | return 1398 | endif 1399 | let targetType = 'git' 1400 | else 1401 | let targetType = 'gitv' 1402 | endif 1403 | let winnum = -1 1404 | windo exec 'if &filetype == targetType | let winnum = winnr() | endif' 1405 | if winnum != -1 1406 | execute winnum.'wincmd w' 1407 | endif 1408 | endfu "}}} 1409 | fu! s:MoveIntoPreviewAndExecute(cmd, tryToOpenNewWin) "{{{ 1410 | if winnr("$") == 1 "is the only window 1411 | call s:AttemptToCreateAPreviewWindow(a:tryToOpenNewWin, a:cmd, 0) 1412 | return 1413 | endif 1414 | let horiz = s:IsHorizontal() 1415 | let filem = s:IsFileMode() 1416 | let currentWin = winnr() 1417 | 1418 | if horiz || filem 1419 | wincmd j 1420 | else 1421 | wincmd l 1422 | endif 1423 | 1424 | if currentWin == winnr() "haven't moved anywhere 1425 | call s:AttemptToCreateAPreviewWindow(a:tryToOpenNewWin, a:cmd, 1) 1426 | return 1427 | endif 1428 | 1429 | silent exec a:cmd 1430 | let terminalStatus = s:MoveOutOfNvimTerminal() 1431 | 1432 | " Move back into the branch viewer 1433 | if horiz || filem 1434 | wincmd k 1435 | else 1436 | wincmd h 1437 | endif 1438 | 1439 | call s:MoveIntoNvimTerminal(terminalStatus) 1440 | endfu "}}} 1441 | fu! s:AttemptToCreateAPreviewWindow(shouldAttempt, cmd, shouldWarn) "{{{ 1442 | if a:shouldAttempt 1443 | call s:CreateNewPreviewWindow() 1444 | call s:MoveIntoPreviewAndExecute(a:cmd, 0) 1445 | elseif a:shouldWarn 1446 | echoerr "No preview window detected." 1447 | endif 1448 | endfu "}}} 1449 | fu! s:CreateNewPreviewWindow() "{{{ 1450 | "this should not be called by anything other than AttemptToCreateAPreviewWindow 1451 | let horiz = s:IsHorizontal() 1452 | let filem = s:IsFileMode() 1453 | if horiz || filem 1454 | Gsplit HEAD 1455 | else 1456 | Gvsplit HEAD 1457 | endif 1458 | wincmd x 1459 | endfu "}}} 1460 | fu! s:IsHorizontal() "{{{ 1461 | "NOTE: this can only tell you if horizontal while cursor in browser window 1462 | let horizGlobal = exists('g:Gitv_OpenHorizontal') && g:Gitv_OpenHorizontal == 1 1463 | let horizBuffer = exists('b:Gitv_AutoHorizontal') && b:Gitv_AutoHorizontal == 1 1464 | return horizGlobal || horizBuffer 1465 | endf "}}} 1466 | fu! s:AutoHorizontal() "{{{ 1467 | return exists('g:Gitv_OpenHorizontal') && 1468 | \ type(g:Gitv_OpenHorizontal) == type("") && 1469 | \ g:Gitv_OpenHorizontal ==? 'auto' 1470 | endf "}}} 1471 | fu! s:IsFileMode() "{{{ 1472 | return exists('b:Gitv_FileMode') && b:Gitv_FileMode == 1 1473 | endf "}}} 1474 | fu! s:ResizeHorizontal() "{{{ 1475 | let lines = line('$') 1476 | if lines > (&lines/2)-2 1477 | let lines = (&lines/2)-2 1478 | endif 1479 | exec "resize " . lines 1480 | endf "}}} 1481 | fu! s:GetRelativeFilePath() "{{{ 1482 | return exists('b:Gitv_FileModeRelPath') ? b:Gitv_FileModeRelPath : '' 1483 | endf "}}} 1484 | fu! s:GetRange() "{{{ 1485 | return exists('b:Gitv_FileModeRange') ? b:Gitv_FileModeRange : [] 1486 | endfu "}}} 1487 | fu! s:SetRange(idx, value) "{{{ 1488 | "idx - {0,1}, 0 for beginning, 1 for end. 1489 | let b:Gitv_FileModeRange[a:idx] = a:value 1490 | endfu "}}} 1491 | fu! s:FoldToRevealOnlyRange(rangeStart, rangeEnd) "{{{ 1492 | setlocal foldmethod=manual 1493 | normal zE 1494 | let rangeS = '/'.escape(matchstr(a:rangeStart, '/\zs.*\ze/'), '~[]/\.^$*').'/' 1495 | let rangeE = '/'.escape(matchstr(a:rangeEnd, '/\zs.*\ze/'), '~[]/\.^$*').'/' 1496 | exec '1,'.rangeS.'-1fold' 1497 | exec rangeE.'+1,$fold' 1498 | endfu "}}} 1499 | fu! s:OpenRelativeFilePath(sha, geditForm) "{{{ 1500 | let relPath = s:GetRelativeFilePath() 1501 | if relPath == '' 1502 | return 1503 | endif 1504 | let cmd = a:geditForm . " " . a:sha . ":" . relPath 1505 | let cmd = 'call s:RecordBufferExecAndWipe("'.cmd.'", '.(a:geditForm=='Gedit').')' 1506 | call s:MoveIntoPreviewAndExecute(cmd, 1) 1507 | let range = s:GetRange() 1508 | if range != [] 1509 | let rangeS = escape(range[0], '"') 1510 | let rangeE = escape(range[1], '"') 1511 | call s:MoveIntoPreviewAndExecute('call s:FoldToRevealOnlyRange("'.rangeS.'", "'.rangeE.'")', 0) 1512 | endif 1513 | endf "}}} }}} 1514 | "Mapped Functions:"{{{ 1515 | "Operations: "{{{ 1516 | fu! s:GetCommitMsg() "{{{ 1517 | return fugitive#repo().tree().'/.git/COMMIT_EDITMSG' 1518 | endf "}}} 1519 | fu! s:OpenGitvCommit(geditForm, forceOpenFugitive) "{{{ 1520 | let bindingsCmd = 'call s:MoveIntoPreviewAndExecute("call s:SetupMapping('."'".'toggleWindow'."'".', s:defaultMappings)", 0)' 1521 | let line = getline('.') 1522 | if line == "-- Load More --" 1523 | call s:LoadGitv('', 1, b:Gitv_CommitCount+g:Gitv_CommitStep, b:Gitv_ExtraArgs, s:GetRelativeFilePath(), s:GetRange()) 1524 | return 1525 | endif 1526 | if s:IsFileMode() && line =~ "^-- \\[.*\\] --$" 1527 | call s:OpenWorkingCopy(a:geditForm) 1528 | return 1529 | endif 1530 | if line =~ s:pendingRebaseMsg.'$' || line =~ s:rebaseMsg.'$' 1531 | call s:RebaseEdit() 1532 | return 1533 | endif 1534 | if line =~ s:localUncommitedMsg.'$' 1535 | call s:OpenWorkingDiff(a:geditForm, 0) 1536 | exec bindingsCmd 1537 | return 1538 | endif 1539 | if line =~ s:localCommitedMsg.'$' 1540 | call s:OpenWorkingDiff(a:geditForm, 1) 1541 | exec bindingsCmd 1542 | return 1543 | endif 1544 | if s:IsFileMode() && line =~ '^-- /.*/$' 1545 | if s:EditRange(matchstr(line, '^-- /\zs.*\ze/$')) 1546 | call s:NormalCmd('update', s:defaultMappings) 1547 | endif 1548 | return 1549 | endif 1550 | let sha = gitv#util#line#sha(line('.')) 1551 | if sha == "" 1552 | return 1553 | endif 1554 | if s:IsFileMode() && !a:forceOpenFugitive 1555 | call s:OpenRelativeFilePath(sha, a:geditForm) 1556 | else 1557 | let opts = g:Gitv_PreviewOptions 1558 | if opts == '' 1559 | let cmd = a:geditForm . " " . sha 1560 | let cmd = 'call s:RecordBufferExecAndWipe("'.cmd.'", '.(a:geditForm=='Gedit').')' 1561 | else 1562 | let winCmd = a:geditForm[1:] == 'edit' ? '' : a:geditForm[1:] 1563 | let cmd = 'call Gitv_OpenGitCommand(\"show '.opts.' --no-color '.sha.'\", \"'.winCmd.'\")' 1564 | let cmd = 'call s:RecordBufferExecAndWipe("'.cmd.'", '.(winCmd=='').')' 1565 | endif 1566 | call s:MoveIntoPreviewAndExecute(cmd, 1) 1567 | call s:MoveIntoPreviewAndExecute('setlocal fdm=syntax', 0) 1568 | exec bindingsCmd 1569 | endif 1570 | endf 1571 | fu! s:OpenWorkingCopy(geditForm) 1572 | let fp = s:GetRelativeFilePath() 1573 | let form = a:geditForm[1:] "strip off the leading 'G' 1574 | let cmd = form . " " . fugitive#repo().tree() . "/" . fp 1575 | let cmd = 'call s:RecordBufferExecAndWipe("'.cmd.'", '.(form=='edit').')' 1576 | call s:MoveIntoPreviewAndExecute(cmd, 1) 1577 | endfu 1578 | fu! s:OpenWorkingDiff(geditForm, staged) 1579 | let winCmd = a:geditForm[1:] == 'edit' ? '' : a:geditForm[1:] 1580 | if s:IsFileMode() 1581 | let fp = s:GetRelativeFilePath() 1582 | let suffix = ' -- '.fp 1583 | let g:Gitv_InstanceCounter += 1 1584 | let winCmd = 'new gitv'.'-'.g:Gitv_InstanceCounter 1585 | else 1586 | let suffix = '' 1587 | endif 1588 | if a:staged 1589 | let cmd = 'call Gitv_OpenGitCommand(\"diff --no-color --cached'.suffix.'\", \"'.winCmd.'\")' 1590 | else 1591 | let cmd = 'call Gitv_OpenGitCommand(\"diff --no-color'.suffix.'\", \"'.winCmd.'\")' 1592 | endif 1593 | let cmd = 'call s:RecordBufferExecAndWipe("'.cmd.'", '.(winCmd=='').')' 1594 | call s:MoveIntoPreviewAndExecute(cmd, 1) 1595 | endfu 1596 | fu! s:EditRange(rangeDelimiter) 1597 | let range = s:GetRange() 1598 | let rangeDelimWithSlashes = '/'.a:rangeDelimiter.'/' 1599 | let idx = rangeDelimWithSlashes == range[0] ? 0 : rangeDelimWithSlashes == range[1] ? 1 : -1 1600 | if idx == -1 1601 | return 0 1602 | endif 1603 | let value = input("Enter new range regex: ", a:rangeDelimiter) 1604 | let value = '/'.value.'/' 1605 | if value == range[idx] 1606 | return 0 "no need to update 1607 | endif 1608 | call s:SetRange(idx, value) 1609 | return 1 1610 | endfu "}}} 1611 | " Rebase: "{{{ 1612 | fu! s:RebaseHasInstructions() "{{{ 1613 | return exists('b:Gitv_RebaseInstructions') && len(keys(b:Gitv_RebaseInstructions)) > 0 1614 | endf "}}} 1615 | fu! s:RebaseClearInstructions() "{{{ 1616 | let b:Gitv_RebaseInstructions = {} 1617 | endf "}}} 1618 | fu! s:RebaseSetInstruction(instruction) range "{{{ 1619 | if s:IsFileMode() 1620 | return 1621 | endif 1622 | if s:BisectHasStarted() 1623 | echo "Cannot set rebase instructions in bisect mode." 1624 | return 1625 | endif 1626 | if s:RebaseIsEnabled() 1627 | echo "Rebase already in progress." 1628 | return 1629 | endif 1630 | if a:instruction == 'x' 1631 | let cmd = input('Please enter a command to execute for each commit: ') 1632 | if cmd == '' 1633 | echo 'Not marking any commits for exec.' 1634 | return 1635 | endif 1636 | endif 1637 | let ncommits = 0 1638 | for line in range(a:firstline, a:lastline) 1639 | let sha = gitv#util#line#sha(line) 1640 | if sha == '' 1641 | continue 1642 | endif 1643 | let ncommits += 1 1644 | if a:instruction == 'p' || a:instruction == 'pick' || a:instruction == '' 1645 | if !exists('b:Gitv_RebaseInstructions[sha]') 1646 | continue 1647 | endif 1648 | call remove(b:Gitv_RebaseInstructions, sha) 1649 | else 1650 | if exists('cmd') 1651 | if !exists('b:Gitv_RebaseInstructions[sha]') 1652 | let b:Gitv_RebaseInstructions[sha] = { 'instruction': 'p' } 1653 | endif 1654 | let b:Gitv_RebaseInstructions[sha].cmd = cmd 1655 | else 1656 | let b:Gitv_RebaseInstructions[sha] = { 'instruction': a:instruction } 1657 | endif 1658 | endif 1659 | endfor 1660 | if ncommits < 1 1661 | echo "No commits marked." 1662 | return 1663 | elseif ncommits > 1 1664 | let prettyCommit = ncommits .' commits' 1665 | else 1666 | let prettyCommit = gitv#util#line#sha('.') 1667 | endif 1668 | call s:RebaseUpdateView() 1669 | if exists('cmd') 1670 | redraw 1671 | echo cmd 'will be executed after' prettyCommit.'.' 1672 | else 1673 | echo prettyCommit.' marked with "'.a:instruction.'".' 1674 | endif 1675 | endf "}}} 1676 | fu! s:RebaseHasStarted() "{{{ 1677 | return !empty(glob(fugitive#repo().tree().'/.git/rebase-merge')) 1678 | endf "}}} 1679 | fu! s:RebaseGetRefs(line) "{{{ 1680 | let sha = gitv#util#line#sha(a:line) 1681 | if sha == "" 1682 | return [] 1683 | endif 1684 | return gitv#util#line#refs(a:line) + [sha] 1685 | endf "}}} 1686 | fu! s:RebaseGetChoice(refs, purpose) "{{{ 1687 | let msg = "Choose destination to rebase ".a:purpose.":" 1688 | let choice = confirm(msg, s:GetConfirmString(a:refs, "Cancel")) 1689 | if choice == 0 || choice > len(a:refs) 1690 | return '' 1691 | endif 1692 | let choice = a:refs[choice - 1] 1693 | let choice = substitute(choice, "^t:", "", "") 1694 | let choice = substitute(choice, "^r:", "", "") 1695 | return choice 1696 | endf "}}} 1697 | fu! s:RebaseGetRange(first, last, fromPlaceholder, ontoPlaceholder) "{{{ 1698 | " will attempt to grab a reference from a range of lines 1699 | " if the range is 0, will only grab one reference and use a placeholder for the other 1700 | " at least one placeholder must be given 1701 | " the placeholder can be an empty string 1702 | " for no placeholder, use a number 1703 | if a:first != a:last 1704 | let msg = 'Rebase from top or bottom?' 1705 | let choice = confirm(msg, "&top\n&bottom\n&cancel") 1706 | if choice == 0 || choice == 3 1707 | return [] 1708 | endif 1709 | else 1710 | let choice = 1 1711 | endif 1712 | if choice == 1 1713 | let from = a:first 1714 | let onto = a:last 1715 | else 1716 | let from = a:last 1717 | let onto = a:first 1718 | endif 1719 | 1720 | " get refs 1721 | if a:first != a:last || type(a:fromPlaceholder) != 1 " string 1722 | let fromRefs = s:RebaseGetRefs(from) 1723 | if !len(fromRefs) 1724 | return [] 1725 | endif 1726 | else 1727 | let fromRefs = from 1728 | endif 1729 | if a:first != a:last || type(a:ontoPlaceholder) != 1 1730 | let ontoRefs = s:RebaseGetRefs(onto) 1731 | if !len(ontoRefs) 1732 | return [] 1733 | endif 1734 | else 1735 | let ontoRefs = onto 1736 | endif 1737 | 1738 | " set placeholder 1739 | if a:first == a:last 1740 | if type(a:fromPlaceholder) == 1 1741 | unlet fromRefs 1742 | let fromRefs = a:fromPlaceholder 1743 | elseif type(a:ontoPlaceholder) == 1 1744 | unlet ontoRefs 1745 | let ontoRefs = a:ontoPlaceholder 1746 | else 1747 | echoerr 'A default must be given.' 1748 | return [] 1749 | endif 1750 | endif 1751 | 1752 | " get choices 1753 | if a:first != a:last || type(a:fromPlaceholder) != 1 1754 | let fromChoice = s:RebaseGetChoice(fromRefs, 'from') 1755 | if fromChoice == '' 1756 | return [] 1757 | endif 1758 | else 1759 | let fromChoice = fromRefs 1760 | endif 1761 | if a:first != a:last || type(a:ontoPlaceholder) != 1 1762 | let ontoChoice = s:RebaseGetChoice(ontoRefs, 'onto') 1763 | if ontoChoice == '' 1764 | return [] 1765 | endif 1766 | else 1767 | let ontoChoice = ontoRefs 1768 | endif 1769 | 1770 | return [fromChoice, ontoChoice] 1771 | endf "}}} 1772 | fu! s:Rebase() range "{{{ 1773 | if s:IsFileMode() 1774 | return 1775 | endif 1776 | if s:BisectIsEnabled() || s:BisectHasStarted() 1777 | echo "Cannot rebase in bisect mode." 1778 | return 1779 | endif 1780 | if s:RebaseHasStarted() 1781 | echoerr "Rebase already in progress." 1782 | return 1783 | endif 1784 | let choice = s:RebaseGetRange(a:firstline, a:lastline, 'HEAD', 0) 1785 | if !len(choice) 1786 | redraw 1787 | echo "Not rebasing." 1788 | return 1789 | endif 1790 | let result = s:RunGitCommand('rebase '.choice[0].' '.choice[1], 0)[0] 1791 | let hasError = v:shell_error 1792 | call s:RebaseUpdateView() 1793 | if hasError 1794 | redraw 1795 | echoerr split(result, '\n')[0] 1796 | endif 1797 | endf "}}} 1798 | fu! s:SetRebaseEditor() "{{{ 1799 | " override the default editor used for interactive rebasing 1800 | if s:RebaseHasInstructions() 1801 | " replace default instructions with stored instructions 1802 | let $GIT_SEQUENCE_EDITOR='function gitv_edit() {' 1803 | for sha in keys(b:Gitv_RebaseInstructions) 1804 | let short = sha[0:6] 1805 | let instruction = b:Gitv_RebaseInstructions[sha].instruction 1806 | let $GIT_SEQUENCE_EDITOR .= ' SHA_'.short.'='.instruction.';' 1807 | if exists('b:Gitv_RebaseInstructions[sha].cmd') 1808 | let cmd = b:Gitv_RebaseInstructions[sha].cmd 1809 | let $GIT_SEQUENCE_EDITOR .= ' CMD_'.short.'='.shellescape(cmd).';' 1810 | endif 1811 | endfor 1812 | let $GIT_SEQUENCE_EDITOR .= 'while read line; do 1813 | \ if [[ $line == "" ]]; then break; fi; 1814 | \ sha=${line:5:7}; 1815 | \ key=SHA_$sha; 1816 | \ if [[ ${!key} != "" ]]; then 1817 | \ cmd=CMD_$sha; 1818 | \ echo ${!key} ${line:5}; 1819 | \ if [[ ${!cmd} != "" ]]; then 1820 | \ echo x ${!cmd}; 1821 | \ fi; 1822 | \ else 1823 | \ echo $line; 1824 | \ fi; 1825 | \ done < $1 > '.s:workingFile.'; 1826 | \ mv '.s:workingFile.' $1; 1827 | \ }; gitv_edit' 1828 | else 1829 | " change pick to edit 1830 | let $GIT_SEQUENCE_EDITOR='function gitv_edit() { 1831 | \ echo "" > '.s:workingFile.'; 1832 | \ while read line; do 1833 | \ if [[ "${line:0:4}" == "pick" ]]; then 1834 | \ echo "edit ${line:5}"; 1835 | \ else 1836 | \ echo $line; 1837 | \ fi; 1838 | \ done < $1 > '.s:workingFile.'; 1839 | \ mv '.s:workingFile.' $1; 1840 | \ }; gitv_edit' 1841 | endif 1842 | endf "}}} 1843 | fu! s:RebaseUpdateView() "{{{ 1844 | " attempt to move out of the rebase/commit/preview window and update 1845 | wincmd j 1846 | wincmd h 1847 | wincmd j 1848 | if &ft == 'gitv' 1849 | call s:NormalCmd('update', s:defaultMappings) 1850 | endif 1851 | endf "}}} 1852 | fu! s:RebaseAbort() "{{{ 1853 | if s:RebaseHasStarted() 1854 | echo 'Abort current rebase? (y/n) ' 1855 | if nr2char(getchar()) == 'y' 1856 | call s:RunGitCommand('rebase --abort', 0) 1857 | call s:RebaseUpdateView() 1858 | endif 1859 | return 1860 | else 1861 | echo 'Rebase not in progress.' 1862 | endif 1863 | endf "}}} 1864 | fu! s:RebaseUpdate() "{{{ 1865 | if s:RebaseHasInstructions() && (s:BisectIsEnabled() || s:RebaseHasStarted() || s:BisectHasStarted()) 1866 | echoerr "Mode changed elsewhere, dropping rebase instructions." 1867 | let b:Gitv_RebaseInstructions = {} 1868 | endif 1869 | if !s:RebaseHasStarted() && s:RebaseIsEnabled() 1870 | let b:Gitv_Rebasing = 0 1871 | endif 1872 | endf "}}} 1873 | fu! s:RebaseIsEnabled() "{{{ 1874 | return exists('b:Gitv_Rebasing') && b:Gitv_Rebasing == 1 1875 | endf "}}} 1876 | fu! s:RebaseToggle() range "{{{ 1877 | if s:IsFileMode() 1878 | return 1879 | endif 1880 | if s:RebaseIsEnabled() 1881 | let b:Gitv_Rebasing = 0 1882 | call s:RebaseUpdateView() 1883 | return 1884 | elseif s:RebaseHasStarted() 1885 | let b:Gitv_Rebasing = 1 1886 | call s:RebaseUpdateView() 1887 | return 1888 | elseif s:BisectIsEnabled() || s:BisectHasStarted() 1889 | echoerr "Cannot rebase in bisect mode." 1890 | return 1891 | endif 1892 | let choice = s:RebaseGetRange(a:firstline, a:lastline, 0, '') 1893 | if !len(choice) 1894 | redraw 1895 | echo "Not rebasing." 1896 | return 1897 | endif 1898 | let b:Gitv_Rebasing = 1 1899 | call s:SetRebaseEditor() 1900 | let cmd = 'rebase --preserve-merges --interactive '.choice[0] 1901 | if s:RebaseHasInstructions() 1902 | " we don't know what the instructions are, treat it like a continue 1903 | call s:RebaseContinueSetup() 1904 | " only jump to the commit before this 1905 | let cmd .= '^' 1906 | else 1907 | " jump to two commits before so we can stop and edit 1908 | let cmd .= '~2' 1909 | endif 1910 | let cmd .= ' '.choice[1] 1911 | let result=s:RunGitCommand(cmd, 0)[0] 1912 | let result = split(result, '\n')[0] 1913 | let hasError = v:shell_error 1914 | let hasInstructions = s:RebaseHasInstructions() 1915 | call s:RebaseClearInstructions() 1916 | call s:RebaseUpdateView() 1917 | let $GIT_SEQUENCE_EDITOR="" 1918 | if hasError && !hasInstructions 1919 | echoerr result 1920 | return 1921 | elseif !hasError && hasInstructions 1922 | echo result 1923 | endif 1924 | if hasInstructions 1925 | call s:RebaseContinueCleanup() 1926 | else 1927 | call s:RebaseEdit() 1928 | endif 1929 | endf "}}} 1930 | fu! s:RebaseSkip() "{{{ 1931 | if !s:RebaseIsEnabled() 1932 | return 1933 | endif 1934 | for i in range(0, v:count) 1935 | let result = split(s:RunGitCommand('rebase --skip', 0)[0], '\n')[0] 1936 | let hasError = v:shell_error 1937 | if i == v:count || hasError 1938 | call s:RebaseUpdateView() 1939 | endif 1940 | if hasError 1941 | echoerr result 1942 | return 1943 | else 1944 | echo result 1945 | endif 1946 | endfor 1947 | endf "}}} 1948 | fu! s:GetRebaseMode() "{{{ 1949 | let output = readfile(s:GetRebaseDone()) 1950 | let length = len(output) 1951 | if length < 1 1952 | return '' 1953 | endif 1954 | return output[length - 1][0] 1955 | endf "}}} 1956 | fu! s:RebaseContinueSetup() "{{{ 1957 | " override the commit editor in a way that lets us take over rebase 1958 | let $GIT_EDITOR='exit 1' 1959 | endf "}}} 1960 | fu! s:RebaseContinue() "{{{ 1961 | if !s:RebaseIsEnabled() 1962 | return 1963 | endif 1964 | call s:RebaseContinueSetup() 1965 | let result = s:RunGitCommand('rebase --continue', 0)[0] 1966 | " we expect an error because of what we did with exit 1967 | if !v:shell_error 1968 | echo split(result, '\n')[0] 1969 | endif 1970 | call s:RebaseUpdateView() 1971 | call s:RebaseContinueCleanup() 1972 | endf "}}} 1973 | fu! s:RebaseContinueCleanup() "{{{ 1974 | let $GIT_EDITOR="" 1975 | if !s:RebaseIsEnabled() 1976 | return 1977 | endif 1978 | let mode = s:GetRebaseMode() 1979 | if mode == 's' 1980 | " errors with squash cause us to fall through to the next commit 1981 | " the desired commit message is still in place when we fall through 1982 | call writefile([], s:workingFile) 1983 | call writefile(readfile(s:GetCommitMsg()), s:workingFile) 1984 | let result = s:RunGitCommand('reset --soft HEAD~1', 0)[0] 1985 | if v:shell_error 1986 | echoerr split(result, '\n')[0] 1987 | return 1988 | endif 1989 | endif 1990 | if mode == 'r' || mode == 's' 1991 | if mode == 'r' 1992 | Gcommit --amend 1993 | else 1994 | Gcommit 1995 | endif 1996 | set modifiable 1997 | if &ft == 'gitcommit' 1998 | if mode == 's' 1999 | call writefile(readfile(s:workingFile), s:GetCommitMsg()) 2000 | endif 2001 | endif 2002 | endif 2003 | endf "}}} 2004 | fu! s:GetRebaseHeadname() "{{{ 2005 | return fugitive#repo().tree().'/.git/rebase-merge/head-name' 2006 | endf "}}} 2007 | fu! s:GetRebaseDone() "{{{ 2008 | return fugitive#repo().tree().'/.git/rebase-merge/done' 2009 | endf "}}} 2010 | fu! s:GetRebaseTodo() "{{{ 2011 | return fugitive#repo().tree().'/.git/rebase-merge/git-rebase-todo' 2012 | endf "}}} 2013 | fu! s:RebaseViewInstructions() "{{{ 2014 | exec 'edit' s:workingFile 2015 | if expand('%') == s:workingFile 2016 | silent setlocal syntax=gitrebase 2017 | silent setlocal nomodifiable 2018 | endif 2019 | endf "}}} 2020 | fu! s:RebaseEditTodo() "{{{ 2021 | exec 'edit' s:GetRebaseTodo() 2022 | if &ft == 'gitrebase' 2023 | silent setlocal modifiable 2024 | silent setlocal noreadonly 2025 | silent setlocal buftype 2026 | silent setlocal nobuflisted 2027 | silent setlocal noswapfile 2028 | silent setlocal bufhidden=wipe 2029 | endif 2030 | endf "}} 2031 | fu! s:RebaseEdit() "{{{ 2032 | if s:RebaseHasInstructions() 2033 | " rebase should not be started, but we have set instructions to view 2034 | let output = [] 2035 | for key in keys(b:Gitv_RebaseInstructions) 2036 | let line = b:Gitv_RebaseInstructions[key].instruction.' '.key 2037 | if exists('b:Gitv_RebaseInstructions[key].cmd') 2038 | let line .= ' '.b:Gitv_RebaseInstructions[key].cmd 2039 | endif 2040 | call add(output, line) 2041 | endfor 2042 | call writefile(output, s:workingFile) 2043 | call s:MoveIntoPreviewAndExecute('call s:RebaseViewInstructions()', 1) 2044 | endif 2045 | if !s:RebaseHasStarted() 2046 | return 2047 | endif 2048 | let todo = s:GetRebaseTodo() 2049 | if empty(glob(todo)) 2050 | echoerr 'No interactive rebase in progress.' 2051 | return 2052 | endif 2053 | call s:MoveIntoPreviewAndExecute('call s:RebaseEditTodo()', 1) 2054 | wincmd l 2055 | endf "}}} }}} 2056 | "Bisect: "{{{ 2057 | fu! s:IsBisectingFiles(filePaths) "{{{ 2058 | let path = fugitive#repo().tree().'/.git/BISECT_NAMES' 2059 | if empty(glob(path)) 2060 | return 0 2061 | endif 2062 | let paths = '' 2063 | for filePath in a:filePaths 2064 | let paths .= " '".filePath."'" 2065 | endfor 2066 | return join(readfile(path), ' ') == paths 2067 | endf "}}} 2068 | fu! s:IsBisectingCurrentFiles() "{{{ 2069 | if s:IsFileMode() && !s:IsBisectingFiles([b:Gitv_FileModeRelPath]) 2070 | return 0 2071 | endif 2072 | if b:Gitv_ExtraArgs[1] != '' && !s:IsBisectingFiles(split(b:Gitv_ExtraArgs[1], ' ')) 2073 | return 0 2074 | endif 2075 | return 1 2076 | endf "}}} 2077 | fu! s:BisectUpdate() "{{{ 2078 | if s:BisectIsEnabled() && !s:BisectHasStarted() 2079 | echoerr "Mode changed elsewhere, dropping bisect." 2080 | let b:Gitv_Bisecting = 0 2081 | endif 2082 | endf "}}} 2083 | fu! s:BisectIsEnabled() "{{{ 2084 | return exists('b:Gitv_Bisecting') && b:Gitv_Bisecting == 1 2085 | endf "}}} 2086 | fu! s:BisectHasStarted() "{{{ 2087 | call s:RunGitCommand('bisect log', 0) 2088 | return !v:shell_error 2089 | endf "}}} 2090 | fu! s:BisectStart(mode) range "{{{ 2091 | if s:RebaseHasInstructions() || s:RebaseHasStarted() 2092 | echo "Cannot bisect in rebase mode." 2093 | return 2094 | endif 2095 | if s:BisectIsEnabled() 2096 | if g:Gitv_QuietBisect == 0 2097 | echom 'Bisect disabled' 2098 | endif 2099 | let b:Gitv_Bisecting = 0 2100 | return 2101 | elseif !s:BisectHasStarted() 2102 | let cmd = 'bisect start' 2103 | if b:Gitv_ExtraArgs[1] != '' 2104 | let cmd .= join(b:Gitv_ExtraArgs, ' ') 2105 | elseif s:IsFileMode() 2106 | let cmd .= ' '.b:Gitv_FileModeRelPath 2107 | endif 2108 | let result = s:RunGitCommand(cmd, 0)[0] 2109 | if v:shell_error 2110 | echoerr split(result, '\n')[0] 2111 | return 2112 | endif 2113 | if a:mode == 'v' 2114 | call s:RunGitCommand('bisect bad ' . gitv#util#line#sha(a:firstline), 0)[0] 2115 | if a:firstline != a:lastline 2116 | call s:RunGitCommand('bisect good ' . gitv#util#line#sha(a:lastline), 0)[0] 2117 | endif 2118 | endif 2119 | let b:Gitv_Bisecting = 1 2120 | if g:Gitv_QuietBisect == 0 2121 | echom 'Bisect started' 2122 | endif 2123 | else 2124 | if !s:IsBisectingCurrentFiles() 2125 | echoerr "Not bisecting the current files, cannot enable." 2126 | return 2127 | endif 2128 | let b:Gitv_Bisecting = 1 2129 | if g:Gitv_QuietBisect == 0 2130 | echom 'Bisect enabled' 2131 | endif 2132 | endif 2133 | call s:LoadGitv('', 1, b:Gitv_CommitCount, b:Gitv_ExtraArgs, s:GetRelativeFilePath(), s:GetRange()) 2134 | endf "}}} 2135 | fu! s:BisectReset() "{{{ 2136 | if s:BisectIsEnabled() 2137 | let b:Gitv_Bisecting = 0 2138 | endif 2139 | if s:BisectHasStarted() 2140 | call s:RunGitCommand('bisect reset', 0) 2141 | if g:Gitv_QuietBisect == 0 2142 | echom 'Bisect stopped' 2143 | endif 2144 | else 2145 | if g:Gitv_QuietBisect == 0 2146 | echom 'Bisect disabled' 2147 | endif 2148 | endif 2149 | call s:LoadGitv('', 1, b:Gitv_CommitCount, b:Gitv_ExtraArgs, s:GetRelativeFilePath(), s:GetRange()) 2150 | endf "}}} 2151 | fu! s:BisectGoodBad(goodbad) range "{{{ 2152 | let goodbad = a:goodbad . ' ' 2153 | if s:BisectIsEnabled() && s:BisectHasStarted() 2154 | let result = '' 2155 | if a:firstline == a:lastline 2156 | let ref = gitv#util#line#sha('.') 2157 | let result = s:RunGitCommand('bisect ' . goodbad . ref, 0)[0] 2158 | if v:shell_error 2159 | echoerr split(result, '\n')[0] 2160 | return 2161 | endif 2162 | if g:Gitv_QuietBisect == 0 2163 | echom ref . ' marked as ' . a:goodbad 2164 | endif 2165 | else 2166 | let refs2 = gitv#util#line#sha(a:firstline) 2167 | let refs1 = gitv#util#line#sha(a:lastline) 2168 | let refs = refs1 . "^.." . refs2 2169 | let cmd = 'log --pretty=format:%h ' 2170 | let reflist = split(s:RunGitCommand(cmd . refs, 0)[0], '\n') 2171 | if v:shell_error 2172 | echoerr reflist[0] 2173 | return 2174 | endif 2175 | let errors = 0 2176 | for ref in reflist 2177 | let result = s:RunGitCommand('bisect ' . goodbad . ref, 0)[0] 2178 | if v:shell_error 2179 | echoerr split(result, '\n')[0] 2180 | errors += 1 2181 | endif 2182 | endfor 2183 | if g:Gitv_QuietBisect == 0 2184 | echom refs . ' commits marked as ' . a:goodbad 2185 | endif 2186 | if errors == len(reflist) 2187 | return 2188 | endif 2189 | endif 2190 | call s:LoadGitv('', 1, b:Gitv_CommitCount, b:Gitv_ExtraArgs, s:GetRelativeFilePath(), s:GetRange()) 2191 | endif 2192 | endf "}}} 2193 | fu! s:BisectSkip(mode) range "{{{ 2194 | if s:BisectIsEnabled() && s:BisectHasStarted() 2195 | if a:mode == 'n' 2196 | let loops = abs(v:count || 1) 2197 | let loop = 0 2198 | let errors = 0 2199 | while loop < loops 2200 | let result = s:RunGitCommand('bisect skip', 0)[0] 2201 | if v:shell_error 2202 | echoerr split(result, '\n')[0] 2203 | let errors += 1 2204 | endif 2205 | let loop += 1 2206 | endwhile 2207 | if g:Gitv_QuietBisect == 0 2208 | echom loop - errors . ' commits skipped' 2209 | endif 2210 | if errors == loops 2211 | return 2212 | endif 2213 | else "visual mode or no range 2214 | let cmd = 'bisect skip ' 2215 | let refs = gitv#util#line#sha(a:lastline) 2216 | if a:firstline != a:lastline 2217 | let refs2 = gitv#util#line#sha(a:firstline) 2218 | let refs .= "^.." . refs2 2219 | endif 2220 | let result = s:RunGitCommand('bisect skip ' . refs, 0)[0] 2221 | if v:shell_error 2222 | echoerr split(result, '\n')[0] 2223 | return 2224 | else 2225 | if g:Gitv_QuietBisect == 0 2226 | echom refs . 'skipped' 2227 | endif 2228 | endif 2229 | endif 2230 | call s:LoadGitv('', 1, b:Gitv_CommitCount, b:Gitv_ExtraArgs, s:GetRelativeFilePath(), s:GetRange()) 2231 | endif 2232 | endf "}}} 2233 | fu! s:BisectLog() "{{{ 2234 | if !s:BisectHasStarted() 2235 | return 2236 | endif 2237 | let fname = input('Enter a filename to save the log to: ', '', 'file') 2238 | let result = split(s:RunGitCommand('bisect log', 0)[0], '\n') 2239 | if v:shell_error 2240 | echoerr result[0] 2241 | return 2242 | endif 2243 | call writefile(result, fname) 2244 | endf "}}} 2245 | fu! s:BisectReplay() "{{{ 2246 | let fname = input('Enter a filename to replay: ', '', 'file') 2247 | let result = s:RunGitCommand('bisect replay ' . fname, 0)[0] 2248 | if v:shell_error 2249 | echoerr split(result, '\n')[0] 2250 | return 2251 | endif 2252 | let b:Gitv_Bisecting = 1 2253 | call s:LoadGitv('', 1, b:Gitv_CommitCount, b:Gitv_ExtraArgs, s:GetRelativeFilePath(), s:GetRange()) 2254 | endf "}}} }}} 2255 | fu! s:CheckOutGitvCommit() "{{{ 2256 | let allrefs = gitv#util#line#refs('.') 2257 | let sha = gitv#util#line#sha(line('.')) 2258 | if sha == "" 2259 | return 2260 | endif 2261 | 2262 | " Modify refs to create and checkout new local branch for remote-only branches 2263 | let newrefs = [] 2264 | for ref in allrefs 2265 | if (match(ref, '^r:') == 0) || (match(ref, '^refs/remotes/') == 0) 2266 | " Remove remote part 2267 | let ref = substitute(ref, '^r:\(\w\+\)/\(.*\)', '\2', '') 2268 | let ref = substitute(ref, '^refs\/remotes\/\(\w\+\)/\(.*\)', '\2', '') 2269 | if count(allrefs, ref) == 0 2270 | " Prevent dublicates 2271 | let newrefs += [ref] 2272 | endif 2273 | else 2274 | let newrefs += [ref] 2275 | endif 2276 | endfor 2277 | 2278 | let refs = newrefs + [sha] 2279 | let refstr = s:GetConfirmString(refs, 'Cancel') 2280 | let choice = confirm("Checkout commit:", refstr) 2281 | if choice == 0 2282 | return 2283 | endif 2284 | let choice = get(refs, choice-1, "") 2285 | if choice == "" 2286 | return 2287 | endif 2288 | let choice = substitute(choice, "^t:", "", "") 2289 | let choice = substitute(choice, "^r:", "", "") 2290 | if s:IsFileMode() 2291 | let relPath = s:GetRelativeFilePath() 2292 | let choice .= " -- " . relPath 2293 | endif 2294 | exec "Git checkout " . choice 2295 | endf "}}} 2296 | fu! s:CloseGitv() "{{{ 2297 | if s:IsFileMode() 2298 | q 2299 | else 2300 | "only tab: quit vim 2301 | if tabpagenr() == tabpagenr('$') && tabpagenr() == 1 2302 | qa 2303 | endif 2304 | 2305 | if g:Gitv_WipeAllOnClose 2306 | silent windo setlocal bufhidden=wipe 2307 | endif 2308 | let moveLeft = tabpagenr() == tabpagenr('$') ? 0 : 1 2309 | tabc 2310 | if moveLeft && tabpagenr() != 1 2311 | tabp 2312 | endif 2313 | endif 2314 | endf "}}} 2315 | fu! s:DiffGitvCommit() range "{{{ 2316 | if !s:IsFileMode() 2317 | echom "Diffing is not possible in browser mode." 2318 | return 2319 | endif 2320 | let shafirst = gitv#util#line#sha(a:firstline) 2321 | let shalast = gitv#util#line#sha(a:lastline) 2322 | if shafirst == "" || shalast == "" 2323 | return 2324 | endif 2325 | if a:firstline != a:lastline 2326 | call s:OpenRelativeFilePath(shafirst, "Gedit") 2327 | endif 2328 | call s:MoveIntoPreviewAndExecute("Gdiff " . shalast, a:firstline != a:lastline) 2329 | endf "}}} 2330 | fu! s:GetMergeArguments(from, to, verbose) "{{{ 2331 | if exists('g:Gitv_MergeArgs') && type(g:Gitv_MergeArgs) == 1 2332 | if a:verbose 2333 | echom "Merging '" . a:from . "' in to '" . a:to . "' with arguments '" . g:Gitv_MergeArgs . "'." 2334 | endif 2335 | " safely pad with spaces to avoid returning base value, put space before merge ref 2336 | return g:Gitv_MergeArgs . ' ' 2337 | endif 2338 | 2339 | let choices = "&Yes\n&No\n&Cancel" 2340 | let ff = confirm("Use fast-forward, if possible, to merge '". a:from . "' in to '" . a:to ."'?", choices) 2341 | if ff == 0 || ff == 3 | return "" | endif 2342 | let ff = ff == 1 ? ff : 0 2343 | 2344 | if a:verbose 2345 | if ff 2346 | echom "Merging '" . a:from . "' in to '" . a:to . "' with fast-forward." 2347 | else 2348 | echom "Merging '" . a:from . "' in to '" . a:to . "' without fast-forward." 2349 | endif 2350 | endif 2351 | 2352 | if ff 2353 | return '--ff ' 2354 | else 2355 | return '--no-ff ' 2356 | endif 2357 | endfu 2358 | fu! s:MergeBranches() range 2359 | if a:firstline == a:lastline 2360 | echom 'Already up to date.' 2361 | return 2362 | endif 2363 | let refs = gitv#util#line#refs(a:firstline) 2364 | let refs += gitv#util#line#refs(a:lastline) 2365 | call filter(refs, 'v:val !=? "HEAD"') 2366 | if len(refs) < 2 2367 | echom 'Not enough refs found to perform a merge.' 2368 | return 2369 | endif 2370 | let target = confirm("Choose target branch to merge into:", s:GetConfirmString(refs, "Cancel")) 2371 | if target == 0 || get(refs, target-1, '')=='' | return | endif 2372 | let target = remove(refs, target-1) 2373 | let target = substitute(target, "^[tr]:", "", "") 2374 | 2375 | let merge = confirm("Choose branch to merge in to '".target."':", s:GetConfirmString(refs, "Cancel")) 2376 | if merge == 0 || get(refs, merge-1, '')==''| return | endif 2377 | let merge = refs[merge-1] 2378 | let merge = substitute(merge, "^[tr]:", "", "") 2379 | 2380 | let mergeArgs = s:GetMergeArguments(merge, target, 1) 2381 | if mergeArgs == "" | return | endif 2382 | 2383 | call s:PerformMerge(target, merge, args) 2384 | endfu 2385 | fu! s:PerformMerge(target, mergeBranch, mergeArgs) abort 2386 | exec 'Git checkout ' . a:target 2387 | exec 'Git merge ' . a:mergeArgs . a:mergeBranch 2388 | 2389 | if g:Gitv_PromptToDeleteMergeBranch 2390 | let choices = "&Yes\n&No\n&Cancel" 2391 | let delBranch = confirm("Delete merge branch: '" . a:mergeBranch . "'?", choices) 2392 | if delBranch == 0 || delBranch == 3 | return | endif 2393 | let delBranch = delBranch == 1 ? delBranch : 0 2394 | if delBranch 2395 | exec 'Git branch -d ' . a:mergeBranch 2396 | endif 2397 | endif 2398 | endfu 2399 | fu! s:MergeToCurrent() 2400 | let refs = gitv#util#line#refs(".") 2401 | call filter(refs, 'v:val !=? "HEAD"') 2402 | if len(refs) < 1 2403 | echoerr 'No ref found to perform a merge.' 2404 | return 2405 | endif 2406 | let target = refs[0] 2407 | let target = substitute(target, "^[tr]:", "", "") 2408 | 2409 | let mergeArgs = s:GetMergeArguments(target, 'HEAD', 0) 2410 | if mergeArgs == "" | return | endif 2411 | 2412 | call s:PerformMerge("HEAD", target, mergeArgs) 2413 | endfu "}}} 2414 | fu! s:CherryPick() range "{{{ 2415 | let refs2 = gitv#util#line#sha(a:firstline) 2416 | let refs1 = gitv#util#line#sha(a:lastline) 2417 | if refs1 == refs2 2418 | let refs = refs1 2419 | else 2420 | let refs = refs1 . "^.." . refs2 2421 | endif 2422 | 2423 | echom "Cherry-Pick " . refs 2424 | exec 'Git cherry-pick ' . refs 2425 | endfu "}}} 2426 | fu! s:ResetBranch(mode) range "{{{ 2427 | let ref = gitv#util#line#sha(a:firstline) 2428 | 2429 | echom "Reset " . a:mode . " to " . ref 2430 | exec 'Git reset ' . a:mode . " " . ref 2431 | endfu "}}} 2432 | fu! s:Revert() range "{{{ 2433 | let refs2 = gitv#util#line#sha(a:firstline) 2434 | let refs1 = gitv#util#line#sha(a:lastline) 2435 | let refs = refs1 2436 | if refs1 != refs2 2437 | let refs = refs1 . "^.." . refs2 2438 | endif 2439 | 2440 | let mergearg = '' 2441 | let mergerefs = split(s:RunGitCommand('show ' . refs, 0)[0], '\n') 2442 | let mergerefs = split(matchstr(mergerefs, '^Merge:'))[1:] 2443 | if len(mergerefs) > 0 2444 | if refs1 != refs2 2445 | throw 'Cannot revert a range with a merge commit.' 2446 | return 2447 | endif 2448 | let mergearg = '-m 1' 2449 | endif 2450 | let cmd = 'revert --no-commit ' . mergearg . ' ' . refs 2451 | let result = s:RunGitCommand(cmd, 0)[0] 2452 | if result != '' 2453 | throw split(result, '\n')[0] 2454 | return 2455 | endif 2456 | exec 'Gcommit' 2457 | endfu "}}} 2458 | fu! s:DeleteRef() range "{{{ 2459 | let refs = gitv#util#line#refs(a:firstline) 2460 | call filter(refs, 'v:val !=? "HEAD"') 2461 | let choice = confirm("Choose branch to delete:", s:GetConfirmString(refs, "Cancel")) 2462 | if choice == 0 2463 | return 2464 | endif 2465 | let choice = get(refs, choice-1, "") 2466 | if choice == "" 2467 | return 2468 | endif 2469 | if match(choice, 'tag: .*') < 0 2470 | let command = "branch" 2471 | else 2472 | let command = "tag" 2473 | endif 2474 | let choice = substitute(choice, "^t:", "", "") 2475 | let choice = substitute(choice, "^r:", "", "") 2476 | let choice = substitute(choice, "^tag: t:", "", "") 2477 | if s:IsFileMode() 2478 | let relPath = s:GetRelativeFilePath() 2479 | let choice .= " -- " . relPath 2480 | endif 2481 | echom "Delete " . command . " " . choice 2482 | exec 'Git ' . command . " -d " . choice 2483 | endfu "}}} 2484 | fu! s:StatGitvCommit() range "{{{ 2485 | let shafirst = gitv#util#line#sha(a:firstline) 2486 | let shalast = gitv#util#line#sha(a:lastline) 2487 | if shafirst == "" || shalast == "" 2488 | return 2489 | endif 2490 | let cmd = 'diff --no-color '.shafirst 2491 | if shafirst != shalast 2492 | let cmd .= ' '.shalast 2493 | endif 2494 | let cmd .= ' --stat' 2495 | let cmd = "call s:SetupStatBuffer('".cmd."')" 2496 | if s:IsFileMode() 2497 | exec cmd 2498 | else 2499 | call s:MoveIntoPreviewAndExecute(cmd, 1) 2500 | endif 2501 | endf 2502 | fu! s:SetupStatBuffer(cmd) 2503 | silent let res = Gitv_OpenGitCommand(a:cmd, s:IsFileMode()?'vnew':'') 2504 | if res 2505 | silent set filetype=gitv 2506 | endif 2507 | endfu "}}} }}} 2508 | "Movement: "{{{ 2509 | fu! s:JumpToBranch(backward) "{{{ 2510 | if a:backward 2511 | silent! ?|/\||\\?-1 2512 | else 2513 | silent! /|\\\||\//+1 2514 | endif 2515 | endf "}}} 2516 | fu! s:JumpToRef(backward) "{{{ 2517 | if a:backward 2518 | silent! ?^\(\(|\|\/\|\\\|\*\)\s\=\)\+\s\+\zs( 2519 | else 2520 | silent! /^\(\(|\|\/\|\\\|\*\)\s\?\)\+\s\+\zs(/ 2521 | endif 2522 | endf "}}} 2523 | fu! s:JumpToHead() "{{{ 2524 | silent! /^\(\(|\|\/\|\\\|\*\)\s\?\)\+\s\+\zs(HEAD/ 2525 | endf "}}} 2526 | fu! s:JumpToCommit(backwards) "{{{ 2527 | let flags = 'W' 2528 | if a:backwards 2529 | let flags .= 'b' 2530 | endif 2531 | 2532 | let c = v:count1 2533 | while c > 0 2534 | let c-=1 2535 | call search( '^[|\/\\ ]*\zs\*', flags ) 2536 | endwhile 2537 | 2538 | redraw 2539 | call s:OpenGitvCommit("Gedit", 0) 2540 | endf "}}} 2541 | fu! s:JumpToParent() "{{{ 2542 | let sha = gitv#util#line#sha(line('.')) 2543 | if sha == "" 2544 | return 2545 | endif 2546 | let parent = s:GetParentSha(sha, v:count1 ) 2547 | if parent == "" 2548 | echom 'Parent '.v:count1.' is out of range' 2549 | return 2550 | endif 2551 | while !search( '^\ze.*\['.parent.'\]$', 'Ws' ) 2552 | call s:LoadGitv('', 1, b:Gitv_CommitCount+g:Gitv_CommitStep, b:Gitv_ExtraArgs, s:GetRelativeFilePath(), s:GetRange()) 2553 | endwhile 2554 | redraw 2555 | endf "}}} 2556 | "}}} }}} 2557 | "Align And Truncate Functions: "{{{ 2558 | if exists("*strwidth") "{{{ 2559 | "introduced in Vim 7.3 2560 | fu! s:StringWidth(string) 2561 | return strwidth(a:string) 2562 | endfu 2563 | else 2564 | fu! s:StringWidth(string) 2565 | return len(split(a:string,'\zs')) 2566 | endfu 2567 | end "}}} 2568 | if exists("*strdisplaywidth") "{{{ 2569 | fu! s:StringDisplayWidth(string) 2570 | return strdisplaywidth(a:string) 2571 | endfu 2572 | else 2573 | fu! s:StringDisplayWidth(string) 2574 | return s:StrWidth(a:string,'\zs') 2575 | endfu 2576 | end "}}} 2577 | fu! s:Align(seperator, filePath) range "{{{ 2578 | let lines = getline(a:firstline, a:lastline) 2579 | call map(lines, 'split(v:val, a:seperator)') 2580 | 2581 | let newlines = copy(lines) 2582 | call filter(newlines, 'len(v:val)>1') 2583 | let maxLens = s:MaxLengths(newlines) 2584 | 2585 | let newlines = [] 2586 | for tokens in lines 2587 | if len(tokens)>1 2588 | let newline = [] 2589 | for i in range(len(tokens)) 2590 | let token = tokens[i] 2591 | call add(newline, token . repeat(' ', maxLens[i]-s:StringWidth(token)+1)) 2592 | endfor 2593 | call add(newlines, newline) 2594 | else 2595 | call add(newlines, tokens) 2596 | endif 2597 | endfor 2598 | 2599 | if g:Gitv_TruncateCommitSubjects 2600 | call s:TruncateLines(newlines, a:filePath) 2601 | endif 2602 | 2603 | call map(newlines, "join(v:val)") 2604 | call setline(a:firstline, newlines) 2605 | endfu "}}} 2606 | fu! s:TruncateLines(lines, filePath) "{{{ 2607 | "truncates the commit subject for any line > &columns 2608 | call map(a:lines, "s:TruncateHelp(v:val, a:filePath)") 2609 | endfu "}}} 2610 | fu! s:TruncateWideString(string, target) "{{{ 2611 | if !exists('*strdisplaywidth') 2612 | return a:string 2613 | endif 2614 | let newString = a:string 2615 | while strdisplaywidth(newString) > a:target 2616 | let newString = newString[0:-2] 2617 | endwhile 2618 | while strdisplaywidth(newString) < a:target 2619 | let newString .= ' ' 2620 | endwhile 2621 | return newString 2622 | endfu "}}} 2623 | fu! s:TruncateHelp(line, filePath) "{{{ 2624 | let length = s:StringWidth(join(a:line)) 2625 | let displayLength = s:StringDisplayWidth(join(a:line)) 2626 | let maxWidth = s:IsHorizontal() ? &columns : &columns/2 2627 | let maxWidth = a:filePath != '' ? winwidth(0) : maxWidth 2628 | if displayLength > maxWidth 2629 | let delta = displayLength - maxWidth 2630 | "offset = 3 for the elipsis and 1 for truncation 2631 | let offset = 3 + 1 2632 | if a:line[0][-(delta + offset + 1):] =~ "^\\s\\+$" 2633 | let extension = " " 2634 | else 2635 | let extension = "..." 2636 | endif 2637 | if length > maxWidth 2638 | let a:line[0] = a:line[0][:-(delta + offset)] 2639 | endif 2640 | let target = maxWidth - offset - s:StringWidth(join(a:line[1:-1])) 2641 | let a:line[0] = s:TruncateWideString(a:line[0], target) 2642 | let a:line[0] = a:line[0] . extension 2643 | endif 2644 | return a:line 2645 | endfu "}}} 2646 | fu! s:MaxLengths(colls) "{{{ 2647 | "precondition: coll is a list of lists of strings -- should be rectangular 2648 | "returns a list of maximum string lengths 2649 | let lengths = [] 2650 | for x in a:colls 2651 | for y in range(len(x)) 2652 | let length = s:StringWidth(x[y]) 2653 | if length > get(lengths, y, 0) 2654 | if len(lengths)-1 < y 2655 | call add(lengths, length) 2656 | else 2657 | let lengths[y] = length 2658 | endif 2659 | endif 2660 | endfor 2661 | endfor 2662 | return lengths 2663 | endfu "}}} }}} 2664 | "Fugitive Functions: "{{{ 2665 | " Returns an array with script number and path 2666 | fu! s:GetFugitiveInfo() "{{{ 2667 | redir => scriptnames 2668 | silent! scriptnames 2669 | redir END 2670 | for script in split(l:scriptnames, "\n") 2671 | if l:script =~ 'fugitive' 2672 | let info = split(l:script, ":") 2673 | " Parse the script number 2674 | let info[0] = str2nr(info[0]) 2675 | " Parse the script path 2676 | let info[1] = expand(info[1][1:]) 2677 | return info 2678 | endif 2679 | endfor 2680 | throw 'Unable to find fugitive' 2681 | endfu "}}} 2682 | fu! s:GetFugitiveSid() "{{{ 2683 | return s:GetFugitiveInfo()[0] 2684 | endfu "}}} }}} 2685 | 2686 | let &cpo = s:savecpo 2687 | unlet s:savecpo 2688 | 2689 | " vim:set et sw=4 ts=4 fdm=marker: 2690 | -------------------------------------------------------------------------------- /syntax/gitv.vim: -------------------------------------------------------------------------------- 1 | " Vim syntax file 2 | "LANGUAGE: Custom git log output 3 | "AUTHOR: Greg Sexton 4 | "MAINTAINER: Roger Bongers 5 | "WEBSITE: http://www.gregsexton.org/portfolio/gitv/ 6 | "LICENSE: Same terms as Vim itself (see :help license). 7 | 8 | if exists("b:current_syntax") 9 | finish 10 | endif 11 | 12 | "set conceallevel=2 13 | "set concealcursor=n 14 | 15 | syn match gitvSubject /.*/ 16 | 17 | syn match gitvRebaseTag /\[[sfper]x\?\]/ contained containedin=gitvSubject 18 | syn match gitvDate /\(\d\+ years\?, \)\?\d\+ \%(second\|minute\|hour\|day\|week\|month\|year\)s\? ago/ contained containedin=gitvSubject 19 | syn match gitvHash /\[[0-9a-f]\{7,9}\]$/ contained containedin=gitvSubject 20 | 21 | syn match gitvGraphEdge9 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge0,gitvRef,gitvSubject skipwhite 22 | syn match gitvGraphEdge8 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge9,gitvRef,gitvSubject skipwhite 23 | syn match gitvGraphEdge7 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge8,gitvRef,gitvSubject skipwhite 24 | syn match gitvGraphEdge6 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge7,gitvRef,gitvSubject skipwhite 25 | syn match gitvGraphEdge5 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge6,gitvRef,gitvSubject skipwhite 26 | syn match gitvGraphEdge4 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge5,gitvRef,gitvSubject skipwhite 27 | syn match gitvGraphEdge3 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge4,gitvRef,gitvSubject skipwhite 28 | syn match gitvGraphEdge2 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge3,gitvRef,gitvSubject skipwhite 29 | syn match gitvGraphEdge1 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge2,gitvRef,gitvSubject skipwhite 30 | syn match gitvGraphEdge0 /_\?\(|\|-\(-\|\.\)\|\/|\?\|\\\|\*\|+\|=\)\s\?/ nextgroup=gitvGraphEdge1,gitvRef,gitvSubject skipwhite 31 | syn match gitvGraphEdgeH /_/ contained containedin=gitvGraphEdge0,gitvGraphEdge1,gitvGraphEdge2,gitvGraphEdge3,gitvGraphEdge4,gitvGraphEdge5,gitvGraphEdge6,gitvGraphEdge7,gitvGraphEdge8,gitvGraphEdge9 32 | 33 | syn match gitvRef /\s*(.\{-})/ nextgroup=gitvSubject skipwhite 34 | syn match gitvRefTag /t:\zs.\{-}\ze\(, \|)\)/ contained containedin=gitvRef 35 | syn match gitvRefRemote /r:\zs.\{-}\ze\(, \|)\)/ contained containedin=gitvRef 36 | syn match gitvRefHead /HEAD/ contained containedin=gitvRef 37 | 38 | syn match gitvLoadMore /^-- Load More --$/ 39 | syn match gitvWorkingCopy /^-- \[.*\] --$/ 40 | 41 | syn match gitvRange /^-- Showing .* in the range:$/ 42 | syn match gitvRangeFromTo /^-- \/.*\/$/ 43 | 44 | syn match gitvLocalUncommit /Local uncommitted changes, not checked in to index\.$/ contained containedin=gitvSubject 45 | syn match gitvLocalCommited /Local changes checked in to index but not committed\.$/ contained containedin=gitvSubject 46 | syn match gitvLocalCommitedNode /+/ contained containedin=gitvGraphEdge0,gitvGraphEdge1,gitvGraphEdge2,gitvGraphEdge3,gitvGraphEdge4,gitvGraphEdge5,gitvGraphEdge6,gitvGraphEdge7,gitvGraphEdge8,gitvGraphEdge9 47 | syn match gitvLocalUncommitNode /=/ contained containedin=gitvGraphEdge0,gitvGraphEdge1,gitvGraphEdge2,gitvGraphEdge3,gitvGraphEdge4,gitvGraphEdge5,gitvGraphEdge6,gitvGraphEdge7,gitvGraphEdge8,gitvGraphEdge9 48 | 49 | syn match gitvAddedMarks /|\s\+\d\+ \zs+*-*\ze$/ contained containedin=gitvSubject 50 | syn match gitvAddedMarks /|\s\+Bin \zs\d\+ -> \d\+\ze bytes$/ contained containedin=gitvSubject 51 | syn match gitvRemovedMarks /-*$/ contained containedin=gitvAddedMarks 52 | syn match gitvRemovedMarks /\d\+\ze ->/ contained containedin=gitvAddedMarks 53 | syn match gitvSeperatorMarks /\s\+->\s\+/ contained containedin=gitvAddedMarks 54 | 55 | hi def link gitvHash Number 56 | hi def link gitvRebaseTag Identifier 57 | hi def link gitvRef Directory 58 | hi def link gitvRefTag String 59 | hi def link gitvRefRemote Statement 60 | hi def link gitvRefHead Special 61 | hi def link gitvDate Statement 62 | hi def link gitvSubject Normal 63 | hi def link gitvLoadMore Question 64 | hi def link gitvWorkingCopy Question 65 | hi def link gitvRange ModeMsg 66 | hi def link gitvRangeFromTo Function 67 | 68 | hi def link gitvAddedMarks diffAdded 69 | hi def link gitvRemovedMarks diffRemoved 70 | hi def link gitvSeperatorMarks Normal 71 | 72 | hi def link gitvGraphEdge0 Delimiter 73 | 74 | if &background == "dark" 75 | highlight default gitvGraphEdge1 ctermfg=magenta guifg=green1 76 | highlight default gitvGraphEdge2 ctermfg=green guifg=yellow1 77 | highlight default gitvGraphEdge3 ctermfg=yellow guifg=orange1 78 | highlight default gitvGraphEdge4 ctermfg=cyan guifg=greenyellow 79 | highlight default gitvGraphEdge5 ctermfg=red guifg=springgreen1 80 | highlight default gitvGraphEdge6 ctermfg=yellow guifg=cyan1 81 | highlight default gitvGraphEdge7 ctermfg=green guifg=slateblue1 82 | highlight default gitvGraphEdge8 ctermfg=cyan guifg=magenta1 83 | highlight default gitvGraphEdge9 ctermfg=magenta guifg=purple1 84 | else 85 | highlight default gitvGraphEdge1 ctermfg=darkyellow guifg=orangered3 86 | highlight default gitvGraphEdge2 ctermfg=darkgreen guifg=orange2 87 | highlight default gitvGraphEdge3 ctermfg=blue guifg=yellow3 88 | highlight default gitvGraphEdge4 ctermfg=darkmagenta guifg=olivedrab4 89 | highlight default gitvGraphEdge5 ctermfg=red guifg=green4 90 | highlight default gitvGraphEdge6 ctermfg=darkyellow guifg=paleturquoise3 91 | highlight default gitvGraphEdge7 ctermfg=darkgreen guifg=deepskyblue4 92 | highlight default gitvGraphEdge8 ctermfg=blue guifg=darkslateblue 93 | highlight default gitvGraphEdge9 ctermfg=darkmagenta guifg=darkviolet 94 | endif 95 | 96 | highlight default gitvLocalCommitedNode ctermfg=green guifg=green 97 | highlight default gitvLocalUncommitNode ctermfg=red guifg=red 98 | highlight default gitvLocalCommited gui=bold 99 | highlight default gitvLocalUncommit gui=bold 100 | 101 | let b:current_syntax = "gitv" 102 | --------------------------------------------------------------------------------