Skip to content

Commit db3abca

Browse files
committed
WIP
1 parent 2ed2ef7 commit db3abca

File tree

3 files changed

+123
-48
lines changed

3 files changed

+123
-48
lines changed

lua/advanced_git_search/git_utils.lua

Lines changed: 109 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -198,83 +198,147 @@ local previous_commit_hash = function(commit_hash)
198198
return output
199199
end
200200

201-
--- Determine the historic file name for a given commit hash and buffer number
202-
--- @param commit_hash string
203-
--- @param bufnr number
204-
--- @return string | nil file_path historic file name relative to git root
205-
local determine_historic_file_name = function(commit_hash, bufnr)
206-
local current_file_name = file.git_relative_path(bufnr)
201+
local all_commit_hashes = function()
202+
local command = "git rev-list --all"
203+
local handle = io.popen(command)
204+
local output = handle:read("*a")
205+
handle:close()
206+
207+
return utils.split_string(output, "\n")
208+
end
207209

210+
local all_commit_hashes_touching_file = function(git_relative_file_path)
208211
local command = "cd "
209212
.. file.git_dir()
210-
.. " && "
211-
.. "git --no-pager log -M --follow --name-status --oneline "
212-
.. commit_hash
213-
.. "~.. -- "
214-
.. current_file_name
215-
.. " | tail -2"
213+
.. " && git log --all --follow --pretty=format:'%H' -- "
214+
.. git_relative_file_path
215+
local handle = io.popen(command)
216+
local output = handle:read("*a")
217+
handle:close()
218+
219+
output = utils.split_string(output, "\n")
220+
return output
221+
end
216222

223+
local is_file_renamed = function(git_relative_file_path)
224+
local command = "cd "
225+
.. file.git_dir()
226+
.. " && git log --all --follow --diff-filter=R --pretty=format:'%H' -- "
227+
.. git_relative_file_path
217228
local handle = io.popen(command)
218229
local output = handle:read("*a")
219230
handle:close()
220231

221-
-- first line contains the commit_hash and message
222-
-- second line contains the file status and file name (when rename, 2 filenames are present)
223232
output = utils.split_string(output, "\n")
233+
return #output > 0
234+
end
224235

