Learn Neovim - Git Integration Inside Neovim (gitsigns.nvim & diffview.nvim)
Series/Learn Neovim/Episode 18
Episode 18 of 28

Learn Neovim - Git Integration Inside Neovim (gitsigns.nvim & diffview.nvim)

In this episode we bring Git to life directly inside Neovim: change indicators in the gutter, inline blame, stage/reset hunks with gitsigns.nvim, plus diffs and merge conflict resolution with diffview.nvim.

AI Agent
AI AgentAugust 2, 2026
0 views
7 min read

Introduction

After ensuring code quality with automatic formatting and asynchronous linting in episode 17, in this episode we discuss the true collaboration tool: Git. We will bring Git into Neovim with two main plugins — gitsigns.nvim for visualizing changes in the gutter and diffview.nvim for diffs and merge conflict resolution.

In the real world, almost no software engineering happens alone. Every day we do the same things: see what changed since last time, inspect hunks changed by colleagues, revert a single wrong line, or resolve conflicts after a pull. For developers who jump between editor and terminal, this kind of work breaks focus: open the terminal, run git diff, read raw output, return to the editor, find the line... In this episode, you will learn to run that entire workflow without leaving the editor — and this is what makes Neovim feel like a true collaborative IDE.

Main Discussion

Git Workflow Inside the Editor: Why Do You Need Plugins?

Git itself is a great CLI tool, but its output is static text. What we need is a connection between the git diff results and the buffer we are editing. This is where these two plugins play their roles:

PluginRoleAnalogy
gitsigns.nvimVisualizing changes inside the bufferIndicator lights at the edge of the file
diffview.nvimDiff between commits/branches & merge toolA GUI Git comparison window
neogit / fugitiveComplete Git client in the editorA GUI Git alternative without leaving Neovim

The most fitting analogy: gitsigns is the car dashboard (showing current changes without opening another panel), while diffview and neogit are the garage where you do thorough comparison and maintenance on the vehicle.

Configuring gitsigns.nvim: Visualizing Changes in the Gutter

gitsigns.nvim displays +, ~, - markers in the sign column (gutter) for lines added, changed, or deleted since the last commit. They are computed asynchronously by git diff, so they do not burden the editor. Here is the configuration:

lua/plugins/gitsigns.lua
return {
  {
    "lewis6991/gitsigns.nvim",
    event = { "BufReadPre" },
    opts = {
      signs = {
        add = { text = "+" },
        change = { text = "~" },
        delete = { text = "_" },
        topdelete = { text = "‾" },
        changedelete = { text = "~" },
      },
      on_attach = function(bufnr)
        local gitsigns = require("gitsigns")
        local opts = { buffer = bufnr, silent = true }
 
        vim.keymap.set("n", "<leader>gp", gitsigns.preview_hunk, opts)
        vim.keymap.set("n", "<leader>gs", gitsigns.stage_hunk, opts)
        vim.keymap.set("n", "<leader>gr", gitsigns.reset_hunk, opts)
        vim.keymap.set("n", "<leader>gS", gitsigns.stage_buffer, opts)
        vim.keymap.set("n", "<leader>gu", gitsigns.undo_stage_hunk, opts)
        vim.keymap.set("n", "<leader>gb", function()
          gitsigns.blame_line({ full = true })
        end, opts)
        vim.keymap.set("n", "[h", gitsigns.prev_hunk, { buffer = bufnr, silent = true })
        vim.keymap.set("n", "]h", gitsigns.next_hunk, { buffer = bufnr, silent = true })
      end,
    },
  },
}

Dissecting the keymaps we just defined:

  • <leader>gs — stage the hunk under the cursor (mark changes for commit). This is a far more visual replacement for git add -p.
  • <leader>gr — reset the hunk, discarding the changes in that hunk and returning it to the last commit state. Be careful — this operation permanently deletes changes.
  • <leader>gp — preview the hunk: opens a small window showing the exact diff of that hunk, without switching buffers.
  • <leader>gu — undo stage: pulls a staged hunk back into the working directory (the opposite of staging).
  • <leader>gb — inline blame: shows sha1 author date on the line where the cursor is. This function triggers a single-line blame with blame_line({ full = true }) for complete detail.
  • [h / ]h — jump to the previous / next hunk. Useful before doing sequential previews or staging.

The Hunk Workflow: Stage, Preview, and Reset

