From 6fe24697d330ef2d836b17be4a595bad1d8da776 Mon Sep 17 00:00:00 2001 From: Aaron Hallaert Date: Tue, 4 Apr 2023 01:11:50 +0200 Subject: [PATCH] Fix first commit in preview --- lua/advanced_git_search/git_utils.lua | 143 +++++++++++++++++++++---- lua/advanced_git_search/init.lua | 8 +- lua/advanced_git_search/utils/file.lua | 13 +++ 3 files changed, 139 insertions(+), 25 deletions(-) diff --git a/lua/advanced_git_search/git_utils.lua b/lua/advanced_git_search/git_utils.lua index 101c110..1f03c72 100644 --- a/lua/advanced_git_search/git_utils.lua +++ b/lua/advanced_git_search/git_utils.lua @@ -188,44 +188,143 @@ M.git_log_grepper_on_file = function(bufnr) end, git_log_entry_maker) end -local determine_historic_file_name = function(commit_hash, bufnr) - local current_file_name = file.relative_path(bufnr) +local previous_commit_hash = function(commit_hash) + local command = "git rev-parse " .. commit_hash .. "~" + local handle = io.popen(command) + local output = handle:read("*a") + handle:close() - local command = "git log -M --diff-filter=R --follow --name-status --summary " - .. commit_hash - .. ".. -- " - .. current_file_name - .. " | grep ^R | tail -1 | cut -f2,2" + output = string.gsub(output, "\n", "") + return output +end + +local all_commit_hashes = function() + local command = "git rev-list --all" + local handle = io.popen(command) + local output = handle:read("*a") + handle:close() + return utils.split_string(output, "\n") +end + +local all_commit_hashes_touching_file = function(git_relative_file_path) + local command = "cd " + .. file.git_dir() + .. " && git log --all --follow --pretty=format:'%H' -- " + .. git_relative_file_path + local handle = io.popen(command) + local output = handle:read("*a") + handle:close() + + output = utils.split_string(output, "\n") + return output +end + +local file_exists_on_commit = function(commit_hash, git_relative_file_path) + local command = "cd " + .. file.git_dir() + .. " && git ls-tree --name-only " + .. commit_hash + .. " -- " + .. git_relative_file_path local handle = io.popen(command) local output = handle:read("*a") handle:close() output = string.gsub(output, "\n", "") - if output == "" then - output = file.git_relative_path(bufnr) + + return output ~= "" +end + +local file_name_on_commit = function(commit_hash, git_relative_file_path) + if file_exists_on_commit(commit_hash, git_relative_file_path) then + return git_relative_file_path + end + + -- FIXME: this is a very naive implementation, but it always returns the + -- correct filename for each commit (even if the commit didn't touch the file) + + -- first find index in all_commit_hashes + local all_hashes = all_commit_hashes() + local index = 0 + for i, hash in ipairs(all_hashes) do + -- compare on first 7 chars + if string.sub(hash, 1, 7) == string.sub(commit_hash, 1, 7) then + index = i + break + end + end + + -- then find the first commit that has a different file name + local touched_hashes = + all_commit_hashes_touching_file(git_relative_file_path) + local last_touched_hash = nil + for i = index, #all_hashes do + local hash = all_hashes[i] + -- search the hash in touched_hashes + for _, touched_hash in ipairs(touched_hashes) do + if string.sub(touched_hash, 1, 7) == string.sub(hash, 1, 7) then + last_touched_hash = touched_hash + break + end + end + + -- print("searching next") + if last_touched_hash ~= nil then + break + end end - -- output is relative to git root + if last_touched_hash == nil then + return nil + end + + local command = "cd " + .. file.git_dir() + .. " && " + .. "git --no-pager log -M --follow --pretty=format:'%H' --name-only " + .. last_touched_hash + .. "~.. -- " + .. git_relative_file_path + .. " | tail -1" + + local handle = io.popen(command) + local output = handle:read("*a") + handle:close() + + output = string.gsub(output, "\n", "") + return output end M.git_diff_previewer_file = function(bufnr) return previewers.new_termopen_previewer({ get_command = function(entry) + local filename_on_head = file.git_relative_path(bufnr) + local commit_hash = entry.opts.commit_hash - local prev_commit = string.format("%s~", commit_hash) - return git_diff_command({ - "git", - "diff", - prev_commit - .. ":" - .. determine_historic_file_name(prev_commit, bufnr), - commit_hash - .. ":" - .. determine_historic_file_name(commit_hash, bufnr), - }) + local prev_commit = previous_commit_hash(commit_hash) + local curr_name = file_name_on_commit(commit_hash, filename_on_head) + local prev_name = file_name_on_commit(prev_commit, filename_on_head) + + if prev_name ~= nil then + return git_diff_command({ + "git", + "diff", + prev_commit .. ":" .. prev_name, + commit_hash .. ":" .. curr_name, + }) + else + return git_diff_command({ + "git", + "diff", + prev_commit, + commit_hash, + "--", + file.git_relative_path_to_relative_path(curr_name), + }) + end end, }) end @@ -282,7 +381,7 @@ M.current_branch = function() return output end -M.determine_historic_file_name = determine_historic_file_name +M.file_name_on_commit = file_name_on_commit M.git_diff_command = git_diff_command return M diff --git a/lua/advanced_git_search/init.lua b/lua/advanced_git_search/init.lua index 788bf23..fda28e9 100644 --- a/lua/advanced_git_search/init.lua +++ b/lua/advanced_git_search/init.lua @@ -108,7 +108,7 @@ M.diff_branch_file = function() gu.open_diff_view( branch, - gu.determine_historic_file_name(branch, file_name) + gu.file_name_on_commit(branch, file_name) ) end) @@ -300,8 +300,10 @@ M.diff_commit_file = function() actions.close(prompt_bufnr) local selection = action_state.get_selected_entry() local commit_hash = selection.opts.commit_hash - local old_file_name = - gu.determine_historic_file_name(commit_hash, bufnr) + local old_file_name = gu.file_name_on_commit( + commit_hash, + file.git_relative_path(bufnr) + ) gu.open_diff_view(commit_hash, old_file_name) end) diff --git a/lua/advanced_git_search/utils/file.lua b/lua/advanced_git_search/utils/file.lua index c678692..7f7871c 100644 --- a/lua/advanced_git_search/utils/file.lua +++ b/lua/advanced_git_search/utils/file.lua @@ -8,6 +8,10 @@ M.relative_path = function(bufnr) return vim.fn.expand("#" .. bufnr .. ":~:.") end +M.git_dir = function() + return M.find_first_ancestor_dir_or_file(vim.fn.getcwd(), ".git") +end + M.file_name = function(bufnr) return vim.fn.expand("#" .. bufnr .. ":t") end @@ -47,6 +51,15 @@ M.git_relative_path = function(bufnr) end end +M.git_relative_path_to_relative_path = function(git_relative_path) + local git_dir = M.find_first_ancestor_dir_or_file(vim.fn.getcwd(), ".git") + local project_dir = vim.fn.getcwd() + + local absolute_path = git_dir .. "/" .. git_relative_path + project_dir = utils.escape_chars(project_dir .. "/") + return string.gsub(absolute_path, project_dir, "") +end + M.path = (function() local is_windows = uv.os_uname().version:match("Windows")