Fugitive, the git
wrapper so awesome, it should be illegal.
That’s at least what states the github page. And it is indeed
awesome and I’m glad it is not illegal. I do most of my work in vim and
fugitive is my go-to wrapper for git
in vim.
Why would you want to use git
inside vim in the first place ? The command line
interface is fine.
Well, most of my work consists in software development. I carefully craft my commits to make them atomic and to keep the code history clean. My workflow consists in reviewing every changes before staging and committing them. During the review I often find myself editing the changes. Once there are changes they need to be at least compiled and sometimes even tested.
With fugitive I can do this without quitting vim (who knowns how to do that anyway ?). Here I’ll present most of the important features of fugitive and how I use them.
Basics
git status
is available with the command :Git
. It opens a new window with
all the information returned by git status
. Quite a few commands can be
started from this window, I will described the most important in the following
sections.
By default the arguments of the :Git
commands if they are not handled by
fugitive itself are sent to the git
program. For example, one can create
and checkout a new branch with :Git checkout -b bug-fix-1234
or fetch the
latest changes in the remote with :Git fetch
.
Viewing current editing changes
Let’s say that I currently edit a file tracked by git
. I want to see the
changes I made. I can see them in diff mode in a split view with:
:Gvdiffsplit
for a vertical split diff view:Ghdiffsplit
for a horizontal split diff view:Gdiffsplit
for a vertical or horizontal split depending on the current window ratio
Now I can browse and edit the changes. Every shortcuts from diff mode are of course available here. I use mostly:
do
to obtain the changes from the other windowdp
to put the changes to the other window
Some other interesting commands when editing files tracked by git
are:
:Gread
to revert the local changes:Gwrite
to stage the file for the next commit
Change review and commit
Now I have done a bunch changes I want to commit. I can get the git status
of
my working directory with :Git
.
A window appears in an horizontal split showing usually three sections:
- The list of untracked files
- The list of modified files tracked by
git
in the section unstaged - The list of staged files
I can put the cursor on each of these files and toggle the staged/unstaged
state with the key -
. Hitting X
reverts a changed file for the version in
the repository.
Once I’m done I can hit:
cc
to commit staged changesca
to amend the latest commit
Then I’m presented a new buffer where I can write the commit message. Once
saved and closed the commit is added to the git
database. That’s the most
basic workflow, it assumes that I’m happy with the changes I made which is
rarely the case.
Now before committing my changes, I want to review every single hunk to make sure that they are all relevant for the commit I want to add.
I can put my cursor on an unstaged file and hit:
dv
for a vertical split diff viewdh
for a horizontal split diff viewdd
for a vertical or horizontal split depending on the current window ratio
Then I can review my changes and make the appropriate modifications if needed.
Split views are very powerful. It is even possible to stage individual hunks editing the version of the file in the index.
For example, let’s say I have changed a file. I open a vertical diff. On the
left I can see the version in the repository, on the right I have the version I
have edited. The buffer on the left is editable and represents what it is about
to be staged. Practically it means that being in my version on the right if I
go to the first change, I hit dp
it will put my changes in the buffer on the
left. When I save this buffer, that change will be staged.
That’s great, that means that I don’t have to delete all temporary statement in my code before committing them.
Browsing logs
Browsing logs can be achieved with the command :Git log
. It opens a buffer
showing the commits in a reverse chronological order such as the latest
commits are located at the top.
Hitting the Enter
key on any ref in the log opens the corresponding commit.
The commit buffer shows the commit message, its author and the changes it
contains as a diff. Well, viewing diff is not very helpful, we usually need to
see the changes in the context of a file. It is fortunately possible to do it
with fugitive.
This is how I like to do it:
:set foldmethod=syntax
folds the diff under each modified filename. Great, now I can see the list of files changed by the commit.zo
zc
open/close the diff on each filename. But there is better.O
on a file opens a new tab with a diff view of the changes:tabclose
I’m back to the commit view and I can choose another file to view
Viewing the commits of a file
A special case of the previous workflow is to look at the log of the current
file. This can be done with :Git log %
.
Browsing through the history of a file
Another variation would be to quickly browse the different versions of the
current file. :0Gllog
does just that by filling the location list with those
versions. Browsing through them is possible with the usual :lnext
:lprevious
. Then call :lclose
when you are done.
Resolving conflicts
In case of a conflict during a merge or a rebase operation, fugitive can present a three panes view with:
- on the left the target branch: where you want to merge i.e. HEAD
- in the middle the version to reconcile. This is the one which contains the
markers you need to get rid of:
=======
,>>>>>>>
and<<<<<<<
. - on the right the branch you want to merge
Just resolve the conflicting hunks one after another, save the file, stage it,
then move on with :Git merge --continue
or :Git rebase --continue
depending
on the case.
Other cool features
Interactive rebase
A feature I use a lot is git
interactive rebase. It’s a great tool to clean
up the commits in a branch before merging them. That can also be done in
fugitive.
In a git
log view, just hit ri
on any commit to start an interactive rebase
from this commit to the current one. Alternatively you can also start it with
:Git rebase -i anyref
.
Then you’ll be presented the window listing the commits and a short message describing what you can do with the commit.
git blame
git blame
is available in two versions in current fugitive.
The first one can be triggered with :Git blame
It opens a window on the left
showing the last editor of the current line. One can blame the previous version
with ~
on any commit or view the commit with the Enter
key.
One problem with that workflow tho. It is that there is no easy way to get back
to the previous view. Indeed the two windows need to be updated so CTRL-o
is
not of any help here.
A workaround is to use :Git blame %
. It opens a buffer with the blame message
in front of each line in a single window. An issue is that the syntax highlight
doesn’t work anymore but now it is possible to blame the previous version with
~
and move in the location list as usual with CTRL-o
CTRL-i
.
Viewing the history of a range
In the same vein as here, it is
possible to load all the versions that has affected a specific range of lines
in a file with: :[range] Gllog
. This one is incredibly useful to diagnostic a
bug or a regression.
Getting back to the current editing file
When browsing the git
history, it’s easy to get lost after a while. You can
get back to the current version of the current file any time with :Gedit
.
Viewing the commit the current file belongs to
Another handy trick is to reach the last commit that has touched the current
file: :Gedit !
.
Conclusion
I hope that these use case descriptions have made the demonstration that there
is no point using git
command line or reaching for another tool when editing
git
tracked files in vim.
And it only scratches the surface, feel free to start reading the great :help fugitive
and be thankful to Tim Pope for allowing
us not to have to quit vim when in need for git
!