Skip to content
Open
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
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ _Grep behaviour_: filter on added, updated or removed code (log content: `-G` op
- `<C-e>` show the entire commit for all files in neovim with diff plugin
- `<C-o>` Open the selected commit in the browser
- `<C-y>` copy the commit hash to clipboard
- `<C-p>` copy the commit patch for a single commit or a range to clipboard (currently telescope-only)
- `<C-w>` cycle author, date and both in entry

### 2. search_log_content_file -- Search in file log content
Expand All @@ -50,6 +51,7 @@ _Grep behaviour_: filter on added, updated or removed code (log content: `-G` op
- `<C-e>` show the entire commit for all files in neovim with diff plugin
- `<C-o>` Open the selected commit in the browser
- `<C-y>` copy the commit hash to clipboard
- `<C-p>` copy the commit patch for a single commit or a range to clipboard (currently telescope-only)
- `<C-w>` cycle author, date and both in entry

### 3. diff_commit_file -- Diff current file with commit
Expand All @@ -65,6 +67,7 @@ _Grep behaviour_: filter on commit message.
- `<C-e>` show the entire commit for all files in neovim with diff plugin
- `<C-o>` Open the selected commit in the browser
- `<C-y>` copy the commit hash to clipboard
- `<C-p>` copy the commit patch for a single commit or a range to clipboard (currently telescope-only)
- `<C-w>` cycle author, date and both in entry

### 4. diff_commit_line -- Diff current file with selected line history
Expand All @@ -82,6 +85,7 @@ _Grep behaviour_: filter on commit message.
- `<C-e>` show the entire commit for all files in neovim with diff plugin
- `<C-o>` opens a the selected commit in the browser
- `<C-y>` copy the commit hash to clipboard
- `<C-p>` copy the commit patch for a single commit or a range to clipboard (currently telescope-only)
- `<C-w>` cycle author, date and both in entry

### 5. diff_branch_file -- Diff file with branch
Expand Down Expand Up @@ -159,6 +163,7 @@ Enable `show_builtin_git_pickers` to additionally show builtin git pickers.
toggle_date_author = "<C-w>",
open_commit_in_browser = "<C-o>",
copy_commit_hash = "<C-y>",
copy_commit_patch = "<C-p>", -- telescope only
show_entire_commit = "<C-e>",
}

Expand Down Expand Up @@ -273,7 +278,7 @@ To complete this snippet, see [Config](#Config) and [Dependencies](#Dependencies
},
}
```

</details>

<details>
Expand Down
93 changes: 93 additions & 0 deletions lua/advanced_git_search/actions.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
local config = require("advanced_git_search.utils.config")
local command_utils = require("advanced_git_search.commands.utils")
local command_util = require("advanced_git_search.utils.command")
local file_utils = require("advanced_git_search.utils.file")
local git_utils = require("advanced_git_search.utils.git")

local M = {}

Expand Down Expand Up @@ -52,6 +56,33 @@ M.copy_to_clipboard = function(commit_hash)
vim.fn.setreg("*", commit_hash)
end

---General action: Copy commit patch to system clipboard
---@param commit_hash string
---@param bufnr? number
M.copy_patch_to_clipboard = function(commit_hash, bufnr)
local command = { "git", "show" }
command = command_utils.format_git_diff_command(command)
table.insert(command, commit_hash)

if bufnr ~= nil then
local filename = file_utils.git_relative_path(bufnr)
local commit_file = git_utils.file_name_on_commit(commit_hash, filename)
table.insert(command, "--")
table.insert(command, commit_file or filename)
end

local patch = command_util.execute(table.concat(command, " "))

vim.notify(
"Copied commit patch " .. commit_hash .. " to clipboard",
vim.log.levels.INFO,
{ title = "Advanced Git Search" }
)

vim.fn.setreg("+", patch)
vim.fn.setreg("*", patch)
end

---General action: Open commit in browser
---@param commit_hash string
M.open_in_browser = function(commit_hash)
Expand All @@ -64,4 +95,66 @@ M.checkout_commit = function(commit_hash)
vim.api.nvim_command(":!git checkout " .. commit_hash)
end

--------------------------------------------------------------------------------
-- Range patch support (A..B), repo-wide or file-scoped with rename follow
--------------------------------------------------------------------------------
local function build_range_diff_cmd(start_hash, end_hash, bufnr)
local cmd = { "git", "diff" }
cmd = command_utils.format_git_diff_command(cmd)

if bufnr == nil then
table.insert(cmd, start_hash .. ".." .. end_hash)
return table.concat(cmd, " ")
end

local head_rel = file_utils.git_relative_path(bufnr)
local left_name = git_utils.file_name_on_commit(start_hash, head_rel)
local right_name = git_utils.file_name_on_commit(end_hash, head_rel)

if left_name ~= nil and right_name ~= nil then
table.insert(cmd, start_hash .. ":" .. left_name)
table.insert(cmd, end_hash .. ":" .. right_name)
return table.concat(cmd, " ")
end

table.insert(cmd, start_hash)
table.insert(cmd, end_hash)
table.insert(cmd, "--")
table.insert(cmd, file_utils.relative_path(bufnr))
return table.concat(cmd, " ")
end

M.copy_range_patch_to_clipboard = function(start_hash, end_hash, bufnr)
if not start_hash or not end_hash or start_hash == "" or end_hash == "" then
vim.notify(
"Need two commits selected to copy a range patch",
vim.log.levels.WARN,
{ title = "Advanced Git Search" }
)
return
end
local shell = build_range_diff_cmd(start_hash, end_hash, bufnr)
local patch = command_util.execute(shell)
if patch == "" then
vim.notify(
"No diff between " .. start_hash .. " and " .. end_hash,
vim.log.levels.INFO,
{ title = "Advanced Git Search" }
)
return
end
vim.fn.setreg("+", patch)
vim.fn.setreg("*", patch)
local scope = bufnr and " (file)" or ""
vim.notify(
("Copied patch %s..%s%s to clipboard"):format(
start_hash,
end_hash,
scope
),
vim.log.levels.INFO,
{ title = "Advanced Git Search" }
)
end

return M
34 changes: 34 additions & 0 deletions lua/advanced_git_search/telescope/mappings/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,40 @@ M.copy_commit_hash_to_clipboard = function(map)
omnimap(map, config.get_keymap("copy_commit_hash"), copy_commit_hash)
end

-- SMART: <C-p> single-or-range patch (uses multi-selection if 2+)
local copy_patch_smart = function(opts)
return function(prompt_bufnr)
local picker = action_state.get_current_picker(prompt_bufnr)
local multi = picker.get_multi_selection
and picker:get_multi_selection()
or {}
if multi and #multi >= 2 then
local first_hash = multi[1].opts.commit_hash
local last_hash = multi[#multi].opts.commit_hash

-- Ensure commits are in chronological order (older..newer)
local start_hash, end_hash = git_utils.order_commits_chronologically(first_hash, last_hash)

global_actions.copy_range_patch_to_clipboard(
start_hash,
end_hash,
opts and opts.bufnr
)
return
end
-- fallback: single selected entry
local selection = action_state.get_selected_entry()
local commit_hash = selection.opts.commit_hash

global_actions.copy_patch_to_clipboard(commit_hash, opts and opts.bufnr)
end
end

--- copy commit patch to clipboard with <C-p>
M.copy_commit_patch_to_clipboard = function(map, opts)
omnimap(map, config.get_keymap("copy_commit_patch"), copy_patch_smart(opts))
end

-------------------------------------------------------------------------------
local checkout = function(prompt_bufnr)
actions.close(prompt_bufnr)
Expand Down
9 changes: 7 additions & 2 deletions lua/advanced_git_search/telescope/pickers/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ M.diff_commit_line = function()
)
telescope_ags_mappings.open_selected_commit_in_browser(map)
telescope_ags_mappings.copy_commit_hash_to_clipboard(map)
telescope_ags_mappings.copy_commit_patch_to_clipboard(map, { bufnr = bufnr })
telescope_ags_mappings.show_entire_commit(map)
telescope_ags_mappings.toggle_entry_value(map)
return true
Expand Down Expand Up @@ -139,6 +140,7 @@ M.search_log_content = function()
)
telescope_ags_mappings.open_selected_commit_in_browser(map)
telescope_ags_mappings.copy_commit_hash_to_clipboard(map)
telescope_ags_mappings.copy_commit_patch_to_clipboard(map)
telescope_ags_mappings.show_entire_commit(map)
telescope_ags_mappings.toggle_entry_value(map)
return true
Expand All @@ -155,6 +157,7 @@ M.search_log_content_file = function()
-- local relative_file_name = vim.fn.expand("%:~:.")

local theme_opts = config.telescope_theme("search_log_content_file")
local bufnr = vim.fn.bufnr()

-- git log -L741,751:'app/models/patients/patient.rb' \
-- --format='%C(auto)%h \t %as \t %C(green)%an _ %Creset %s'
Expand All @@ -164,17 +167,18 @@ M.search_log_content_file = function()
results_title = "Commits",
prompt_title = "Git log content (added, removed or updated text in this file)",
finder = telescope_ags_finders.git_log_content_finder({
bufnr = vim.fn.bufnr(),
bufnr = bufnr,
}),
previewer = telescope_ags_previewers.git_diff_content_previewer({
bufnr = vim.fn.bufnr(),
bufnr = bufnr,
}),
attach_mappings = function(_, map)
telescope_ags_mappings.open_diff_view_current_file_selected_commit(
map
)
telescope_ags_mappings.open_selected_commit_in_browser(map)
telescope_ags_mappings.copy_commit_hash_to_clipboard(map)
telescope_ags_mappings.copy_commit_patch_to_clipboard(map, { bufnr = bufnr })
telescope_ags_mappings.show_entire_commit(map)
telescope_ags_mappings.toggle_entry_value(map)

Expand Down Expand Up @@ -210,6 +214,7 @@ M.diff_commit_file = function()
telescope_ags_mappings.toggle_entry_value(map)
telescope_ags_mappings.open_selected_commit_in_browser(map)
telescope_ags_mappings.copy_commit_hash_to_clipboard(map)
telescope_ags_mappings.copy_commit_patch_to_clipboard(map, { bufnr = bufnr })

return true
end,
Expand Down
1 change: 1 addition & 0 deletions lua/advanced_git_search/utils/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ local function get_keymaps()
toggle_date_author = "<C-w>",
open_commit_in_browser = "<C-o>",
copy_commit_hash = "<C-y>",
copy_commit_patch = "<C-p>",
show_entire_commit = "<C-e>",
}
keymaps = vim.tbl_extend("force", keymaps, config["keymaps"] or {})
Expand Down
27 changes: 27 additions & 0 deletions lua/advanced_git_search/utils/git.lua
Original file line number Diff line number Diff line change
Expand Up @@ -192,4 +192,31 @@ M.current_branch = function()
return string.gsub(output, "\n", "")
end

M.order_commits_chronologically = function(a, b)
local repo = file.git_dir()

local function run(cmd)
local r = vim.system(cmd):wait()
return r.code, (r.stdout or ""):gsub("%s+$","")
end

local function is_ancestor(x, y)
local code = run({ "git", "-C", repo, "merge-base", "--is-ancestor", x, y })
return code == 0
end

if is_ancestor(a, b) then return a, b end
if is_ancestor(b, a) then return b, a end

-- Divergent: use topo order (first visited is "newer")
local code, first = run({ "git", "-C", repo, "rev-list", "--topo-order", "--max-count=1", a, b })
if code == 0 and first ~= "" then
return (first == a) and b or a, (first == a) and a or b
end

-- Final fallback: Git’s date-aware order (parents before children)
local code2, first2 = run({ "git", "-C", repo, "rev-list", "--date-order", "--max-count=1", a, b })
return (first2 == a) and b or a, (first2 == a) and a or b
end

return M
Loading