Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions doc/neogit.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2186,17 +2186,28 @@ Mappings (normal mode):
Note: it can fail if the file no longer exists or it can take
you to a wrong location in the file if the lines have been
moved around.

• `<cr>` On a diff hunk, open the file contents from the commit in a
temporary read-only tab, or the parent revision for deleted
lines; on a filepath line, jump to that file's diff section
within the buffer.

• `o` Open the commit in the configured git service (requires
Neovim >= 0.10 for |vim.ui.open|).
• `{` / `}` Jump to previous/next hunk or diff header.
• `q` / `<esc>` Close the commit buffer (keys are configurable via
|neogit_setup_mappings| for the commit view).
• `Y` Yank the current commit hash to the clipboard.

• `{` / `}` Jump to previous/next hunk or diff header.

• `q` / `<esc>` Close the commit buffer (keys are configurable via
|neogit_setup_mappings| for the commit view).

• `Y` Opens the Yank popup, allowing copying of different details
from the commit.

note: To copy only a specific hunk's diff, place the
cursor within that hunk.

• `za` / `<tab>` Toggle folding for the current section.

• Popup shortcuts honour |neogit_setup_mappings|, default keys:
- `A` Cherry-pick, `b` Branch, `B` Bisect, `c` Commit, `d` Diff,
`f` Fetch, `i` Ignore, `l` Log, `m` Merge, `p` Pull, `P` Push,
Expand Down
50 changes: 45 additions & 5 deletions lua/neogit/buffers/commit_view/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ local commit_view_maps = require("neogit.config").get_reversed_commit_view_maps(
local status_maps = require("neogit.config").get_reversed_status_maps()
local notification = require("neogit.lib.notification")
local jump = require("neogit.lib.jump")
local util = require("neogit.lib.util")

local api = vim.api

Expand Down Expand Up @@ -425,11 +426,50 @@ function M:open(kind)
["<esc>"] = function()
self:close()
end,
[status_maps["YankSelected"]] = function()
local yank = string.format("'%s'", self.commit_info.oid)
vim.cmd.let("@+=" .. yank)
vim.cmd.echo(yank)
end,
[status_maps["YankSelected"]] = popups.open("yank", function(p)
-- If the cursor is over a specific hunk, just copy that diff.
local diff
local c = self.buffer.ui:get_component_under_cursor(function(c)
return c.options.hunk ~= nil
end)

if c then
local hunks = util.flat_map(self.commit_info.diffs, function(diff)
return diff.hunks
end)

for _, hunk in ipairs(hunks) do
if hunk.hash == c.options.hunk.hash then
diff = table.concat(util.merge({ hunk.line }, hunk.lines), "\n")
break
end
end
end

-- If for some reason we don't find the specific hunk, or there isn't one, fall-back to the entire patch.
if not diff then
diff = table.concat(
vim.tbl_map(function(diff)
return table.concat(diff.lines, "\n")
end, self.commit_info.diffs),
"\n"
)
end

p {
hash = self.commit_info.oid,
subject = self.commit_info.description[1],
message = table.concat(self.commit_info.description, "\n"),
body = table.concat(
util.slice(self.commit_info.description, 2, #self.commit_info.description),
"\n"
),
url = git.remote.commit_url(self.commit_info.oid),
diff = diff,
author = ("%s <%s>"):format(self.commit_info.author_name, self.commit_info.author_email),
tags = table.concat(git.tag.for_commit(self.commit_info.oid), ", "),
}
end),
[status_maps["Toggle"]] = function()
pcall(vim.cmd, "normal! za")
end,
Expand Down
8 changes: 8 additions & 0 deletions lua/neogit/lib/git/cli.lua
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ end
---@field n self
---@field list self
---@field delete self
---@field points_at fun(oid: string): self

---@class GitCommandRebase: GitCommandBuilder
---@field interactive self
Expand Down Expand Up @@ -557,6 +558,13 @@ local configurations = {
list = "--list",
delete = "--delete",
},
aliases = {
points_at = function(tbl)
return function(oid)
return tbl.args("--points-at", oid)
end
end,
},
},

rebase = config {
Expand Down
2 changes: 1 addition & 1 deletion lua/neogit/lib/git/log.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ local commit_header_pat = "([| ]*)(%*?)([| ]*)commit (%w+)"
---@field committer_name string the name of the committer
---@field committer_email string the email of the committer
---@field committer_date string when the committer committed
---@field description string a list of lines
---@field description string[] a list of lines
---@field commit_arg string the passed argument of the git command
---@field subject string
---@field parent string
Expand Down
7 changes: 7 additions & 0 deletions lua/neogit/lib/git/tag.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ function M.list_remote(remote)
return git.cli["ls-remote"].tags.args(remote).call({ hidden = true }).stdout
end

---Find tags that point at an object ID
---@param oid string
---@return string[]
function M.for_commit(oid)
return git.cli.tag.points_at(oid).call({ hidden = true }).stdout
end

local tag_pattern = "(.-)%-([0-9]+)%-g%x+$"

function M.register(meta)
Expand Down
25 changes: 25 additions & 0 deletions lua/neogit/popups/yank/actions.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
local notification = require("neogit.lib.notification")
local M = {}

---@param key string
---@return fun(popup: PopupData)
local function yank(key)
return function(popup)
local data = popup:get_env(key)
if data then
vim.cmd.let(("@+='%s'"):format(data))
notification.info(("Copied %s to clipboard."):format(key))
end
end
end

M.hash = yank("hash")
M.subject = yank("subject")
M.message = yank("message")
M.body = yank("body")
M.url = yank("url")
M.diff = yank("diff")
M.author = yank("author")
M.tags = yank("tags")

return M
27 changes: 27 additions & 0 deletions lua/neogit/popups/yank/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
local popup = require("neogit.lib.popup")
local actions = require("neogit.popups.yank.actions")

local M = {}

function M.create(env)
local p = popup
.builder()
:name("NeogitYankPopup")
:group_heading("Yank Commit info")
:action("Y", "Hash", actions.hash)
:action("s", "Subject", actions.subject)
:action("m", "Message (subject and body)", actions.message)
:action("b", "Message body", actions.body)
:action_if(env.url, "u", "URL", actions.url)
:action("d", "Diff", actions.diff)
:action("a", "Author", actions.author)
:action_if(env.tags ~= "", "t", "Tags", actions.tags)
:env(env)
:build()

p:show()

return p
end

return M
57 changes: 55 additions & 2 deletions spec/buffers/commit_buffer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,62 @@
expect(nvim.filetype).to eq("NeogitLogView")
end

it "can yank OID" do
it "can open Yank popup" do
nvim.keys("Y")
expect(nvim.screen.last.strip).to match(/\A[a-f0-9]{40}\z/)
expect(nvim.filetype).to eq("NeogitPopup")
end

if ENV["CI"].nil? # Fails in GHA :'(
it "can yank oid" do
nvim.keys("YY")
yank = nvim.cmd("echo @*").first
expect(yank).to match(/[0-9a-f]{40}/)
end

it "can yank author" do
nvim.keys("Ya")
yank = nvim.cmd("echo @*").first
expect(yank).to eq("tester <test@example.com>")
end

it "can yank subject" do
nvim.keys("Ys")
yank = nvim.cmd("echo @*").first
expect(yank).to eq("Initial commit")
end

it "can yank message" do
nvim.keys("Ym")
yank = nvim.cmd("echo @*")
expect(yank).to contain_exactly("Initial commit\n", "commit message")
end

it "can yank body" do
nvim.keys("Yb")
yank = nvim.cmd("echo @*").first
expect(yank).to eq("commit message")
end

it "can yank diff" do
nvim.keys("Yd")
yank = nvim.cmd("echo @*")
expect(yank).to contain_exactly("@@ -0,0 +1 @@\n", "+hello, world")
end

it "can yank tag" do
git.add_tag("test-tag", "HEAD")
nvim.keys("Yt")
yank = nvim.cmd("echo @*").first
expect(yank).to eq("test-tag")
end

it "can yank tags" do
git.add_tag("test-tag-a", "HEAD")
git.add_tag("test-tag-b", "HEAD")
nvim.keys("Yt")
yank = nvim.cmd("echo @*").first
expect(yank).to eq("test-tag-a, test-tag-b")
end
end

it "can open the bisect popup" do
Expand Down
2 changes: 1 addition & 1 deletion spec/popups/commit_popup_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
nvim.keys("w")
nvim.keys("cc")
nvim.keys("reworded!<esc>:w<cr>q")
expect(git.log(1).entries.first.message).to eq("reworded!")
expect(git.log(1).entries.first.message).to eq("reworded!\ncommit message")
end
end

Expand Down
6 changes: 3 additions & 3 deletions spec/support/context/git.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
let(:git) { Git.open(Dir.pwd) }

before do
system("touch testfile")

git.config("user.email", "test@example.com")
git.config("user.name", "tester")

create_file("testfile", "hello, world\n")
git.add("testfile")
git.commit("Initial commit")
git.commit("Initial commit\ncommit message")
end
end
Loading