Categories

Tags

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!