A lightweight bundle of commands focused on swift and streamlined git operations.
- Interactive staging of hunks (parts of a file). Displays hunk diffs with proper syntax highlighting, and allows resetting or navigating to the hunk.
- Smart-commit: Open a popup to enter a commit message with syntax highlighting,
commit preview, automatic issue number insertion, and overlength indicators.
If there are no staged changes, stages all changes before doing so (
git add -A
). Optionally trigger agit push
afterward. - Quick commands for amend, stash, fixup, or undoing commits.
- Search issues & PRs. Open the selected issue or PR in the browser.
- Open the GitHub URL of the current line or selection.
- Explore the git history: Search the file for a string ("git pickaxe"), or examine the history of a function or line range. Displays the results in a diff view with syntax highlighting, correctly following file renamings.
- Statusline components:
git blame
of a file and branch state. - Streamlined workflow: operations are smartly combined to minimize friction. For instance, the smart-commit command combines staging, committing, and pushing, and searching the file history combines unshallowing, searching, and navigating diffs.
Hard requirements
- nvim 0.10 or higher
dressing.nvim
telescope
is required for interactive staging.- GitHub-related features require
curl
.
Optional/recommended requirements
- Treesitter parser for syntax highlighting:
TSInstall gitcommit
- nvim-notify OR snacks.nvim for the commit preview, issue number insertion, and various notifications.
- telescope.nvim OR fzf-lua for nicer UI when selecting commits, issues, or PRs.
-- lazy.nvim
{
"chrisgrieser/nvim-tinygit",
dependencies = "stevearc/dressing.nvim",
},
-- packer
use {
"chrisgrieser/nvim-tinygit",
requires = "stevearc/dressing.nvim",
}
- This feature requires telescope.nvim.
- This command stages hunks, that is, parts of a file instead of the
full file. It is roughly comparable to
git add -p
. - Use
<Space>
to (un)stage the hunk,<CR>
to go to the hunk, or<C-r
to reset the hunk (mappings customizable). Your regulartelescope
mappings also apply. - The size of the hunks is determined by the setting
staging.contextSize
. Larger context size is going to "merge" changes that are close to one another into one hunk. (As such, the hunks displayed are not 1:1 the same as the hunks fromgitsigns.nvim
.) A context size between 1 and 4 is recommended. - Limitations:
contextSize=0
(= no merging at all) is currently not supported.
require("tinygit").interactiveStaging()
- Open a commit popup, alongside a preview of what is going to be committed. If
there are no staged changes, stage all changes (
git add --all
) before the commit. - Input field contents of aborted commits are briefly kept, if you just want to fix a detail.
- 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. - Typing
#
inserts the most recent issue number,<Tab>
cycles through the issues (currently opt-in, see plugin configuration). - Only supports the commit subject line (no commit body).
-- values shown are the defaults
require("tinygit").smartCommit { pushIfClean = false, pullBeforePush = true }
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)
- Stage some hunks (changes) via
ga
. - Use
gc
to enter a commit message. - Repeat 1 and 2.
- 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.
Amending
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, stages all changes (git add --all
), likesmartCommit
.- Optionally runs
git push --force-with-lease
afterward, if the branch has diverged (that is, the amended commit was already pushed).
-- options default to `false`
require("tinygit").amendOnlyMsg { forcePushIfDiverged = false }
require("tinygit").amendNoEdit { forcePushIfDiverged = false, stageAllIfNothingStaged = true }
Fixup or Squash Commits
fixupCommit
lets you select a commit from the last X commits and runsgit commit --fixup
on the selected commit.- If there are no staged changes, stages all changes (
git add --all
), likesmartCommit
. - 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 conflicts.)
-- options show default values
require("tinygit").fixupCommit {
selectFromLastXCommits = 15,
squashInstead = false,
autoRebase = false,
}
require("tinygit").undoLastCommitOrAmend()
- Changes in the working directory are kept, but unstaged. (In the background,
this uses
git reset --mixed
.) - If there was a
push
operation done as a followup (such as.smartCommit { pushIfClean = false }
), the last commit is not undone.
Search issues & PRs
- Requires
curl
.
-- 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`,
-- open that issue/PR
require("tinygit").openIssueUnderCursor()
GitHub URL Opens 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 selected lines.
-- file|repo (default: file)
require("tinygit").githubUrl("file")
push
can be combined with other actions, depending on the options.createGitHubPr
opens a PR from the current branch browser. (This requires the repo to be a fork with sufficient information on the remote.)
-- options default to `false`
require("tinygit").push {
pullBefore = false,
forceWithLease = false,
createGitHubPr = false,
}
require("tinygit").createGitHubPr()
Search the git history of the current file. Select from the matching commits to open a popup with a diffview of the changes.
If the config history.autoUnshallowIfNeeded
is set to true
, will also
automatically un-shallow the repo if needed.
require("tinygit").fileHistory()
The type of history search depends on the mode .searchHistory
is called from:
- Normal mode: search history for a string (
git log -G
)- Correctly follows file renamings, and displays past file names in the commit selection.
- The search input is case-insensitive and supports regex.
- Leave the input field empty to display all commits that changed the current file.
- Visual mode: function history (
git log -L
).- The selected text is assumed to be the name of the function whose history you want to explore.
- 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.
- Caveat: for function history, git does not support to follow renamings of the file or function name.
- Visual line mode: line range history (
git log -L
).- Uses the selected lines as the line range.
- Caveat: for line history, git does not support to follow file renamings.
Keymaps in the diff popup
<Tab>
: show older commit<S-Tab>
: show newer commityh
: yank the commit hash to the system clipboardR
: restore file to state at commitn
/N
: go to the next/previous occurrence of the query (only file history)
Simple wrappers around git stash push
and git stash pop
.
require("tinygit").stashPush()
require("tinygit").stashPop()
Shows the message and date (git blame
) of the last commit that changed the
current file (not line).
require("tinygit.statusline").blame()
Tip
Some status line plugins also allow you to put components into the tabline or winbar. If your status line is too crowded, you can add the blame-component to one of those bars instead.
The component can be configured with the statusline.blame
options in the plugin
configuration.
Shows whether the local branch is ahead or behind of its remote counterpart.
(Note that this component does not run git fetch
for performance reasons, so
the information may not be up-to-date with remote changes.)
require("tinygit.statusline").branchState()
The setup
call is optional.
Note
Recently (2024-11-23), the config structure has been overhauled:
staging
→stage
commitMsg
→commit
commitMsg.commitPreview
→commit.preview
commitMsg.insertIssuesOnHash
→commit.insertIssuesOnHashSign
historySearch
→history
issueIcons
→github.icons
backdrop
→appearance.backdrop
mainIcon
→appearance.mainIcon
-- default config
require("tinygit").setup {
stage = { -- requires `telescope.nvim`
contextSize = 1, -- larger values "merge" hunks. 0 is not supported.
stagedIndicator = "",
keymaps = { -- insert & normal mode
stagingToggle = "<Space>", -- stage/unstage hunk
gotoHunk = "<CR>",
resetHunk = "<C-r>",
},
moveToNextHunkOnStagingToggle = false,
},
commit = {
preview = true, -- requires `nvim-notify` or `snacks.nvim`
spellcheck = false,
keepAbortedMsgSecs = 300,
inputFieldWidth = 72, -- `false` to use dressing.nvim config
conventionalCommits = {
enforce = false,
-- stylua: ignore
keywords = {
"fix", "feat", "chore", "docs", "refactor", "build", "test",
"perf", "style", "revert", "ci", "break", "improv",
},
},
insertIssuesOnHashSign = {
-- Typing `#` will insert the most recent open issue.
-- Requires `nvim-notify` or `snacks.nvim`.
enabled = false,
next = "<Tab>", -- insert & normal mode
prev = "<S-Tab>",
issuesToFetch = 20,
},
},
push = {
preventPushingFixupOrSquashCommits = true,
confirmationSound = true, -- currently macOS only, PRs welcome
-- Pushed commits contain references to issues, open those issues.
-- Not used when using force-push.
openReferencedIssues = false,
},
github = {
icons = {
openIssue = "🟢",
closedIssue = "🟣",
notPlannedIssue = "⚪",
openPR = "🟩",
mergedPR = "🟪",
draftPR = "⬜",
closedPR = "🟥",
},
},
history = {
diffPopup = {
width = 0.8, -- between 0-1
height = 0.8,
border = "single",
},
autoUnshallowIfNeeded = false,
},
appearance = {
mainIcon = "",
backdrop = {
enabled = true,
blend = 50, -- 0-100
},
},
statusline = {
blame = {
ignoreAuthors = {}, -- hide component if these authors (useful for bots)
hideAuthorNames = {}, -- show component, but hide names (useful for your own name)
maxMsgLen = 40,
icon = "ﰖ",
},
branchState = {
icons = {
ahead = "",
behind = "",
diverge = "",
},
},
},
}
The appearance of the commit preview and notifications is determined by nvim-notify or snacks.nvim respectively.
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.
I also occasionally blog about vim: Nano Tips for Vim