Skip to content

Commit 035c5b9

Browse files
authored
Initial commit
Just manually tested and written in a hurry. Readme and License are generated with llms. WIP
0 parents  commit 035c5b9

6 files changed

Lines changed: 289 additions & 0 deletions

File tree

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Salanoiod
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# gitlogdiff.nvim
2+
3+
A tiny Neovim plugin that shows a simple, keyboard‑driven list of recent Git commits and lets you diff them quickly via Diffview.
4+
5+
Works great for: “show me the last N commits, let me pick one (or two) and open the diff”.
6+
7+
## Features
8+
9+
- Lists recent commits using `git log` (configurable `max_count`)
10+
- Toggle selection with space, navigate with `j/k`
11+
- Press Enter to open diffs in [diffview.nvim]
12+
- 1 selected commit → diff that commit against its parent (`<hash>^..<hash>`)
13+
- 2 selected commits → diff between the two commits
14+
15+
## Requirements
16+
17+
- Neovim ≥ 0.10 (uses `vim.system`)
18+
- Git available on your `$PATH`
19+
- Dependencies:
20+
- [sindrets/diffview.nvim]
21+
- [folke/snacks.nvim]
22+
23+
## Installation
24+
25+
### lazy.nvim
26+
27+
```lua
28+
{
29+
"Salanoiod/gitlogdiff.nvim",
30+
dependencies = {
31+
"sindrets/diffview.nvim",
32+
"folke/snacks.nvim",
33+
},
34+
cmd = "GitLogDiff", -- lazy-load on command
35+
opts = {
36+
max_count = 300,
37+
},
38+
}
39+
```
40+
41+
### packer.nvim
42+
43+
```lua
44+
use({
45+
"Salanoiod/gitlogdiff.nvim",
46+
requires = {
47+
"sindrets/diffview.nvim",
48+
"folke/snacks.nvim",
49+
},
50+
config = function()
51+
require("gitlogdiff").setup({
52+
max_count = 300,
53+
})
54+
end,
55+
})
56+
```
57+
58+
Note: This plugin defines the `:GitLogDiff` command on load. If your plugin manager pre-defines lazy command stubs, `gitlogdiff.nvim` will safely overwrite them (we create the command with `force = true`).
59+
60+
## Usage
61+
62+
- Run `:GitLogDiff` inside a Git repository
63+
- Navigate with `j/k`
64+
- Toggle selection with `<space>`
65+
- Press `<CR>` to open diffs in Diffview
66+
- Press `q` to close the list
67+
68+
## Configuration
69+
70+
```lua
71+
require("gitlogdiff").setup({
72+
max_count = 300, -- how many commits to list
73+
})
74+
```
75+
76+
## Troubleshooting
77+
78+
- “No git commits found”: you are likely not in a Git repo (or `max_count` is 0)
79+
- “git log failed …”: check that `git` is installed and available in `$PATH`
80+
81+
## Roadmap / Notes
82+
83+
- Currently, selecting two commits diffs A..B. Selecting more than two is not supported and may yield unexpected results.
84+
85+
## License
86+
87+
MIT — see [LICENSE](./LICENSE).
88+
89+
[sindrets/diffview.nvim]: https://github.com/sindrets/diffview.nvim
90+
[folke/snacks.nvim]: https://github.com/folke/snacks.nvim

actions.lua

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
local M = {}
2+
3+
local function diffview_open(args)
4+
vim.cmd("DiffviewOpen " .. table.concat(args, " "))
5+
-- vim.cmd("CodeDiff " .. table.concat(args, " "))
6+
end
7+
8+
function M.show_selected(hashes)
9+
if #hashes == 0 then
10+
vim.notify("No commits selected", vim.log.levels.INFO)
11+
return
12+
end
13+
14+
local ordered = vim.deepcopy(hashes)
15+
table.sort(ordered, function(a, b)
16+
return a < b
17+
end)
18+
19+
if #ordered == 1 then
20+
diffview_open({ ordered[1] .. "^.." .. ordered[1] })
21+
return
22+
end
23+
24+
diffview_open(ordered)
25+
end
26+
27+
return M

init.lua

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
local M = {}
2+
3+
M.config = {
4+
max_count = 300,
5+
}
6+
7+
function M.setup(opts)
8+
M.config = vim.tbl_deep_extend("force", M.config, opts or {})
9+
end
10+
11+
function M.open()
12+
require("gitlogdiff.log").get_commits(function(commits)
13+
if #commits == 0 then
14+
vim.notify("No git commits found", vim.log.levels.WARN)
15+
return
16+
end
17+
require("gitlogdiff.ui").open(commits)
18+
end)
19+
end
20+
21+
vim.api.nvim_create_user_command("GitLogDiff", M.open, {
22+
force = true,
23+
desc = "Open a picker to select commits and diff them",
24+
})
25+
26+
return M

