Skip to content

Commit 074231f

Browse files
committed
fix(release-notes): improve commit range handling and tag detection
- Fix commit range to exclude from_tag itself using ^ notation - Improve tag detection with better fallback strategies - Handle empty outputs and first commits properly - Use git diff-tree for safer diff stats retrieval - Add support for relative references like HEAD~10
1 parent 95ba377 commit 074231f

File tree

2 files changed

+68
-17
lines changed

2 files changed

+68
-17
lines changed

lua/codecompanion/_extensions/gitcommit/tools/ai_release_notes.lua

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,21 @@ Output styles:
6060

6161
-- Helper function to get commit details with diffs
6262
local function get_detailed_commits(from_ref, to_ref)
63-
-- Get commit log with full details
64-
local range = from_ref .. ".." .. to_ref
63+
-- Build the range correctly
64+
-- We want commits AFTER from_ref up to and including to_ref
65+
-- Use ^..to_ref to include commits after from_ref (not including from_ref itself)
66+
local range
67+
68+
-- Check if from_ref looks like a relative reference (e.g., HEAD~10)
69+
if from_ref:match("^HEAD[~^]") then
70+
-- For relative refs, use as-is
71+
range = from_ref .. ".." .. (to_ref or "HEAD")
72+
else
73+
-- For tags/commits, use ^ to get commits AFTER the from_ref
74+
-- This ensures we don't include the from_ref commit itself in the release notes
75+
range = from_ref .. "^.." .. (to_ref or "HEAD")
76+
end
77+
6578
local escaped_range = vim.fn.shellescape(range)
6679

6780
-- Get commits with more details
@@ -73,6 +86,11 @@ local function get_detailed_commits(from_ref, to_ref)
7386
return nil, "Failed to get commit history"
7487
end
7588

89+
-- Handle empty output
90+
if not output or vim.trim(output) == "" then
91+
return {}, nil
92+
end
93+
7694
local commits = {}
7795
for line in output:gmatch("[^\r\n]+") do
7896
local parts = vim.split(line, "|")
@@ -81,9 +99,13 @@ local function get_detailed_commits(from_ref, to_ref)
8199
local subject = parts[2]
82100
local body = parts[3] ~= "" and parts[3] or nil
83101

84-
-- Get diff stats for this commit
85-
local diff_cmd = string.format("git diff --stat %s~1 %s", hash, hash)
86-
local _, diff_stats = pcall(vim.fn.system, diff_cmd)
102+
-- Get diff stats for this commit (safely handle first commit)
103+
local diff_stats = nil
104+
local diff_cmd = string.format("git diff-tree --stat --root %s", hash)
105+
local diff_success, stats_output = pcall(vim.fn.system, diff_cmd)
106+
if diff_success and vim.v.shell_error == 0 then
107+
diff_stats = stats_output
108+
end
87109

88110
-- Parse conventional commit type
89111
local commit_type, scope = subject:match("^(%w+)%((.-)%):")
@@ -100,7 +122,7 @@ local function get_detailed_commits(from_ref, to_ref)
100122
date = parts[6],
101123
type = commit_type,
102124
scope = scope,
103-
diff_stats = vim.v.shell_error == 0 and diff_stats or nil,
125+
diff_stats = diff_stats,
104126
})
105127
end
106128
end
@@ -230,24 +252,52 @@ AIReleaseNotes.cmds = {
230252

231253
-- Get tags if not specified
232254
if not to_tag or not from_tag then
255+
-- Try to get tags sorted by version
233256
local success, tags_output = pcall(vim.fn.system, "git tag --sort=-version:refname")
234-
if success and vim.v.shell_error == 0 then
257+
if success and vim.v.shell_error == 0 and tags_output and vim.trim(tags_output) ~= "" then
235258
local tags = {}
236259
for tag in tags_output:gmatch("[^\r\n]+") do
237-
if tag ~= "" then
238-
table.insert(tags, tag)
260+
local trimmed = vim.trim(tag)
261+
if trimmed ~= "" then
262+
table.insert(tags, trimmed)
239263
end
240264
end
241265

266+
-- Set to_tag if not specified
267+
if not to_tag then
268+
if #tags > 0 then
269+
to_tag = tags[1] -- Use latest tag
270+
else
271+
to_tag = "HEAD" -- No tags, use HEAD
272+
end
273+
end
274+
275+
-- Set from_tag if not specified
276+
if not from_tag then
277+
if #tags > 1 then
278+
from_tag = tags[2] -- Use previous tag
279+
elseif #tags == 1 then
280+
-- Only one tag, get first commit as starting point
281+
local first_commit_cmd = "git rev-list --max-parents=0 HEAD"
282+
local fc_success, first_commit_output = pcall(vim.fn.system, first_commit_cmd)
283+
if fc_success and vim.v.shell_error == 0 then
284+
from_tag = vim.trim(first_commit_output):sub(1, 8)
285+
else
286+
-- Fallback to 10 commits ago
287+
from_tag = "HEAD~10"
288+
end
289+
else
290+
-- No tags at all, use HEAD~10 as a reasonable default
291+
from_tag = "HEAD~10"
292+
end
293+
end
294+
else
295+
-- No tags or git command failed
242296
if not to_tag then
243-
to_tag = tags[1] or "HEAD"
297+
to_tag = "HEAD"
244298
end
245-
if not from_tag and #tags > 1 then
246-
from_tag = tags[2]
247-
elseif not from_tag then
248-
-- Get the first commit if no previous tag
249-
local first_commit = vim.fn.system("git rev-list --max-parents=0 HEAD"):gsub("\n", "")
250-
from_tag = first_commit:sub(1, 8)
299+
if not from_tag then
300+
from_tag = "HEAD~10" -- Default to last 10 commits
251301
end
252302
end
253303
end

lua/codecompanion/_extensions/gitcommit/tools/git.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,8 @@ function GitTool.generate_release_notes(from_tag, to_tag, format)
779779
end
780780

781781
-- Get commit range between tags
782-
local range = from_tag .. ".." .. to_tag
782+
-- Use ^.. to get commits AFTER from_tag (excluding from_tag itself)
783+
local range = from_tag .. "^.." .. to_tag
783784
local escaped_range = vim.fn.shellescape(range)
784785
if not escaped_range or escaped_range == "" then
785786
local msg = "Failed to escape tag range: " .. range

0 commit comments

Comments
 (0)