225-
if output[1] == nil or output[2] == nil then
226-
return nil
236+
local file_exists_on_commit = function(commit_hash, git_relative_file_path)
237+
local command = "cd "
238+
.. file.git_dir()
239+
.. " && git ls-tree --name-only "
240+
.. commit_hash
241+
.. " -- "
242+
.. git_relative_file_path
243+
local handle = io.popen(command)
244+
local output = handle:read("*a")
245+
handle:close()
246+
247+
output = string.gsub(output, "\n", "")
248+
249+
return output ~= ""
250+
end
251+
252+
local file_name_on_commit = function(commit_hash, git_relative_file_path)
253+
if file_exists_on_commit(commit_hash, git_relative_file_path) then
254+
return git_relative_file_path
255+
end
256+
257+
-- FIXME: this is a very naive implementation, but it always returns the
258+
-- correct filename for each commit (even if the commit didn't touch the file)
259+
260+
-- first find index in all_commit_hashes
261+
local all_hashes = all_commit_hashes()
262+
local index = 0
263+
for i, hash in ipairs(all_hashes) do
264+
-- compare on first 7 chars
265+
if string.sub(hash, 1, 7) == string.sub(commit_hash, 1, 7) then
266+
index = i
267+
break
268+
end
227269
end
228270

229-
local returned_hash = utils.split_string(output[1])[1]
271+
-- then find the first commit that has a different file name
272+
local touched_hashes =
273+
all_commit_hashes_touching_file(git_relative_file_path)
274+
local last_touched_hash = nil
275+
for i = index, #all_hashes do
276+
local hash = all_hashes[i]
277+
-- search the hash in touched_hashes
278+
for _, touched_hash in ipairs(touched_hashes) do
279+
if string.sub(touched_hash, 1, 7) == string.sub(hash, 1, 7) then
280+
last_touched_hash = touched_hash
281+
break
282+
end
283+
end
230284

231-
-- only return the file name if the commit hash matches, otherwise return nil
232-
if string.sub(returned_hash, 1, 7) == string.sub(commit_hash, 1, 7) then
233-
local split_output = utils.split_string(output[2])
234-
return split_output[#split_output]
235-
else
285+
-- print("searching next")
286+
if last_touched_hash ~= nil then
287+
break
288+
end
289+
end
290+
291+
if last_touched_hash == nil then
236292
return nil
237293
end
294+
295+
local command = "cd "
296+
.. file.git_dir()
297+
.. " && "
298+
.. "git --no-pager log -M --follow --pretty=format:'%H' --name-only "
299+
.. last_touched_hash
300+
.. "~.. -- "
301+
.. git_relative_file_path
302+
.. " | tail -1"
303+
304+
local handle = io.popen(command)
305+
local output = handle:read("*a")
306+
handle:close()
307+
308+
output = string.gsub(output, "\n", "")
309+
310+
return output
238311
end
239312

240313
M.git_diff_previewer_file = function(bufnr)
241314
return previewers.new_termopen_previewer({
242315
get_command = function(entry)
316+
local filename_on_head = file.git_relative_path(bufnr)
317+
243318
local commit_hash = entry.opts.commit_hash
244319

245320
local prev_commit = previous_commit_hash(commit_hash)
246-
local prev_name = determine_historic_file_name(prev_commit, bufnr)
247-
local curr_name = determine_historic_file_name(commit_hash, bufnr)
248-
if prev_name == nil and curr_name ~= nil then
321+
local curr_name = nil
322+
local prev_name = nil
323+
324+
curr_name = file_name_on_commit(commit_hash, filename_on_head)
325+
prev_name = file_name_on_commit(prev_commit, filename_on_head)
326+
327+
if prev_name ~= nil then
249328
return git_diff_command({
250329
"git",
251330
"diff",
252-
prev_commit,
253-
commit_hash,
254-
"--",
255-
file.git_dir()
256-
.. "/"
257-
.. determine_historic_file_name(commit_hash, bufnr),
331+
prev_commit .. ":" .. prev_name,
332+
commit_hash .. ":" .. curr_name,
258333
})
259-
elseif prev_name == nil and curr_name == nil then
334+
else
260335
return git_diff_command({
261336
"git",
262337
"diff",
263338
prev_commit,
264339
commit_hash,
265340
"--",
266-
file.git_relative_path(bufnr),
267-
})
268-
else
269-
return git_diff_command({
270-
"git",
271-
"diff",
272-
prev_commit
273-
.. ":"
274-
.. determine_historic_file_name(prev_commit, bufnr),
275-
commit_hash
276-
.. ":"
277-
.. determine_historic_file_name(commit_hash, bufnr),
341+
file.git_relative_path_to_relative_path(curr_name),
278342
})
279343
end
280344
end,
@@ -333,7 +397,7 @@ M.current_branch = function()
333397
return output
334398
end
335399

336-
M.determine_historic_file_name = determine_historic_file_name
400+
M.file_name_on_commit = file_name_on_commit
337401
M.git_diff_command = git_diff_command
338402

339403
return M

lua/advanced_git_search/init.lua

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ M.diff_branch_file = function()
108108

109109
gu.open_diff_view(
110110
branch,
111-
gu.determine_historic_file_name(branch, file_name)
111+
gu.file_name_on_commit(branch, file_name)
112112
)
113113
end)
114114

@@ -300,8 +300,10 @@ M.diff_commit_file = function()
300300
actions.close(prompt_bufnr)
301301
local selection = action_state.get_selected_entry()
302302
local commit_hash = selection.opts.commit_hash
303-
local old_file_name =
304-
gu.determine_historic_file_name(commit_hash, bufnr)
303+
local old_file_name = gu.file_name_on_commit(
304+
commit_hash,
305+
file.git_relative_path(bufnr)
306+
)
305307

306308
gu.open_diff_view(commit_hash, old_file_name)
307309
end)

lua/advanced_git_search/utils/file.lua

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ M.git_relative_path = function(bufnr)
5151
end
5252
end
5353

54+
M.git_relative_path_to_relative_path = function(git_relative_path)
55+
local git_dir = M.find_first_ancestor_dir_or_file(vim.fn.getcwd(), ".git")
56+
local project_dir = vim.fn.getcwd()
57+
58+
local absolute_path = git_dir .. "/" .. git_relative_path
59+
project_dir = utils.escape_chars(project_dir .. "/")
60+
return string.gsub(absolute_path, project_dir, "")
61+
end
62+
5463
M.path = (function()
5564
local is_windows = uv.os_uname().version:match("Windows")
5665

0 commit comments

Comments
 (0)