216 lines
9.0 KiB
Lua
216 lines
9.0 KiB
Lua
--- ### Git LUA API
|
|
--
|
|
-- This module can be loaded with `local git = require "astronvim.utils.git"`
|
|
--
|
|
-- @module astronvim.utils.git
|
|
-- @copyright 2022
|
|
-- @license GNU General Public License v3.0
|
|
|
|
local git = { url = "https://github.com/" }
|
|
|
|
local function trim_or_nil(str) return type(str) == "string" and vim.trim(str) or nil end
|
|
|
|
--- Run a git command from the AstroNvim installation directory
|
|
---@param args string|string[] the git arguments
|
|
---@return string|nil # The result of the command or nil if unsuccessful
|
|
function git.cmd(args, ...)
|
|
if type(args) == "string" then args = { args } end
|
|
return require("astronvim.utils").cmd(vim.list_extend({ "git", "-C", astronvim.install.home }, args), ...)
|
|
end
|
|
|
|
--- Get the first worktree that a file belongs to
|
|
---@param file string? the file to check, defaults to the current file
|
|
---@param worktrees table<string, string>[]? an array like table of worktrees with entries `toplevel` and `gitdir`, default retrieves from `vim.g.git_worktrees`
|
|
---@return table<string, string>|nil # a table specifying the `toplevel` and `gitdir` of a worktree or nil if not found
|
|
function git.file_worktree(file, worktrees)
|
|
worktrees = worktrees or vim.g.git_worktrees
|
|
if not worktrees then return end
|
|
file = file or vim.fn.expand "%"
|
|
for _, worktree in ipairs(worktrees) do
|
|
if
|
|
require("astronvim.utils").cmd({
|
|
"git",
|
|
"--work-tree",
|
|
worktree.toplevel,
|
|
"--git-dir",
|
|
worktree.gitdir,
|
|
"ls-files",
|
|
"--error-unmatch",
|
|
file,
|
|
}, false)
|
|
then
|
|
return worktree
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Check if the AstroNvim is able to reach the `git` command
|
|
---@return boolean # The result of running `git --help`
|
|
function git.available() return vim.fn.executable "git" == 1 end
|
|
|
|
--- Check the git client version number
|
|
---@return table|nil # A table with version information or nil if there is an error
|
|
function git.git_version()
|
|
local output = git.cmd({ "--version" }, false)
|
|
if output then
|
|
local version_str = output:match "%d+%.%d+%.%d"
|
|
local major, min, patch = unpack(vim.tbl_map(tonumber, vim.split(version_str, "%.")))
|
|
return { major = major, min = min, patch = patch, str = version_str }
|
|
end
|
|
end
|
|
|
|
--- Check if the AstroNvim home is a git repo
|
|
---@return string|nil # The result of the command
|
|
function git.is_repo() return git.cmd({ "rev-parse", "--is-inside-work-tree" }, false) end
|
|
|
|
--- Fetch git remote
|
|
---@param remote string the remote to fetch
|
|
---@return string|nil # The result of the command
|
|
function git.fetch(remote, ...) return git.cmd({ "fetch", remote }, ...) end
|
|
|
|
--- Pull the git repo
|
|
---@return string|nil # The result of the command
|
|
function git.pull(...) return git.cmd({ "pull", "--rebase" }, ...) end
|
|
|
|
--- Checkout git target
|
|
---@param dest string the target to checkout
|
|
---@return string|nil # The result of the command
|
|
function git.checkout(dest, ...) return git.cmd({ "checkout", dest }, ...) end
|
|
|
|
--- Hard reset to a git target
|
|
-- @param dest the target to hard reset to
|
|
---@return string|nil # The result of the command
|
|
function git.hard_reset(dest, ...) return git.cmd({ "reset", "--hard", dest }, ...) end
|
|
|
|
--- Check if a branch contains a commit
|
|
---@param remote string the git remote to check
|
|
---@param branch string the git branch to check
|
|
---@param commit string the git commit to check for
|
|
---@return boolean # The result of the command
|
|
function git.branch_contains(remote, branch, commit, ...)
|
|
return git.cmd({ "merge-base", "--is-ancestor", commit, remote .. "/" .. branch }, ...) ~= nil
|
|
end
|
|
|
|
--- Get the remote name for a given branch
|
|
---@param branch string the git branch to check
|
|
---@return string|nil # The name of the remote for the given branch
|
|
function git.branch_remote(branch, ...) return trim_or_nil(git.cmd({ "config", "branch." .. branch .. ".remote" }, ...)) end
|
|
|
|
--- Add a git remote
|
|
---@param remote string the remote to add
|
|
---@param url string the url of the remote
|
|
---@return string|nil # The result of the command
|
|
function git.remote_add(remote, url, ...) return git.cmd({ "remote", "add", remote, url }, ...) end
|
|
|
|
--- Update a git remote URL
|
|
---@param remote string the remote to update
|
|
---@param url string the new URL of the remote
|
|
---@return string|nil # The result of the command
|
|
function git.remote_update(remote, url, ...) return git.cmd({ "remote", "set-url", remote, url }, ...) end
|
|
|
|
--- Get the URL of a given git remote
|
|
---@param remote string the remote to get the URL of
|
|
---@return string|nil # The url of the remote
|
|
function git.remote_url(remote, ...) return trim_or_nil(git.cmd({ "remote", "get-url", remote }, ...)) end
|
|
|
|
--- Get branches from a git remote
|
|
---@param remote string the remote to setup branches for
|
|
---@param branch string the branch to setup
|
|
---@return string|nil # The result of the command
|
|
function git.remote_set_branches(remote, branch, ...) return git.cmd({ "remote", "set-branches", remote, branch }, ...) end
|
|
|
|
--- Get the current version with git describe including tags
|
|
---@return string|nil # The current git describe string
|
|
function git.current_version(...) return trim_or_nil(git.cmd({ "describe", "--tags" }, ...)) end
|
|
|
|
--- Get the current branch
|
|
---@return string|nil # The branch of the AstroNvim installation
|
|
function git.current_branch(...) return trim_or_nil(git.cmd({ "rev-parse", "--abbrev-ref", "HEAD" }, ...)) end
|
|
|
|
--- Verify a reference
|
|
---@return string|nil # The referenced commit
|
|
function git.ref_verify(ref, ...) return trim_or_nil(git.cmd({ "rev-parse", "--verify", ref }, ...)) end
|
|
|
|
--- Get the current head of the git repo
|
|
---@return string|nil # the head string
|
|
function git.local_head(...) return trim_or_nil(git.cmd({ "rev-parse", "HEAD" }, ...)) end
|
|
|
|
--- Get the current head of a git remote
|
|
---@param remote string the remote to check
|
|
---@param branch string the branch to check
|
|
---@return string|nil # The head string of the remote branch
|
|
function git.remote_head(remote, branch, ...)
|
|
return trim_or_nil(git.cmd({ "rev-list", "-n", "1", remote .. "/" .. branch }, ...))
|
|
end
|
|
|
|
--- Get the commit hash of a given tag
|
|
---@param tag string the tag to resolve
|
|
---@return string|nil # The commit hash of a git tag
|
|
function git.tag_commit(tag, ...) return trim_or_nil(git.cmd({ "rev-list", "-n", "1", tag }, ...)) end
|
|
|
|
--- Get the commit log between two commit hashes
|
|
---@param start_hash? string the start commit hash
|
|
---@param end_hash? string the end commit hash
|
|
---@return string[] # An array like table of commit messages
|
|
function git.get_commit_range(start_hash, end_hash, ...)
|
|
local range = start_hash and end_hash and start_hash .. ".." .. end_hash or nil
|
|
local log = git.cmd({ "log", "--no-merges", '--pretty="format:[%h] %s"', range }, ...)
|
|
return log and vim.fn.split(log, "\n") or {}
|
|
end
|
|
|
|
--- Get a list of all tags with a regex filter
|
|
---@param search? string a regex to search the tags with (defaults to "v*" for version tags)
|
|
---@return string[] # An array like table of tags that match the search
|
|
function git.get_versions(search, ...)
|
|
local tags = git.cmd({ "tag", "-l", "--sort=version:refname", search == "latest" and "v*" or search }, ...)
|
|
return tags and vim.fn.split(tags, "\n") or {}
|
|
end
|
|
|
|
--- Get the latest version of a list of versions
|
|
---@param versions? table a list of versions to search (defaults to all versions available)
|
|
---@return string|nil # The latest version from the array
|
|
function git.latest_version(versions, ...)
|
|
if not versions then versions = git.get_versions(...) end
|
|
return versions[#versions]
|
|
end
|
|
|
|
--- Parse a remote url
|
|
---@param str string the remote to parse to a full git url
|
|
---@return string # The full git url for the given remote string
|
|
function git.parse_remote_url(str)
|
|
return vim.fn.match(str, require("astronvim.utils").url_matcher) == -1
|
|
and git.url .. str .. (vim.fn.match(str, "/") == -1 and "/AstroNvim.git" or ".git")
|
|
or str
|
|
end
|
|
|
|
--- Check if a Conventional Commit commit message is breaking or not
|
|
---@param commit string a commit message
|
|
---@return boolean true if the message is breaking, false if the commit message is not breaking
|
|
function git.is_breaking(commit) return vim.fn.match(commit, "\\[.*\\]\\s\\+\\w\\+\\((\\w\\+)\\)\\?!:") ~= -1 end
|
|
|
|
--- Get a list of breaking commits from commit messages using Conventional Commit standard
|
|
---@param commits string[] an array like table of commit messages
|
|
---@return string[] # An array like table of commits that are breaking
|
|
function git.breaking_changes(commits) return vim.tbl_filter(git.is_breaking, commits) end
|
|
|
|
--- Generate a table of commit messages for neovim's echo API with highlighting
|
|
---@param commits string[] an array like table of commit messages
|
|
---@return string[][] # An array like table of echo messages to provide to nvim_echo or astronvim.echo
|
|
function git.pretty_changelog(commits)
|
|
local changelog = {}
|
|
for _, commit in ipairs(commits) do
|
|
local hash, type, msg = commit:match "(%[.*%])(.*:)(.*)"
|
|
if hash and type and msg then
|
|
vim.list_extend(changelog, {
|
|
{ hash, "DiffText" },
|
|
{ type, git.is_breaking(commit) and "DiffDelete" or "DiffChange" },
|
|
{ msg },
|
|
{ "\n" },
|
|
})
|
|
end
|
|
end
|
|
return changelog
|
|
end
|
|
|
|
return git
|