Современный Neovim 2026 закрывает почти весь повседневный git-воркфлоу не выходя из буфера. Ключевая мысль этой главы: не существует одного "правильного" git-плагина. Есть три уровня задач, и под каждый - свой инструмент:
- Inline-операции в буфере (знаки изменений, stage/reset отдельного куска, blame под строкой) - это gitsigns.nvim.
- Полноценный интерактивный клиент (коммиты, ветки, rebase, разрешение конфликтов, история) - это TUI вроде lazygit/neogit или Ex-команды vim-fugitive.
- Ревью диффов и истории - это diffview.nvim и встроенный vimdiff.
gitsigns.nvim: git прямо в gutter
gitsigns.nvim - стандарт де-факто для работы с git внутри буфера. Он рисует в знаковой колонке (signcolumn, она же "gutter") маркеры напротив каждой изменённой строки: добавленные, изменённые, удалённые. Это первое, что вы видите, открыв файл под контролем версий, - мгновенная карта ваших правок относительно индекса/HEAD.
Но gitsigns - это далеко не только разноцветные палочки слева. Главная его сила в операциях над ханками (hunk - непрерывный блок изменений). Вы можете застейджить, откатить или предпросмотреть конкретный ханк, не трогая остальные, навигировать между изменениями и смотреть blame под текущей строкой.
Два условия, без которых знаков не будет
Прежде чем настраивать маппинги, важно понять, когда gitsigns вообще включается. Это источник самого частого вопроса новичков - "почему нет знаков?".
- Файл должен лежать в git-репозитории. gitsigns активируется (вызывает on_attach) только если открытый файл находится внутри дерева с .git и отслеживается git. Открыли файл вне репо, в свежей папке без git init или в новом, ещё не добавленном файле - gitsigns молча не подключится: ни знаков, ни маппингов, без всякой ошибки. Это нормальное поведение, а не баг конфига. Быстрая проверка - :Gitsigns toggle_signs или git status в каталоге файла.
- Включите signcolumn. Знаки рисуются в знаковой колонке. Если оставить дефолт signcolumn=auto, колонка появляется и исчезает по мере возникновения знаков - текст при этом дёргается влево-вправо, gutter "мигает". Поставьте фиксированную колонку, чтобы она была всегда:
Код: Выделить всё
vim.opt.signcolumn = 'yes' -- колонка зарезервирована всегда, текст не прыгает
Почему это меняет рабочий процесс
Классический git заставляет вас мыслить файлами целиком: git add file.lua берёт в индекс весь файл. Но реальная правка часто смешанная: вы пофиксили баг и заодно подправили форматирование в другом месте. Хочется закоммитить баг отдельно от косметики. В терминале для этого есть git add -p (patch mode), но это интерактивный диалог с y/n/s. Gitsigns даёт то же самое визуально: курсор стоит на ханке - нажали маппинг - ханк ушёл в индекс. Видно ровно то, что происходит.
Базовая навигация и операции
gitsigns не навязывает маппинги по умолчанию - их принято настраивать в колбэке on_attach. Канонический набор (используется в kickstart.nvim и LazyVim) выглядит так:
Код: Выделить всё
Клавиша | Действие | Команда gitsigns
------------+----------------------------------+---------------------------
]c | следующий ханк | next_hunk
[c | предыдущий ханк | prev_hunk
<leader>hs | застейджить ханк (stage) | stage_hunk
<leader>hr | откатить ханк (reset) | reset_hunk
<leader>hS | застейджить весь буфер | stage_buffer
<leader>hR | откатить весь буфер | reset_buffer
<leader>hu | отменить stage ханка (unstage) | stage_hunk (тоггл)
<leader>hp | предпросмотр ханка (preview) | preview_hunk
<leader>hb | blame текущей строки (полный) | blame_line
<leader>tb | тоггл inline-blame | toggle_current_line_blame
<leader>hd | diff против индекса | diffthis
ih / ah | текстовый объект "ханк" | select_hunk
Про <leader>hu (unstage) есть тонкость версий. В актуальном gitsigns отдельной функции undo_stage_hunk больше нет - она deprecated и убрана: stage_hunk стал тогглом, то есть повторный вызов на уже застейдженном ханке снимает его со stage. Поэтому в таблице выше <leader>hu маппится на тот же stage_hunk. Но многие конфиги (исторические версии kickstart.nvim и LazyVim) всё ещё ссылаются на gs.undo_stage_hunk - на свежем gitsigns такой маппинг выдаст ошибку "нет такого поля". Если видите её, замените вызов на stage_hunk.
Текстовый объект ханка ih ("inner hunk") - отдельная жемчужина. Он встраивается в грамматику Vim (оператор + объект, подробнее в главе 6): vih выделит ханк визуально, dih удалит, а в связке со stage-маппингом можно стейджить ровно выделенный диапазон в визуальном режиме. Важно понимать: у gitsigns один объект ханка - select_hunk. У ханка нет "окружения", поэтому реального различия inner/around (как у скобок или тегов) тут не существует. По соглашению select_hunk обычно вешают сразу на оба обозначения - и ih, и ah, - чтобы объект срабатывал с любым оператором независимо от привычки писать i или a; но это один и тот же ханк, а не две разные семантики.
Практика: разбираем смешанные правки
Код: Выделить всё
Состояние: вы отредактировали две функции в одном файле.
Ханк 1 (строки 10-12) - фикс бага, хотим закоммитить.
Ханк 2 (строки 40-45) - эксперимент, пока трогать не надо.
Курсор где-то вверху файла.
Нажимаем ]c -> курсор прыгает на ханк 1 (строки 10-12)
Нажимаем <leader>hp -> всплывает окно предпросмотра: видим diff ханка
Нажимаем <leader>hs -> ханк 1 уходит в индекс, знаки слева гаснут
Нажимаем ]c -> курсор на ханке 2 (строки 40-45)
его НЕ трогаем
Теперь в индексе только фикс бага. Идём коммитить (через fugitive
или lazygit - см. ниже) - закоммитится ровно ханк 1.
Blame: кто и зачем
toggle_current_line_blame включает виртуальный текст в конце текущей строки: автор, дата и сообщение коммита, который последним её менял. Это "летающая" подсказка - она следует за курсором и не засоряет файл. Для подробностей (<leader>hb) открывается полный blame конкретной строки с возможностью прыгнуть в сам коммит.
Код: Выделить всё
set: vim.b.gitsigns_blame_line_dirty показывает, что строка не закоммичена
Курсор на строке 42.
<leader>tb -> в конце строки появляется:
" Alice, 3 months ago - refactor auth flow"
Двигаем курсор на строку 50 ->
" Bob, 2 years ago - initial commit"
По умолчанию gitsigns показывает знаки относительно индекса (staged-версия файла): отредактировали строку - появился знак, застейджили ханк - знак погас. Но базу сравнения можно сменить на произвольную ревизию командой :Gitsigns change_base:
Код: Выделить всё
:Gitsigns change_base HEAD~3 -- знаки = дифф против коммита 3 шага назад
:Gitsigns change_base main -- дифф текущего файла против ветки main
:Gitsigns change_base HEAD~3 true -- 2-й аргумент true = глобально для всех буферов
:Gitsigns change_base -- без аргумента вернуть базу к индексу
vim-fugitive: git как Ex-команды
vim-fugitive от Tim Pope - старейший и самый "вимовый" git-плагин. Его философия противоположна TUI: git здесь - это набор Ex-команд, а его статус-окно встроено в модель буферов и окон Vim. В 2026 fugitive ушёл в нишу для тех, кому ближе Ex-команды и кто хочет, чтобы git подчинялся той же грамматике, что и весь редактор. Но эта ниша устойчивая: fugitive невероятно мощный и отлично работает там, где TUI избыточен.
Центральная команда: :Git
:Git (можно сокращать до :G) без аргументов открывает окно статуса - интерактивный аналог git status. Это не просто текст: внутри работают маппинги.
Код: Выделить всё
Клавиша (в окне :Git) | Действие
-----------------------+-------------------------------------------
s | застейджить файл/ханк под курсором
u | unstage (убрать из индекса)
- | тоггл stage/unstage
= | развернуть inline-diff файла под курсором
X | откатить (discard) изменение
cc | создать коммит (:Git commit)
ca | commit --amend
dd / dv | открыть diff файла (split/vertical)
<CR> | открыть файл под курсором
gq / q | закрыть окно статуса
:Git blame - навигируемый blame
:Git blame открывает вертикальный сплит-аннотацию слева от файла: каждая строка подписана коммитом, автором и датой. В отличие от inline-blame gitsigns, это полноэкранный навигируемый blame для глубокого расследования истории.
Код: Выделить всё
Клавиша (в окне blame) | Действие
------------------------+-----------------------------------------------------
<CR> | открыть коммит этой строки
o / O | открыть коммит в сплите / новой вкладке
~ | blame на родительском коммите (шаг назад в историю)
q | закрыть
:Gdiffsplit - трёхпанельное разрешение и сравнение
:Gdiffsplit открывает текущий файл в режиме vimdiff против индекса (или против произвольной ревизии: :Gdiffsplit HEAD~2). Слева - версия из git, справа - ваш рабочий файл. Дальше работают все возможности vimdiff: навигация ]c/[c, перенос изменений do (diff obtain) и dp (diff put).
Во время merge-конфликта :Gdiffsplit! (с восклицательным знаком) открывает трёхпанельный вид: слева "наша" версия (//2), справа "их" (//3), а в центре - файл с маркерами конфликта. Из боковых панелей переносите нужные куски в центр через :diffget //2 или :diffget //3.
Важно: //2 и //3 - это нотация самого fugitive, а не свойство голого vimdiff. За ней стоят имена буферов вида fugitive://.../2/ (наша сторона, :2) и fugitive://.../3/ (их сторона, :3), и :diffget принимает их как часть имени буфера. В чистом vimdiff (без fugitive) такого синтаксиса нет - там источник указывают номером буфера: :diffget {bufnr}, например :diffget 3 (номера смотрите по :ls).
Код: Выделить всё
Конфликт слияния в config.lua. Открываем файл, видим <<<<<<< ======= >>>>>>>
:Gdiffsplit! -> три окна: //2 (наше) | рабочий | //3 (их)
Курсор в центре на конфликтном ханке.
:diffget //3 -> подтягиваем "их" версию ханка
]c -> следующий конфликтный ханк
:diffget //2 -> для него берём "нашу" версию
:Gwrite -> записать результат и застейджить (=git add)
lazygit и neogit: полноценные TUI-клиенты
Когда задача выходит за рамки "застейджить ханк" - интерактивный rebase, перенос коммитов, работа с ветками и стешами, разрешение десятка конфликтов - нужен полноценный git-клиент. Здесь два лидера 2026 года: lazygit и neogit.
lazygit
lazygit - самостоятельный TUI-инструмент на Go, не зависящий от Neovim. Это его главное преимущество: один и тот же интерфейс работает и внутри редактора (во встроенном терминале), и в чистом терминале на сервере. Это самый популярный git-TUI в принципе.
В Neovim его открывают во встроенном терминале (см. главу 30 про терминал). Самый удобный способ в 2026 - через snacks.nvim:
Код: Выделить всё
-- через snacks.nvim (folke)
vim.keymap.set('n', '<leader>gg', function()
require('snacks').lazygit()
end, { desc = 'Lazygit' })
neogit
neogit - это клон Magit из Emacs, написанный на нативном Lua с UI прямо из попапов и буферов Neovim. В отличие от lazygit, он не запускает внешний процесс - всё происходит в родных окнах редактора, что даёт более тесную интеграцию (общие маппинги, единый стиль, интеграция с diffview.nvim для просмотра диффов).
Код: Выделить всё
-- neogit + diffview
{
'NeogitOrg/neogit',
dependencies = { 'nvim-lua/plenary.nvim', 'sindrets/diffview.nvim' },
cmd = 'Neogit',
keys = { { '<leader>gn', '<cmd>Neogit<cr>', desc = 'Neogit' } },
}
Что выбрать
Код: Выделить всё
| lazygit | neogit
-----------------------+--------------------+------------------------
Реализация | внешний TUI (Go) | нативный Lua UI
Работает вне Neovim | да | нет
Интеграция с буферами | через терминал | родная
Интеграция diffview | нет | да
Зависимости | бинарь lazygit | plenary, опц. diffview
diffview.nvim: ревью как в pull request
diffview.nvim - это инструмент именно для ревью: детального просмотра диффов и истории. Если gitsigns показывает ваши локальные правки, а fugitive/TUI - управляют коммитами, то diffview даёт обзорный взгляд "что изменилось между двумя точками" в формате, похожем на интерфейс PR на GitHub.
Код: Выделить всё
Команда | Что показывает
-----------------------------+---------------------------------------------------------------
:DiffviewOpen | все незакоммиченные изменения (рабочее дерево vs индекс/HEAD)
:DiffviewOpen HEAD~3 | дифф рабочего дерева против коммита 3 шага назад
:DiffviewOpen main..feature | дифф между ветками
:DiffviewFileHistory % | история изменений текущего файла
:DiffviewFileHistory | история всего репозитория (как git log с диффами)
:DiffviewClose | закрыть
Именно поэтому neogit идёт в связке с diffview: neogit управляет операциями, а diffview обеспечивает качественный просмотр того, что эти операции затрагивают.
Встроенный vimdiff и :diffthis
Под всеми diff-плагинами лежит встроенный движок diff Vim/Neovim. Понимать его полезно, потому что он работает без единого плагина - например, на голом сервере, куда вы зашли по SSH (стратегия "Vim на серверах" из главы 24).
Запустить diff можно несколькими способами:
- Снаружи: nvim -d file1 file2 (или vimdiff file1 file2).
- Изнутри: открыть два окна и в каждом выполнить :diffthis. Чтобы выключить - :diffoff.
- Перечитать diff после правок - :diffupdate.
Код: Выделить всё
Клавиша/команда | Действие
-----------------+-----------------------------------------------------
]c | следующее изменение
[c | предыдущее изменение
do | diff obtain - взять изменение из другого окна СЮДА
dp | diff put - отправить изменение ОТСЮДА в другое окно
:diffget | то же что do, но командой (нужно при 3+ окнах)
:diffput | то же что dp командой
zo / zc | развернуть/свернуть фолд контекста
Для истории правок текущего файла встроенного дерева undo в ядре нет, но есть навигация по веткам undo: :earlier 5m откатывает на состояние пятиминутной давности, :later идёт вперёд, g-/g+ шагают по веткам undo. Если включить персистентный undo (set undofile), история переживает перезапуск. За визуальным деревом undo-веток ставят внешний плагин mbbill/undotree (:UndotreeToggle) - в ядре Neovim такого инструмента нет.
Типовой git-воркфлоу в редакторе 2026
Соберём всё в один связный цикл - как это выглядит на практике за один рабочий заход:
Код: Выделить всё
1. Пишете код. gitsigns рисует знаки изменений в gutter - постоянная
обратная связь, что вы наменяли относительно HEAD.
2. ]c / [c - прыгаете между ханками, <leader>hp - предпросмотр.
Сомнительный кусок откатываете <leader>hr (reset_hunk).
3. Собираете коммит по смыслу: <leader>hs на нужных ханках
(или визуальное выделение + stage для частичного ханка).
Косметику и эксперименты пока НЕ стейджите.
4. Коммитите интерактивным клиентом:
- <leader>gg -> lazygit, либо
- :Neogit -> попап коммита, либо
- :Git -> cc в fugitive.
Пишете осмысленное сообщение, при необходимости amend.
5. Ревьюите перед пушем: :DiffviewOpen - пробегаете изменения
как PR, ловите забытый debug-print.
6. Конфликт после pull? :Gdiffsplit! (fugitive) -> :diffget //2 / //3
(нотация fugitive) или голый vimdiff -> :diffget {bufnr};
разрешаете, :Gwrite.
7. Расследование "почему так": <leader>tb (inline blame) для
быстрого взгляда, :Git blame + ~ для глубокого погружения,
:DiffviewFileHistory % чтобы пролистать эволюцию файла.
8. Push: P в lazygit/neogit или :Git push.
Лучшие практики 2026
- Разделяйте задачи между плагинами осознанно. Каноническая связка 2026: gitsigns для inline-операций в буфере + один полноценный клиент (neogit или lazygit) для сложных операций. gitsigns не делает rebase и не разрешает конфликты деревом, а TUI не дают inline-ханков под курсором. Используйте их вместе.
- gitsigns + lazygit - самый прагматичный набор для большинства. lazygit - отдельный TUI, не зависящий от Neovim, поэтому навык переносится на серверы и в чистый терминал. Открывайте его через snacks.lazygit (часть snacks.nvim) - он передаёт тему и умеет открывать файлы в текущем Neovim, а не во вложенном.
- neogit + diffview.nvim - если хотите всё нативно в Lua. Practicalli Neovim в 2026 рекомендует именно эту связку: neogit управляет операциями попапами в стиле Magit, diffview обеспечивает качественный просмотр диффов и истории.
- fugitive - для тех, кому ближе Ex-команды. Не считайте его "устаревшим": для :Gdiffsplit при разрешении конфликтов и навигируемого :Git blame с шагами ~ по истории он по-прежнему один из лучших. Просто это другая парадигма - git как продолжение командной строки Vim.
- Стейджите ханками, а не файлами. Главная ценность gitsigns - атомарные коммиты по смыслу. Привыкайте к циклу ]c -> <leader>hp -> <leader>hs, а не к git add ..
- Используйте :DiffviewOpen как обязательный pre-push ритуал. Беглый просмотр всего changeset перед пушем ловит забытые print/console.log и случайно застейдженные файлы лучше, чем git status.
- Знайте голый vimdiff. На сервере без плагинов :diffthis/do/dp и ]c/[c спасают. Это та же навигация, что у gitsigns, - мышечная память переносится.
- Путать роли плагинов. Ожидать от gitsigns rebase, а от lazygit - inline-ханков под курсором. Это разные уровни; инструменты дополняют друг друга, а не заменяют.
- do/dp наоборот. В vimdiff do (obtain) тянет изменение к себе, dp (put) отправляет от себя. Перепутав направление, легко затереть не ту версию. Мнемоника: "o - ко мне, p - от меня".
- :diffget без указания буфера при трёх окнах. В трёхпанельном merge Vim не угадает источник - нужно явно указать, откуда брать. Через fugitive (:Gdiffsplit!) это его нотация :diffget //2 (наше) или :diffget //3 (их); в голом vimdiff - номер буфера, например :diffget 3 (номера по :ls). Без указателя получите ошибку или возьмёте не ту сторону.
- Стейджить файл целиком, когда правки смешанные. git add file или stage_buffer тащит в индекс и баг-фикс, и эксперимент. Для чистой истории стейджите ханками (<leader>hs) или частями ханка через визуальное выделение.
- Забыть :Gwrite/сохранение после разрешения конфликта. Разрешили конфликт в diff-окнах, но не записали результат - git по-прежнему считает файл конфликтным. :Gwrite пишет файл И стейджит его.
- Не настроить on_attach у gitsigns и удивляться, что маппингов нет. gitsigns намеренно не вешает клавиши по умолчанию. Без колбэка on_attach (или явных keys) у вас будут только знаки в gutter, но не операции над ханками.
- Ждать знаков вне git-репозитория. gitsigns молча не активируется, если файл не в дереве с .git (или ещё не отслеживается git). Нет знаков и не работают маппинги - первым делом проверьте, что вы внутри репо (git status), а не то, что "сломался конфиг".
- Оставить signcolumn=auto и страдать от дёрганья текста. Без set signcolumn=yes колонка появляется только когда есть знаки, и текст прыгает при каждом первом изменении в файле. Зарезервируйте колонку постоянно.
- Запускать вложенный редактор внутри lazygit. Если открыть lazygit без snacks/правильной интеграции, команда редактирования (e в lazygit) может запустить ещё один Neovim внутри терминала. snacks.lazygit решает это, открывая файл в вашем текущем экземпляре.
- Атомарный коммит. Внесите в один файл две независимые правки в разных местах. С помощью ]c/[c найдите ханки, через <leader>hp просмотрите каждый, застейджите <leader>hs только первый, и закоммитьте его (через :Git, :Neogit или lazygit). Убедитесь по git status, что второй ханк остался незакоммиченным.
- Частичный ханк. Создайте ханк из 5 изменённых строк. Выделите визуально 2 из них и застейджите выделение через stage_hunk. Проверьте :DiffviewOpen, что в индекс ушли ровно две строки.
- Blame-расследование. Откройте любой файл из реального репозитория, включите inline-blame (<leader>tb), найдите строку с интересным изменением. Затем откройте :Git blame, прыгните на её коммит через <CR> и пройдите на шаг назад в истории клавишей ~.
- Голый vimdiff. Сделайте копию файла, измените в ней пару строк. Откройте обе версии и в каждом окне выполните :diffthis. С помощью ]c пройдите по различиям и перенесите одно изменение командой do, а другое - dp. Затем выключите режим через :diffoff.
- Ревью changeset. Сделайте 2-3 коммита в ветке. Выполните :DiffviewOpen main..HEAD (подставьте свою базовую ветку) и пройдите по списку файлов, как по pull request. Найдите изменение, которое стоило бы вынести в отдельный коммит.
- История файла. Откройте :DiffviewFileHistory % на файле с богатой историей. Пролистайте коммиты от свежих к старым и проследите, как менялась одна конкретная функция.
- Git-интеграция в Neovim делится на три уровня, под каждый свой инструмент: gitsigns (inline в буфере), lazygit/neogit/fugitive (интерактивный клиент), diffview (ревью). Их используют вместе.
- gitsigns.nvim даёт знаки в gutter, навигацию ]c/[c, stage/reset/preview ханка, inline-blame и текстовый объект ханка (select_hunk, обычно на ih/ah). Активируется только внутри git-репо; для стабильного gutter нужен signcolumn=yes. База сравнения по умолчанию - индекс, но меняется на любую ревизию через :Gitsigns change_base. Это инструмент атомарных коммитов по смыслу.
- vim-fugitive делает git Ex-командами: :Git (статус с маппингами s/u/cc), :Git blame (навигируемый, с шагом ~ в историю), :Gdiffsplit! (трёхпанельное разрешение конфликтов), :Gwrite/:Gread.
- lazygit - переносимый TUI вне зависимости от Neovim (открывать через snacks.lazygit); neogit - нативный Lua-клон Magit в связке с diffview. Practicalli рекомендует neogit+diffview, но lazygit прагматичнее за счёт переносимости.
- diffview.nvim - ревью в стиле pull request: :DiffviewOpen, сравнение веток, :DiffviewFileHistory %.
- Под всем лежит встроенный vimdiff: :diffthis/:diffoff, навигация ]c/[c, перенос do (obtain, к себе) / dp (put, от себя), а в 3+ окнах - явный :diffget {bufnr} (номера по :ls); нотация :diffget //2///3 - это уже fugitive, а не голый vimdiff. Работает без плагинов - незаменимо на серверах.
- Типовой воркфлоу 2026: пишем -> стейджим ханки gitsigns -> коммитим клиентом -> ревьюим diffview перед пушем -> разбираем конфликты через diff-окна -> расследуем историю blame.