log.lua

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
local M = {}
2+
3+
function M.get_commits(cb)
4+
local cfg = require("gitlogdiff").config or {}
5+
local max_count = tonumber(cfg.max_count)
6+
7+
local args = {
8+
"git", "log",
9+
string.format("--max-count=%d", max_count),
10+
"--pretty=format:%h %ad %s",
11+
"--date=short",
12+
"--color=never",
13+
}
14+
15+
vim.system(args, { text = true }, function(res)
16+
if res.code ~= 0 then
17+
local err = (res.stderr or ""):gsub("\n$", "")
18+
vim.schedule(function()
19+
vim.notify(
20+
(#err > 0) and ("git log failed: " .. err) or "git log failed (are you in a git repo?)",
21+
vim.log.levels.WARN
22+
)
23+
cb({})
24+
end)
25+
return
26+
end
27+
28+
local commits = vim.split(res.stdout, "\n", { trimempty = true })
29+
vim.schedule(function()
30+
cb(commits)
31+
end)
32+
end)
33+
end
34+
35+
return M

ui.lua

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
local api = vim.api
2+
local snacks = require("snacks")
3+
4+
local M = {}
5+
6+
M.state = {
7+
buf = nil,
8+
win = nil,
9+
commits = {},
10+
selected = {},
11+
cursor = 1,
12+
}
13+
14+
function M.open(commits)
15+
M.state.commits = commits
16+
M.state.selected = {}
17+
M.state.cursor = 1
18+
19+
M.state.buf = api.nvim_create_buf(false, true)
20+
vim.bo[M.state.buf].buftype = "nofile"
21+
vim.bo[M.state.buf].bufhidden = "wipe"
22+
vim.bo[M.state.buf].swapfile = false
23+
vim.bo[M.state.buf].filetype = "gitlogdiff"
24+
25+
M.state.win_obj = snacks.win({
26+
buf = M.state.buf,
27+
width = 90,
28+
height = math.min(#commits + 2, 30),
29+
title = "Git Commits",
30+
border = "rounded",
31+
})
32+
M.state.win = M.state.win_obj.win
33+
vim.wo[M.state.win].cursorline = true
34+
35+
M.render()
36+
M.keymaps()
37+
end
38+
39+
function M.render()
40+
local lines = {}
41+
for i, c in ipairs(M.state.commits) do
42+
local mark = M.state.selected[i] and "" or ""
43+
table.insert(lines, mark .. " " .. c)
44+
end
45+
46+
vim.bo[M.state.buf].modifiable = true
47+
api.nvim_buf_set_lines(M.state.buf, 0, -1, false, lines)
48+
vim.bo[M.state.buf].modifiable = false
49+
api.nvim_win_set_cursor(M.state.win, { M.state.cursor, 0 })
50+
end
51+
52+
function M.move(delta)
53+
M.state.cursor = math.max(1, math.min(#M.state.commits, M.state.cursor + delta))
54+
M.render()
55+
end
56+
57+
function M.toggle()
58+
local i = M.state.cursor
59+
M.state.selected[i] = not M.state.selected[i]
60+
M.render()
61+
end
62+
63+
function M.get_selected_hashes()
64+
local hashes = {}
65+
for i, ok in pairs(M.state.selected) do
66+
if ok then
67+
local h = M.state.commits[i]:match("^(%w+)")
68+
table.insert(hashes, h)
69+
end
70+
end
71+
return hashes
72+
end
73+
74+
function M.keymaps()
75+
local opts = { buffer = M.state.buf, silent = true }
76+
77+
vim.keymap.set("n", "j", function() M.move(1) end, opts)
78+
vim.keymap.set("n", "k", function() M.move(-1) end, opts)
79+
vim.keymap.set("n", "<space>", M.toggle, opts)
80+
81+
vim.keymap.set("n", "<CR>", function()
82+
require("gitlogdiff.actions").show_selected(M.get_selected_hashes())
83+
end, opts)
84+
85+
vim.keymap.set("n", "q", function()
86+
M.state.win_obj:close()
87+
end, opts)
88+
end
89+
90+
return M

0 commit comments

Comments
 (0)