Your preferred workflow will of course direct your choice of which tools to use, but what I find interesting is how the tools themselves may reversely influence the way you work. Most developers are quite familiar with the git CLI, and will have no desire for pulling git into their editor of choice. What I have found, though, is that having git at my fingertips while I edit code has changed the way I write code for the better.
In this article I will present some excellent git-related Vim plugins which makes version controlling your code a breeze, so much so that version controlling your code becomes productive in itself, even ignoring all the traditional benefits of using git.
Effortlessly moving between regions of interest in source files is a really central part of maintaining flow while writing code, and many tools are available for this purpose in vim:
C-u, and so on.
But I have always found marks a bit jarring to use in practice, as you have to set the marks before moving away and then having to remember those marks later. Marks do serve a really important purpose though, especially when you are jumping between two sections in the same file repeatedly. This is where the vim-gitgutter plugin comes to the rescue.
Even though many install the plugin for aesthetic purposes, where the plugin actually really shines is the way it enables you to quickly jump between “modified git chunks” and to operate upon those chunks. It is this aspect of the plugin I want to focus on in this article, ending with some interesting observations about how this plugin permanently changed my development workflow for the better, and how it may do the same for you.
Start by installing the plug-in with your vim plug-in manager of choice. Here I’m using vim-plug:
You can modify the signs used in the sign column like so:
" Use fontawesome icons as signs let g:gitgutter_sign_added = '+' let g:gitgutter_sign_modified = '>' let g:gitgutter_sign_removed = '-' let g:gitgutter_sign_removed_first_line = '^' let g:gitgutter_sign_modified_removed = '<'
If you are using a UTF-8 supporting terminal emulator and a patched font, then you can use more fancy icons if you wish. I use the kitty terminal emulator with a patched version of the DejaVu Sans Code font type from Nerd Fonts. The only difference is that the gutter signs are Font Awesome icons with thicker strokes and somewhat rounded corners. You can find my exact glyphs here if you wish to try it out yourself.
If you prefer a simpler look, I also recommend setting the background color of the sign column to your general background color:
let g:gitgutter_override_sign_column_highlight = 1 highlight SignColumn guibg=bg highlight SignColumn ctermbg=bg
The final results with the gruvbox color scheme looks like this:
The different signs might be somewhat cryptic at first sight, but here are what they indicate (from top to bottom):
Changing vim’s option for
updatetime from the default of 4000 to ~100 makes the gutter signs appear more responsive:
" Update sign column every quarter second set updatetime=250
Play the video below too see the signs being updated during an edit-session:
Now that the aesthetics are out of the way, let’s focus on navigating between chunks.
You can jump between modified chunks with the
GitGutterPrevHunk <Plug> bindings.
" Jump between hunks nmap <Leader>gn <Plug>GitGutterNextHunk " git next nmap <Leader>gp <Plug>GitGutterPrevHunk " git previous
Now when you start editing your source code, you will quickly realize that most editing sessions are restricted to just a few sub-sections of your source code, most of which you will jump between frequently. Without putting any effort into it, you end up placing implicit markers all over your source code in the form of git diffs. Using our previously set key-bindings, we can easily jump between these modified chunks:
You can also add a binding for staging individual chunks with
GitGutterStageHunk and reverting a modified chunk back to
git HEAD with
" Hunk-add and hunk-revert for chunk staging nmap <Leader>ga <Plug>GitGutterStageHunk " git add (chunk) nmap <Leader>gu <Plug>GitGutterUndoHunk " git undo (chunk)
I recommend setting these key-bindings to something can be easily succeeded by the key-bindings we made earlier. That way you can jump from chunk to chunk, undoing and staging chunks as needed. We have now completely replaced the need for git’s (cumbersome?) interactive staging functionality, all without leaving our editor at any point in time.
What I find really interesting is how this plugin has changed my workflow for the better. Some changes I have seen are:
git add -aor similar blunders which result in bad code being pushed to a remote repository.
Now, vim-gitgutter works really well for one source file at a time, but project-wide git management is still something we need to handle within Vim. Jreybert’s awesome vimagit plugin offers most of the remaining git CLI-equivalent functionality The somewhat young plugin, relative to Vim’s age that is, was inspired by the excellent Magit plugin for Emacs and is already quite feature complete. We start by installing it:
Magit allows you to open a separate git buffer with
:Magit, let’s start by binding this command.
" Open vimagit pane nnoremap <leader>gs :Magit<CR> " git status
Vimagit’s buffers has modes which will quickly become familiar to most Vim-users.
You can browse through chunks with
<C-n, stage the current chunk with
S, and enter commit mode with
CA for making an amending commit.
Once you have written your commit message in commit mode, “write” the buffer with your preferred save command and you’re done!
One of my favorite features is the
E binding, which will open the modified/staged chunk in your other pane.
Here it is in action:
Here we have another benefit of tightly integrating git into vim: no more
git status or
git diff followed by painstakingly opening your text editor and navigating to the file(s) of interest in the CLI output.
There are a whole lot of additional bindings which I wont go into detail here, but all of theme are easily available in the git buffer by pressing
If you want to quickly check out the available functionality without having to install the plugin, you can also click the following header which summarizes all the bindings (source: vimagit’s documentation).
||If cursor on filename header line, unhide diffs for this file.|
||From stage mode: set commit mode in normal flavor.
From commit mode: commit all staged changes with commit flavor (normal or amend) with message in “Commit message” section
||From stage or commit mode: set commit mode in amend flavor, and display “Commit message” section with previous commit message.|
||From stage mode: amend staged changes to previous commit without modifying the previous commit message.|
||Commit undo, cancel and close current commit message.|
||Refresh magit buffer.|
||shrink,enlarge,reset diff context|
||Close magit buffer.|
||Toggle help showing in magit buffer.|
||If cursor on filename header, unstage file.
If cursor in hunk, unstage hunk.
If visual selection in hunk (with v), unstage selection.
If lines marked in hunk (with M), unstage marked lines.
||Unstage the line under the cursor.|
||If cursor in hunk, mark line under cursor “to be unstaged”.
If visual selection in hunk (with v), mark selected lines “to be unstaged”
||If cursor on filename header or hunk, unstage whole file.|
||Edit, jump cursor to file containing this hunk.|
||Move to Next/Previous hunk in magit buffer|
||If cursor on filename header, stage file.
If cursor in hunk, stage hunk.
If visual selection in hunk (with v), stage selection.
If lines marked in hunk (with M), stage marked lines.
||Stage the line under the cursor.|
||If cursor in hunk, mark line under cursor “to be staged”.
If visual selection in hunk (with v), mark selected lines “to be staged”.
||If cursor on filename header or hunk, stage whole file.|
||Edit, jump cursor to file containing this hunk.|
||Move to Next/Previous hunk in magit buffer.|
||Discard file changes (warning, changes will be lost).|
||Add file in .gitgnore.|
What you may have noticed, is that
git push support is missing (it is in the pipeline), but we can easily fix this with a custom binding:
" Push to remote nnoremap <leader>gP :! git push<CR> " git Push
Once you have gotten used to vimagit’s interface and wont accidentally trigger bindings anymore, I recommend configuring vimagit such that it allows you to delete untracked files directly from the git buffer:
" Enable deletion of untracked files in Magit let g:magit_discard_untracked_do_delete=1
Lastly we have the vim-fugitive plugin written by the prolific Tim Pope.
This plugin offers a lot of functionality that has already been offered by the two previous plugins, so I will focus on the unique features which I use.
Start by installing
vim-fugitive and the GitHub extension
Plug 'tpope/vim-fugitive' Plug 'tpope/vim-rhubarb'
Vim-fugitive allows you to
git blame your current buffer without leaving vim.
You can then take a closer look at the commit of interest if you want to.
Let’s bind this command:
" Show commits for every source line nnoremap <Leader>gb :Gblame<CR> " git blame
In practice it looks like this:
Vim-rhubarb allows you to open the current line(s) as visually selected lines on GitHub:
" Open current line in the browser nnoremap <Leader>gb :.Gbrowse<CR> " Open visual selection in the browser vnoremap <Leader>gb :Gbrowse<CR>
This is great for quickly referencing some version controlled lines to your colleagues. We can also bind a command for staging the entire file like so:
" Add the entire file to the staging area nnoremap <Leader>gaf :Gw<CR> " git add file
I still recommend not using this binding though, since inline partial staging is a great QA measure as mentioned before. You can read the complete documentation for vim-fugitive here if you are interested.
I hope that I have convinced some of you that there are certain intrinsic benefits of integrating git into your editor-of-choice, no matter how familiar you may be with git’s command line interface from before. Thanks for reading!
Comments powered by Talkyard.