This repository was archived by the owner on Jan 14, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
feat(git): add generate_release_notes operation #11
Merged
Merged
Changes from 6 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
3a020b8
feat(git): add generate_release_notes operation
jinzhongjia 55ab246
feat(git): sort contributors by commit count in release notes
jinzhongjia 5c1ff7f
refactor(git): improve formatting and readability in release notes
jinzhongjia 6bd50e3
fix(git): improve commit parsing in release notes generation
jinzhongjia 85555ff
Update README.md
jinzhongjia 5df47b8
refactor(git): optimize release notes string construction
jinzhongjia 45b5868
fix(git): improve commit type parsing in release notes
jinzhongjia 00bb7a5
fix(git): restrict commit type matching in release notes
jinzhongjia 0734006
fix(git): handle shell escaping failure in release notes
jinzhongjia bf326db
docs: update usage examples and feature descriptions
jinzhongjia File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -689,5 +689,200 @@ function GitTool.merge(branch) | |
| end | ||
| end | ||
|
|
||
| --- Generate release notes between two tags | ||
| ---@param from_tag string|nil Starting tag (if not provided, uses second latest tag) | ||
| ---@param to_tag string|nil Ending tag (if not provided, uses latest tag) | ||
| ---@param format string|nil Format for release notes (markdown, plain, json) | ||
|
jinzhongjia marked this conversation as resolved.
Outdated
|
||
| ---@return boolean success | ||
| ---@return string output | ||
| ---@return string user_msg | ||
| ---@return string llm_msg | ||
| function GitTool.generate_release_notes(from_tag, to_tag, format) | ||
|
jinzhongjia marked this conversation as resolved.
|
||
| format = format or "markdown" | ||
|
|
||
| -- Get all tags sorted by version | ||
| local success, tags_output = pcall(vim.fn.system, "git tag --sort=-version:refname") | ||
| if not success or vim.v.shell_error ~= 0 then | ||
| local msg = "Failed to get git tags: " .. (tags_output or "unknown error") | ||
| local user_msg = msg | ||
| local llm_msg = "<gitReleaseNotes>fail: " .. msg .. "</gitReleaseNotes>" | ||
| return false, msg, user_msg, llm_msg | ||
| end | ||
|
|
||
| local tags = {} | ||
| for tag in tags_output:gmatch("[^\r\n]+") do | ||
| if tag ~= "" then | ||
| table.insert(tags, tag) | ||
| end | ||
| end | ||
|
|
||
| if #tags < 1 then | ||
| local msg = "No tags found in repository" | ||
| local user_msg = msg | ||
| local llm_msg = "<gitReleaseNotes>fail: " .. msg .. "</gitReleaseNotes>" | ||
| return false, msg, user_msg, llm_msg | ||
| end | ||
|
|
||
| -- Determine tag range | ||
| if not to_tag then | ||
| to_tag = tags[1] -- Latest tag | ||
| end | ||
|
|
||
| if not from_tag then | ||
| if #tags < 2 then | ||
| local msg = "Cannot generate release notes: only one tag found. Please specify from_tag parameter." | ||
| local user_msg = msg | ||
| local llm_msg = "<gitReleaseNotes>fail: " .. msg .. "</gitReleaseNotes>" | ||
| return false, msg, user_msg, llm_msg | ||
| end | ||
| from_tag = tags[2] -- Second latest tag | ||
| end | ||
|
|
||
| -- Get commit range between tags | ||
| local range = from_tag .. ".." .. to_tag | ||
| local commit_cmd = "git log --pretty=format:'%h\x01%s\x01%an\x01%ad' --date=short " .. vim.fn.shellescape(range) | ||
| local success_commits, commits_output = pcall(vim.fn.system, commit_cmd) | ||
|
jinzhongjia marked this conversation as resolved.
Outdated
|
||
|
|
||
| if not success_commits or vim.v.shell_error ~= 0 then | ||
| local msg = "Failed to get commits between " | ||
| .. from_tag | ||
| .. " and " | ||
| .. to_tag | ||
| .. ": " | ||
| .. (commits_output or "unknown error") | ||
| local user_msg = msg | ||
| local llm_msg = "<gitReleaseNotes>fail: " .. msg .. "</gitReleaseNotes>" | ||
| return false, msg, user_msg, llm_msg | ||
| end | ||
|
|
||
| -- Parse commits | ||
| local commits = {} | ||
| for line in commits_output:gmatch("[^\r\n]+") do | ||
| local parts = vim.split(line, "\x01") | ||
| if #parts == 4 then | ||
| table.insert(commits, { | ||
| hash = parts[1], | ||
| subject = parts[2], | ||
| author = parts[3], | ||
| date = parts[4], | ||
| }) | ||
| end | ||
| end | ||
|
jinzhongjia marked this conversation as resolved.
jinzhongjia marked this conversation as resolved.
|
||
|
|
||
| if #commits == 0 then | ||
| local msg = "No commits found between " .. from_tag .. " and " .. to_tag | ||
| local user_msg = msg | ||
| local llm_msg = "<gitReleaseNotes>success: " .. msg .. "</gitReleaseNotes>" | ||
| return true, msg, user_msg, llm_msg | ||
| end | ||
|
|
||
| -- Generate release notes based on format | ||
| local release_notes = "" | ||
| local user_msg = "" | ||
| local llm_msg = "" | ||
|
|
||
| if format == "markdown" then | ||
| local parts = { "# Release Notes: " .. from_tag .. " → " .. to_tag .. "\n\n" } | ||
| table.insert(parts, "## Changes (" .. #commits .. " commits)\n\n") | ||
|
|
||
| -- Group commits by type (conventional commits) | ||
| local features = {} | ||
| local fixes = {} | ||
| local others = {} | ||
|
|
||
| for _, commit in ipairs(commits) do | ||
| local type_match = commit.subject:match("^(%w+):") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current regex |
||
| if type_match then | ||
| if type_match == "feat" or type_match == "feature" then | ||
| table.insert(features, commit) | ||
| elseif type_match == "fix" or type_match == "bugfix" then | ||
| table.insert(fixes, commit) | ||
|
jinzhongjia marked this conversation as resolved.
Outdated
|
||
| else | ||
| table.insert(others, commit) | ||
| end | ||
| else | ||
| table.insert(others, commit) | ||
| end | ||
| end | ||
|
|
||
| -- Add features | ||
| if #features > 0 then | ||
| table.insert(parts, "### ✨ New Features\n\n") | ||
| for _, commit in ipairs(features) do | ||
| table.insert(parts, "- " .. commit.subject .. " (" .. commit.hash .. ")\n") | ||
| end | ||
| table.insert(parts, "\n") | ||
| end | ||
|
|
||
| -- Add fixes | ||
| if #fixes > 0 then | ||
| table.insert(parts, "### 🐛 Bug Fixes\n\n") | ||
| for _, commit in ipairs(fixes) do | ||
| table.insert(parts, "- " .. commit.subject .. " (" .. commit.hash .. ")\n") | ||
| end | ||
| table.insert(parts, "\n") | ||
| end | ||
|
|
||
| -- Add other changes | ||
| if #others > 0 then | ||
| table.insert(parts, "### 📝 Other Changes\n\n") | ||
| for _, commit in ipairs(others) do | ||
| table.insert(parts, "- " .. commit.subject .. " (" .. commit.hash .. ")\n") | ||
| end | ||
| table.insert(parts, "\n") | ||
| end | ||
|
|
||
| -- Add contributors | ||
| local contributors = {} | ||
| for _, commit in ipairs(commits) do | ||
| if not contributors[commit.author] then | ||
| contributors[commit.author] = 0 | ||
| end | ||
| contributors[commit.author] = contributors[commit.author] + 1 | ||
| end | ||
|
jinzhongjia marked this conversation as resolved.
|
||
|
|
||
| table.insert(parts, "### 👥 Contributors\n\n") | ||
| local sorted_authors = {} | ||
| for author in pairs(contributors) do | ||
| table.insert(sorted_authors, author) | ||
| end | ||
| table.sort(sorted_authors, function(a, b) | ||
| if contributors[a] == contributors[b] then | ||
| return a < b | ||
| end | ||
| return contributors[a] > contributors[b] | ||
| end) | ||
|
jinzhongjia marked this conversation as resolved.
|
||
| for _, author in ipairs(sorted_authors) do | ||
| table.insert(parts, "- " .. author .. " (" .. contributors[author] .. " commits)\n") | ||
| end | ||
|
jinzhongjia marked this conversation as resolved.
Outdated
jinzhongjia marked this conversation as resolved.
|
||
| release_notes = table.concat(parts) | ||
| elseif format == "plain" then | ||
| local parts = { "Release Notes: " .. from_tag .. " → " .. to_tag .. "\n" } | ||
| table.insert(parts, "Changes (" .. #commits .. " commits):\n\n") | ||
| for _, commit in ipairs(commits) do | ||
| table.insert(parts, "- " .. commit.subject .. " (" .. commit.hash .. " by " .. commit.author .. ")\n") | ||
|
jinzhongjia marked this conversation as resolved.
|
||
| end | ||
|
jinzhongjia marked this conversation as resolved.
|
||
| release_notes = table.concat(parts) | ||
| elseif format == "json" then | ||
| local json_data = { | ||
| from_tag = from_tag, | ||
| to_tag = to_tag, | ||
| total_commits = #commits, | ||
| commits = commits, | ||
| } | ||
| release_notes = vim.fn.json_encode(json_data) | ||
| else | ||
| local msg = "Unsupported format: " .. format .. ". Supported formats: markdown, plain, json" | ||
| local user_msg = msg | ||
| local llm_msg = "<gitReleaseNotes>fail: " .. msg .. "</gitReleaseNotes>" | ||
| return false, msg, user_msg, llm_msg | ||
| end | ||
|
|
||
| user_msg = "Generated release notes for " .. from_tag .. " → " .. to_tag .. " (" .. #commits .. " commits)" | ||
| llm_msg = "<gitReleaseNotes>success: " .. user_msg .. "\n\n" .. release_notes .. "</gitReleaseNotes>" | ||
|
|
||
| return true, release_notes, user_msg, llm_msg | ||
| end | ||
|
|
||
| M.GitTool = GitTool | ||
| return M | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.