chrisgrieser/nvim-tinygit

github github
plugingit
stars 44
issues 1
subscribers 3
forks 1
CREATED

2023-09-20

UPDATED

8 days ago


nvim-tinygit

Lightweight and nimble git client for nvim.

Commit Message Input with highlighting

Informative notifications with highlighting (using nvim-notify)

Search File history ("git pickaxe") and inspect the commit diffs.

Table of Contents

Features

  • Smart-Commit: Open a popup to enter a commit message with syntax highlighting and indicators for commit message overlength. If there are no staged changed, stages all changes before doing so (git add -A).
  • Commit messages have syntax highlighting, indicators for commit message overlength, and options to automatically open references GitHub issues in the browser after committing, git push if the repo is clean, spellcheck, enforce conventional commits, …
  • Quick commands for amend, stash, fixup, and squash commits.
  • Search issues & PRs. Open the selected issue or PR in the browser.
  • Open the GitHub URL of the current file or selection.
  • Search the file history for a string ("git pickaxe"), show results in a diff with filetype syntax highlighting.
  • Improvements for interactive rebasing with nvim as sequence editor.

Installation

-- lazy.nvim
{
    "chrisgrieser/nvim-tinygit",
    ft = { "gitrebase", "gitcommit" }, -- so ftplugins are loaded
    dependencies = {
        "stevearc/dressing.nvim",
        "rcarriga/nvim-notify", -- optional, but recommended
    },
},

-- packer
use {
    "chrisgrieser/nvim-tinygit",
    requires = {
        "stevearc/dressing.nvim",
        "rcarriga/nvim-notify", -- optional, but recommended
    },
}

Install the Treesitter parser for git commits for some syntax highlighting of your commit messages like emphasized conventional commit keywords: TSInstall gitcommit

Usage

Smart-Commit

  • Open a commit popup. If there are no staged changes, stage all changes (git add -A) before the commit. Only supports the commit subject line.
  • Optionally run git push if the repo is clean after committing.
  • The title of the input field displays what actions are going to be performed. You can see at glance, whether all changes are going to be committed or whether there a git push is triggered afterward, so there are no surprises.
  • Use <Tab> in the input field to quickly cycle conventional commits keywords.
require("tinygit").smartCommit { pushIfClean = false } -- options default to `false`

Example Workflow Assuming these keybindings:

vim.keymap.set("n", "ga", "<cmd>Gitsigns add_hunk<CR>") -- gitsigns.nvim
vim.keymap.set("n", "gc", function() require("tinygit").smartCommit() end)
vim.keymap.set("n", "gp", function() require("tinygit").push() end)
  1. Stage some hunks (changes) via ga.
  2. Use gc to enter a commit message.
  3. Repeat 1 and 2.
  4. When done, gp to push the commits.

Using pushIfClean = true allows you to combine staging, committing, and pushing into a single step, when it is the last commit you intend to make.

-- to enable normal mode in the commit message input field, configure
-- dressing.nvim like this:
require("dressing").setup {
    input = { insert_only = false },
}

Amend

  • amendOnlyMsg just opens the commit popup to change the last commit message, and does not stage any changes.
  • amendNoEdit keeps the last commit message; if there are no staged changes, it stages all changes (git add -A), like smartCommit.
  • Optionally runs git push --force afterward. (Remember to only do this when you work alone on the branch though.)
-- options default to `false`
require("tinygit").amendOnlyMsg { forcePush = false }
require("tinygit").amendNoEdit { forcePush = false }

Fixup & Squash Commits

  • fixupCommit lets you select a commit from the last X commits and runs git commit --fixup on the selected commit
  • Use squashInstead = true to squash instead of fixup (git commit --squash).
  • autoRebase = true automatically runs rebase with --autosquash and --autostash afterward, confirming all fixups and squashes without opening a rebase view. (Note that this can potentially result in multiple conflicts.)
-- options show default values
require("tinygit").fixupCommit { 
    selectFromLastXCommits = 15
    squashInstead = false, 
    autoRebase = false,
}

GitHub Interaction

-- state: all|closed|open (default: all)
-- type: all|issue|pr (default: all)
require("tinygit").issuesAndPrs { type = "all", state = "all" }

-- alternative: if the word under the cursor is of the form `#123`,
-- just open that issue/PR
require("tinygit").openIssueUnderCursor()
  • Open the current file at GitHub in the browser and copy the URL to the system clipboard.
  • Normal mode: open the current file or repo.
  • Visual mode: open the current selection.
