diff --git a/lua/gitlab/hunks.lua b/lua/gitlab/hunks.lua index 01df7160..046502bd 100644 --- a/lua/gitlab/hunks.lua +++ b/lua/gitlab/hunks.lua @@ -25,38 +25,26 @@ M.parse_possible_hunk_headers = function(line) -- @@ -41,0 +42,4 @@ ... local old_start, old_range, new_start, new_range = line:match("@@+ %-(%d+),?(%d*) %+(%d+),?(%d*) @@+") + -- Git omits the ",N" count when it is exactly 1, so an empty capture means 1, + -- while a captured "0" means a genuine zero-length range (pure insertion/deletion). return { old_line = tonumber(old_start), - old_range = tonumber(old_range) or 0, + old_range = tonumber(old_range) or 1, new_line = tonumber(new_start), - new_range = tonumber(new_range) or 0, + new_range = tonumber(new_range) or 1, } end end ---Return true if given line was removed in the MR. +---The diff comes from `git.diff_files`, which runs with `--unified=0`. A hunk +---therefore carries no context lines and its old range holds removed lines only, +---so membership in that range already answers the question. ---@param linenr integer Line number in the old version of the file ---@param hunk Hunk A hunk candidate from the file's diff ----@param all_diff_output string[] ---@return boolean -local line_was_removed = function(linenr, hunk, all_diff_output) - for matching_line_index, line in ipairs(all_diff_output) do - local found_hunk = M.parse_possible_hunk_headers(line) - if found_hunk ~= nil and vim.deep_equal(found_hunk, hunk) then - -- We found a matching hunk, now we need to iterate over the lines from the raw diff output - -- at that hunk until we reach the line we are looking for. When the indexes match we check - -- to see if that line is deleted or not. - for hunk_line_index = found_hunk.old_line, hunk.old_line + hunk.old_range, 1 do - local line_content = all_diff_output[matching_line_index + 1] - if hunk_line_index == linenr then - if string.match(line_content, "^%-") then - return true - end - end - end - end - end - return false +local line_was_removed = function(linenr, hunk) + return linenr >= hunk.old_line and linenr < hunk.old_line + hunk.old_range end ---Return true if given line was added in the MR. @@ -198,9 +186,8 @@ end ---@param old_line? integer The starting or ending line of the current selection in the old version ---@param new_line? integer The starting or ending line of the current selection in the new version ---@param hunks Hunk[] ----@param all_diff_output string[] ---@return ("deleted"|"unmodified")? -local function get_modification_type_from_old_sha(old_line, new_line, hunks, all_diff_output) +local function get_modification_type_from_old_sha(old_line, new_line, hunks) if old_line == nil then return nil end @@ -210,7 +197,7 @@ local function get_modification_type_from_old_sha(old_line, new_line, hunks, all local new_line_end = hunk.new_line + hunk.new_range - (hunk.new_range > 0 and 1 or 0) local in_old_range = old_line >= hunk.old_line and old_line <= old_line_end local in_new_range = new_line >= hunk.new_line and new_line <= new_line_end - return (in_old_range or in_new_range) and line_was_removed(old_line, hunk, all_diff_output) + return (in_old_range or in_new_range) and line_was_removed(old_line, hunk) end) and "deleted" or "unmodified" end @@ -236,7 +223,7 @@ function M.get_modification_type(old_line, new_line, new_sha_focused) local hunks = hunk_and_diff_data.hunks local all_diff_output = hunk_and_diff_data.all_diff_output return new_sha_focused and get_modification_type_from_new_sha(new_line, hunks, all_diff_output) - or get_modification_type_from_old_sha(old_line, new_line, hunks, all_diff_output) + or get_modification_type_from_old_sha(old_line, new_line, hunks) end ---Return the matching line number of a line in the new/old version of the file compared diff --git a/tests/spec/hunks_spec.lua b/tests/spec/hunks_spec.lua new file mode 100644 index 00000000..4195ed73 --- /dev/null +++ b/tests/spec/hunks_spec.lua @@ -0,0 +1,129 @@ +local hunks = require("gitlab.hunks") + +describe("gitlab/hunks.lua", function() + describe("parse_possible_hunk_headers", function() + it("treats an omitted old count as 1, keeping an explicit new count", function() + local got = hunks.parse_possible_hunk_headers("@@ -5 +5,3 @@") + local want = { old_line = 5, old_range = 1, new_line = 5, new_range = 3 } + assert.are.same(want, got) + end) + + it("keeps an explicit old count of 0 as a genuine pure insertion", function() + local got = hunks.parse_possible_hunk_headers("@@ -5,0 +5,3 @@") + local want = { old_line = 5, old_range = 0, new_line = 5, new_range = 3 } + assert.are.same(want, got) + end) + + it("treats both omitted counts as 1", function() + local got = hunks.parse_possible_hunk_headers("@@ -5 +5 @@") + local want = { old_line = 5, old_range = 1, new_line = 5, new_range = 1 } + assert.are.same(want, got) + end) + + it("treats an omitted new count as 1, keeping an explicit old count", function() + local got = hunks.parse_possible_hunk_headers("@@ -5,3 +5 @@") + local want = { old_line = 5, old_range = 3, new_line = 5, new_range = 1 } + assert.are.same(want, got) + end) + end) + + describe("get_modification_type", function() + local state = require("gitlab.state") + + local function stub_diff(diff_text) + package.loaded["gitlab.git"] = { + diff_files = function() + return diff_text, nil + end, + } + package.loaded["gitlab.reviewer"] = { + get_current_file_oldpath = function() + return "file.txt" + end, + get_current_file_path = function() + return "file.txt" + end, + } + end + + before_each(function() + state.INFO = { diff_refs = { base_sha = "base-sha" } } + end) + + after_each(function() + state.INFO = nil + package.loaded["gitlab.git"] = nil + package.loaded["gitlab.reviewer"] = nil + end) + + it("does not classify the unmodified context line above a single-line deletion as added", function() + stub_diff([[ +diff --git a/file.txt b/file.txt +index 1111111..2222222 100644 +--- a/file.txt ++++ b/file.txt +@@ -5 +4,0 @@ +-old content that was removed +]]) + + local got = hunks.get_modification_type(4, 4, true) + assert.are_not.same("added", got) + assert.are.same("bad_file_unmodified", got) + end) + + it("keeps classifying the context line above a two-line deletion as bad_file_unmodified", function() + stub_diff([[ +diff --git a/file.txt b/file.txt +index 1111111..2222222 100644 +--- a/file.txt ++++ b/file.txt +@@ -5,2 +4,0 @@ +-old line 5 +-old line 6 +]]) + + local got = hunks.get_modification_type(4, 4, true) + assert.are.same("bad_file_unmodified", got) + end) + + it("treats a deleted line as deleted", function() + stub_diff([[ +diff --git a/file.txt b/file.txt +index 1111111..2222222 100644 +--- a/file.txt ++++ b/file.txt +@@ -5 +4,0 @@ +-old line 5 +]]) + + assert.are.same("deleted", hunks.get_modification_type(5, 4, false)) + end) + + it("treats the line below a single-line deletion as unmodified", function() + stub_diff([[ +diff --git a/file.txt b/file.txt +index 1111111..2222222 100644 +--- a/file.txt ++++ b/file.txt +@@ -5 +4,0 @@ +-old line 5 +]]) + + assert.are.same("unmodified", hunks.get_modification_type(6, 5, false)) + end) + + it("treats the line below a multi-line deletion as unmodified", function() + stub_diff([[ +diff --git a/file.txt b/file.txt +index 1111111..2222222 100644 +--- a/file.txt ++++ b/file.txt +@@ -5,2 +4,0 @@ +-old line 5 +-old line 6 +]]) + + assert.are.same("unmodified", hunks.get_modification_type(7, 5, false)) + end) + end) +end)