Git is one of the most widely used tools of the modern programmer, and vim is one of the classic editors. Programmers choose git and vim because they each give tremendous value in working with files, including both data and source code.
Vim (and nvim) are extensible through plugins, and because of their long history as favored programmer’s tools, many programmers actively contribute to their large, rich plugin ecosystems. And so, many programmers use a large set of plugins. Therefore, since vim 8, vim built in basic functionality to e.g. load plugins… but there are plugin managers which further automate installation and updating of plugins and their dependencies.
We will be going over the fugitive vim plugin, which gives the programmer the ability to work with git directly from vim, and adds many minor and several major enhancements to this workflow. We’ll go over installing it, and a sample workflow to learn its key features.
Step 1: Installation
Installing vim-plug, fugitive and git-gutter
We’ll use vim-plug which we can install for Linux from the bash command line as follows (other platforms are also simple, follow the step on the page, and of course if you’re already using a plugin manager then adapt the below as needed!):
1
2
curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
Then we’d add the below block to our startup file, for example .vimrc or .vim/vimrc. Since we’re editing the file, we’ll also add a second git-related plugin called gitgutter, which adds a column (the “gutter”) and shows the current line’s status in git, but you can skip gitgutter, although this is a plug (I’ll be here all week).
1
2
3
4
Plug 'tpope/vim-fugitive'
" optional to add gitgitter, but highly recommend
Plug 'airblade/vim-gitgutter'
call plug#end()
Next we save and quit :wq
, reopen vim, and give the command
1
:PlugInstall
We’ll see progress bars of vim-plug busily downloading and installing the plugins. Once finished, quit vim, and we’re going to create a repo in which we’ll start exploring fugitive!
Step 2: Setting up a repo
Lets start a new vim session and make a directory using the
shell :!mkdir gitexamp
, and then change into it with
:cd gitexamp
.
We can now give our first fugitive command :Git
and we’ll see
it respond with
1
fugitive: working directory does not belong to a Git repository
Initializing git repo
To initialize the git repo, we simply give :Git init
and we see
the usual text from this command. Now when we type the
main :Git
, fugitive splits the screen and places us in its
summary:
1 Head: master
2 Push: origin/master
3 Help: g?
~
~
~
~
~
~
1__fugitive___
1
~
~
~
~
~
~
~
2__N______No_Name__________________________________100____0___1
:Git
Fugitive shows us the basic information of the current head and push target.
Adding a file
So we can next use the lower pane to save some text into
a file we’ll name file1.txt
.
Then we can give the command
:Git add %
which lets us add the file to git using vim’s
abbreviation %
(this is our first time benefitting from
fugitive!) When we do this, fugitive updates the :Git summary to:
1 Head: master
2 Push: origin/master
3 Help: g?
4
5 Staged (1)
6 A gitexamp/file1.txt
~
~
~
1__fugitive___
1 this is what we'll put in file1.txt, then we'll :saveas file1.txt
~
~
~
~
~
~
~
2__N_____file1_txt__________________tex_____11W___100____1___38
So fugitive is showing us the staged area of git, which now contains the file1.txt we added.
Commiting a file
We can commit the change with
:G commit
(abbreviating :Git
to :G
). fugitive splits the
screen again and gives a pane where we can add the commit message
and see the files simulaneously. Seeing the files helps us write a commit
message, especially when we’re commiting a large number of files.
1 this is the first commit, and there's only 1 file!
2 # Please enter the commit message for your changes. Lines starting
3 # with '#' will be ignored, and an empty message aborts the commit.
4 #
5 # On branch master
6 #
1___git_COMMIT_EDITMSG_________________________________git_____8____1_120___50
1 Head: master
2 Push: origin/master
3 Help: g?
4
5 Staged (1)
6 A file1.txt
~
2__fugitive___
1 this is what we'll put in file1.txt, then we'll :saveas file1.txt
~
~
3__file1_txt____________________________________tex_____11W___100____1_10___65
"file1.txt" 1L, 66B written
When we :wq
the commit message, we see the commit information,
and then the fugitive summary goes back to an empty state since
there’s no longer any files in the staging area.
1 Head: master
2 Push: origin/master
3 Help: g?
~
~
~
~
~
~
1__fugitive___
1 this is what we'll put in file1.txt, then we'll :saveas file1.txt
~
~
~
~
~
~
~
2__file1_txt_______________________tex_____11_words___100____1_10___55
Step 3: :Git summary, make a commit history, and diffs
Next we’ll change and commit file1.txt a few times. First lets add a second line
add this second line, then :w
Next we refocus on the :Git summary with <C-w>k
, and it updates and now
shows file1.txt has unstaged changes. Furthermore,
gitgutter (if installed) shows a +
next to the line we added.
The :Git summary is interactive!
The :Git summary is not just a print out however. Let’s
move our cursor over file1.txt and press -
. The :Git
summary will update, showing file1.txt as staged, and
gitgutter will likewise update and no longer show a
difference between the working and staged versions.
And pressing -
repeatedly will switch file1.txt from
staged to unstaged.
But this is just the tip of the iceberg – fugitive
shows its full force when we type g?
. For example,
type =
; this toggles an inline diff, and similarly dv
opens a
side-by-side diff of the file under the cursor.
So after staging file1.txt, we next type cc
to commit (same as :Git commit
previously).
So go ahead and make more changes,
check the diffs with =
and dv
, and commit with cc
several times.
Step 4: Interactive workflows
Logs
Now that we’ve got some commits, we’re ready to see more
fugitive enhanced workflows. Type :Gclog
and fugitive
will load the commit history into vim’s quickfix buffer.
This allows us to navigate the
commit graph simply by pressing <enter>
when we move our
cursor over commits, and using
quickfix commands like :cnext
and :cprev
.
And hitting <enter>
on a tree drills down into
a view of that tree’s files, and clicking <enter>
again opens the file itself as it appeared in the
commit. To go back up to the tree (and commit)
we use the vim standard <ctrl>-o
.
Blame
Another great workflow lets us understand how a region of code changed as we go back through its parentage.
Lets open file1.txt in the last committed version which
we’d also have in the working area. Next we type
:Git blame
. fugitive shows us which commit (and who)
created each line of the buffer. So for example we
see that the second line was from commit b1db78d5. We
can jump to the diff of that commit simply by moving
the cursor over the has and pressing <enter>
:
9 diff --git a/file1.txt b/file1.txt
10 index d6e0e74..43d2ec2 100644
11 --- a/file1.txt
12 +++ b/file1.txt
13 @@ -1,4 +1,4 @@
14 this is what we'll put in file1.txt, then we'll :saveas file1.txt
15 -add this second line, then :w
16 +add this second line, then :w we'll add this later
17 .
18 another change now
~
~
From this we can see the previous version of the line
on line 15 starting with the -
, and the current
version on line 16 starting with the +
. Hence if we
move our cursor to line 15 and press <enter>
– voila,
fugitive loads the file as it was at that commit. And
we can continue our archaeology by again typing :Git
blame
and clicking on lines in the diffs from earlier
commits to see earlier versions of the file, or more
succintly by typing
-
to “reblame” at a commit and P
to reblame at the
parent and see the line in the parent commit.
Branching and Merging
Next up, lets type :Git branch feature
, which will
open a window showing a new feature branch, and then
move over the feature branch and type coo
to checkout
the branch. Open file1.txt
and change the second
line to something like the below:
1 * feature
2 master
~
~
~
~
~
~
~
1___tmp_nvim_sun_DzGWEA_0__________________________________git____100____2___8
1 this is what we'll put in file1.txt, then we'll :saveas file1.txt
~ 2 add this second line, then :w nah lets not
3
4 another change now
~
~
~
~
2__NORMAL____file1_txt______________________________tex_____23W___50____2___32
Then :G
, stage and commit to the feature branch as
before.
Next we type :G branch
, hover over master, and hit
coo
to check it out. Change the second line
differently, stage and commit to master.
Now lets merge feature on master and we’ll get a merge
conflict! Remember the exclamation, as now we do
:Gvdiffsplit!
to get a 3-way diff showing HEAD
(master) on the left corresponding to the arrows and
feature on the right, and in the center is our file1.txt
to be resolved. We can resolve the center file while
looking at the diffs with the 2 parents, then
go back to the :Git summary, stage the file and cc
to
commit it.
1 this is what we'll p| 1 this is what we'll | 1 this is what we'll p
2 add this second line| 2 <<<<<<< HEAD | 2 add this second line
--------------------| 3 add this second lin| --------------------
--------------------| 4 ======= | --------------------
--------------------| 5 add this second lin| --------------------
--------------------| 6 >>>>>>> feature | --------------------
3 | 7 | 3
4 another change now | 8 another change now | 4 another change now
~ |~ |~
~ |~ |~
1__file1_txt__git__________2__N_____file1_txt________3__file1_txt__git_________
There’s a wealth of more features so send in any suggestions for future fugitive articles!