-- file|repo (default: file)
require("tinygit").githubUrl("file")

Push & PR

  • createGitHubPr opens a PR from the current branch browser.
    • This requires the repo to be a fork with sufficient information on the remote.
    • This does not require the gh cli, as it uses a GitHub web feature.
-- options default to `false`
require("tinygit").push {
    pullBefore = false,
    force = false,
    createGitHubPr = false,
}
require("tinygit").createGitHubPr()

Search File/Function History ("git pickaxe")

  • Search the git history of the current file for a term (git log -S).
    • The search is case-insensitive and supports regex.
    • Select from the matching commits to open a diff popup.
  • Explore the history of a function in the current file (git log -L).
    • If the current buffer has an LSP with support for document symbols attached, you select can select a function. (Otherwise, you are prompted to enter a function name.)
    • Select from the matching commits to open a diff popup.
    • Note that git uses heuristics to determine the enclosing function of a change, so this is not 100% perfect, and has varying reliability across languages.

Keymaps in the diff popup:

  • <Tab>/<S-Tab>: cycle through the commits.
  • yh: yank the commit hash to the system clipboard.
  • n/N (file history): go to the next/previous occurrence of the query.
require("tinygit").searchFileHistory()
require("tinygit").functionHistory()

Stash

require("tinygit").stashPush()
require("tinygit").stashPop()

Improved Interactive Rebasing

tinygit also comes with some improvements for interactive rebasing (git rebase -i) with nvim:

  • Improved syntax highlighting of commit messages.
  • <Tab> (normal mode): Cycle through the common rebase actions: pick, reword, fixup, squash, drop. Also supports their short forms.

[!NOTE] This requires that your git editor (or sequence editor) is set to use nvim. You can do so by running git config --global core.editor "nvim".

If you want to disable those modifications, add this to your config:

vim.g.tinygit_no_rebase_ftplugin = true

Configuration

The setup call is optional. These are the default settings:

local defaultConfig = {
    commitMsg = {
        -- Why 50/72 is recommended: https://stackoverflow.com/q/2290016/22114136
        mediumLen = 50,
        maxLen = 72,

        -- When conforming the commit message popup with an empty message, fill
        -- in this message. `false` to disallow empty commit messages.
        emptyFillIn = "chore", ---@type string|false

        conventionalCommits = {
            enforce = false, -- disallow commit messages without a keyword
            -- stylua: ignore
            keywords = { -- also used for the cycle-keywords via <Tab>
                "fix", "feat", "chore", "docs", "refactor", "build", "test",
                "perf", "style", "revert", "ci", "break", "improv",
            },
        },

        -- enable vim's builtin spellcheck for the commit message input field
        -- (configured to ignore capitalization and correctly consider camelCase)
        spellcheck = false,

        -- if commit message references issue/PR, open it in the browser
        openReferencedIssue = false,
    },
    push = {
        preventPushingFixupOrSquashCommits = true,
        confirmationSound = true, -- currently macOS only
    },
    issueIcons = {
        openIssue = "🟢",
        closedIssue = "🟣",
        openPR = "🟩",
        mergedPR = "🟪",
        closedPR = "🟥",
    },
    historySearch = {
        diffPopup = {
            width = 0.8, -- float, 0 to 1
            height = 0.8,
            border = "single",
        },
        -- if trying to call `git log` on a shallow repository, automatically
        -- unshallow the repo by running `git fetch --unshallow`
        autoUnshallowIfNeeded = false,
    },
}

Appearance of Input Field

-- see: https://github.com/stevearc/dressing.nvim#configuration
require("dressing").setup {
    input = {
        insert_only = false, -- enable normal mode in the input field
        -- other appearance settings
    },
}

Use Telescope for selections

-- see: https://github.com/stevearc/dressing.nvim#configuration
require("dressing").setup {
    select = {
        backend = { "telescope" },
        -- other appearance settings
    },
}

Non-Goals

  • Become a full-fledged git client. Use neogit for that.
  • Add features available in gitsigns.nvim. tinygit is intended to complement gitsigns.nvim with some simple commands, not replace it.
  • UI Customization. Configure dressing.nvim for that.

Credits

About Me
In my day job, I am a sociologist studying the social mechanisms underlying the digital economy. For my PhD project, I investigate the governance of the app economy and how software ecosystems manage the tension between innovation and compatibility. If you are interested in this subject, feel free to get in touch.

Blog
I also occasionally blog about vim: Nano Tips for Vim

Profiles