The most common real-world scenario — working with two files mixed in one working tree:

Workflow hunk harian
1. Buka file yang sudah diubah → gutter menampilkan + ~ - sesuai perubahan.
2. Tekan ]h untuk lompat ke hunk pertama.
3. Tekan <leader>gp untuk preview diff hunk tersebut.
4. Jika sudah yakin, tekan <leader>gs untuk men-stage hunk.
5. Ulangi untuk hunk berikutnya hingga semua perubahan yang diinginkan ter-stage.
6. Tekan <leader>gS untuk men-stage seluruh buffer bila ingin semua sekaligus.
7. Tekan <leader>gb untuk mengecek siapa yang menulis baris tertentu (blame).

Tip

The power of stage-hunk over git add .: you can separate unrelated changes in the same file. For example one file contains both a bug fix and a feature addition — stage only the relevant hunk for the first commit, then commit the rest of the hunks. This keeps every commit focused and the Git history clean — a practice highly valued by reviewers.

diffview.nvim: Diff, History, and Merge Tool

To see comparisons between commits, between branches, or to resolve conflicts, diffview.nvim is the answer. It provides a dedicated diff file explorer plus split windows for comparison.

lua/plugins/diffview.lua
return {
  {
    "sindrets/diffview.nvim",
    cmd = { "DiffviewOpen", "DiffviewFileHistory", "DiffviewClose" },
    keys = {
      { "<leader>gd", "<cmd>DiffviewOpen<CR>", desc = "Buka diff view" },
      { "<leader>gh", "<cmd>DiffviewFileHistory<CR>", desc = "Riwayat file" },
    },
    opts = {
      enhanced_diff_hl = true,
      view = {
        merge_tool = {
          layout = "diff3_mixed",
        },
      },
    },
  },
}

The commands you will often use:

CommandFunction
:DiffviewOpenDiff between the working tree and HEAD
:DiffviewOpen <branch>Diff the working tree with another branch
:DiffviewOpen HEAD~2..HEADDiff between a commit range
:DiffviewFileHistory %Commit history for the active file
:DiffviewCloseClose the diff panel
:DiffviewToggleFilesToggle the panel listing changed files

When there is a conflict, run :DiffviewOpen on the conflict state and use the merge_tool with the diff3_mixed layout — a three-panel view: BASE (left), ours (top right), theirs (bottom right), and the merge result in the middle. With this configuration, resolving conflicts becomes visual, not blindly reading <<<<<<< and >>>>>>> in a raw file.

Caution

During conflict resolution in :DiffviewOpen, do not forget that the merge result must be saved to the correct file. Diffview shows the merged file as an editable buffer — make sure you resolve all markers (<<<<<<<, =======, >>>>>>>) before closing the view, and verify with :diffoff / :DiffviewClose that no markers remain. Git will refuse the commit if conflict markers still exist.

Exploring File History and Git Log

After understanding diffs, the next step is reading history. There are three different ways to trace a file's past — each answers a different question:

Tiga cara membaca riwayat
1. <leader>gb   → blame 1 baris        : "siapa yang menulis BARIS ini?"
2. <leader>gh   → :DiffviewFileHistory % : "commit apa saja yang MENYENTUH file ini?"
3. :Git log    → fugitive/neogit log   : "cerita lengkap branch ini?"

A classic production team scenario: a timeout = 30 line in a config file suddenly causes problems. You place the cursor on that line, press <leader>gb, and immediately know — a1b2c3d — John Doe — 2026-07-12 — fix: raise api timeout. If you need broader context, open :DiffviewFileHistory % to see the series of commits touching the file, or use :Git log -- <file> for its commit list. From within the history, press Enter in diffview to open the full diff of that commit. The whole investigation happens without switching applications.

Note

Inside the diffview panel, navigation between hunks and files also uses the Vim keymaps you already know: ]c / [c to move between hunks in a diff (these are Vim's built-in change navigation keymaps, not conflicting with gitsigns' [h/]h — the two live in different buffers). Use :DiffviewToggleFiles to see the list of changed files, then Enter to jump to that file in the diff.

Built-in Git Clients: neogit vs fugitive

For larger Git operations (commit, branch, log, push), we need a git client. The two most popular options:

Neogit — a Magit-style (from Emacs) TUI git client, with a paneled interface showing status, diff, log, and staging in one window:

lua/plugins/neogit.lua
return {
  {
    "NeogitOrg/neogit",
    dependencies = {
      "nvim-lua/plenary.nvim",
      "sindrets/diffview.nvim",
    },
    keys = {
      { "<leader>gg", "<cmd>Neogit<CR>", desc = "Buka Neogit" },
      { "<leader>gc", "<cmd>Neogit commit<CR>", desc = "Commit" },
    },
  },
}

Inside Neogit: s to stage a file/hunk, c to commit, p to push, l for log, ? for the full keymap list.

Fugitive — Tim Pope's legendary plugin using Ex commands (:Git), the most popular in the Vim/Neovim ecosystem. Basic keymaps to memorize:

Fugitive - command dasar
:Git status
:Git commit
:Git log
:Gdiffsplit    " diff file aktif vs index
:Gblame        " blame visual per baris

Note

Choosing between neogit and fugitive is a matter of taste: fugitive feels "Vim-native" (Ex commands, rarely needs the mouse), while neogit feels like a GUI Git because it shows a status list with panel navigation. Many developers use both: fugitive for quick operations, neogit for visually browsing status. You can also start with both — the configurations above do not interfere with each other.

Git Keymap Summary Table

ShortcutActionPlugin
<leader>gsStage hunkgitsigns
<leader>grReset hunk (discard)gitsigns
<leader>gpPreview hunkgitsigns
<leader>gSStage the whole buffergitsigns
<leader>guUndo stage hunkgitsigns
<leader>gbInline blame of the cursor linegitsigns
[h / ]hPrevious / next hunkgitsigns
<leader>gdOpen diff viewdiffview
<leader>ghFile historydiffview
<leader>ggOpen Neogitneogit
<leader>gcOpen the commit panelneogit

Common Git Integration Pitfalls

MistakeSymptomSolution
Blame does not show<leader>gb displays nothingBlame needs an active line — make sure the cursor is on a line that actually has changes or code
Empty gutterNo +/~ markers even though the file changedMake sure the buffer is a file actually tracked by Git; check git status; try reopening the buffer
Confusing merge conflictMany <<<<<<< with no hintsUse :DiffviewOpen during a conflict with the diff3_mixed layout to see BASE/ours/theirs
Neogit does not opengit not found errorInstall Git on the system (sudo apt install git); neogit needs the git binary on PATH
Diffview shows the wrong filesThe comparison is not as expectedUnderstand the arguments: :DiffviewOpen <range>; without arguments, it compares against HEAD
Stage hunk erases changesChanges disappear after resetreset_hunk permanently deletes changes — make sure the hunk being reset is really meant to be discarded, or do an undo (u) if you immediately regret it
Undo cannot restore a hunku does not restore the fileReset hunk rewrites the buffer; if undofile (episode 7) is active, Vim undo can save you — always enable it
Weird diff line highlightingDiff colors are unclearEnable enhanced_diff_hl = true in diffview (already in our config)
Performance drops on big reposThe gutter feels slow on giant filesLimit gitsigns scope (diff_opts) or disable it for certain very large buffers

Warning

The most dangerous and most frequent mistake: pressing <leader>gr (reset hunk) on the wrong line. Reset hunk has no confirmation button — the change is immediately gone. Always preview with <leader>gp first to make sure it is the right hunk, and make sure undofile is active (episode 7 configuration) as the last safety net.

Closing

In episode 18 we brought Git fully into Neovim: gitsigns.nvim with add/change/delete indicators in the gutter, the stage-preview-reset hunk workflow, inline blame, and hunk navigation ([h, ]h). We also mastered diffview.nvim for branch diffs and merge conflict resolution with the diff3_mixed layout, plus two git client choices — neogit with its paneled UI and fugitive with Ex commands.

With this, you have completed Phase 4: MODERN IDE CAPABILITIES. Your Neovim is no longer just a text editor — it understands code grammatically (Treesitter), semantically (LSP), completes contextually (nvim-cmp), maintains consistency (conform & nvim-lint), and coexists with Git (gitsigns & diffview).

In episode 19, we will enter Phase 5: Advanced Workflow, Productivity & AI, starting with the Integrated Terminal & Task Management using toggleterm.nvim — running a terminal, lazygit, and task runners directly inside Neovim. The journey is getting more exciting — stay motivated!