Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(git): support detached working trees #2597

Merged
merged 3 commits into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions doc/telescope.txt
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,22 @@ telescope.setup({opts}) *telescope.setup()*
Default: `function() return 0 end`


*telescope.defaults.git_worktrees*
git_worktrees: ~
A table of arrays of detached working trees with keys `gitdir` and `toplevel`.
Used to pass `--git-dir` and `--work-tree` flags to git commands when telescope fails
to infer the top-level directory of a given working tree based on cwd.
Example:
git_worktrees = {
{
toplevel = vim.env.HOME,
gitdir = vim.env.HOME .. '/.cfg'
}
}

Default: nil


*telescope.defaults.file_previewer*
file_previewer: ~
Function pointer to the default file_previewer. It is mostly used
Expand Down
89 changes: 60 additions & 29 deletions lua/telescope/builtin/__git.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ local strings = require "plenary.strings"
local Path = require "plenary.path"

local conf = require("telescope.config").values
local git_command = utils.__git_command

local git = {}

local get_git_command_output = function(args, opts)
return utils.get_os_command_output(git_command(args, opts), opts.cwd)
end

git.files = function(opts)
if opts.is_bare then
utils.notify("builtin.git_files", {
Expand All @@ -35,14 +40,14 @@ git.files = function(opts)
-- By creating the entry maker after the cwd options,
-- we ensure the maker uses the cwd options when being created.
opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_file(opts))
local git_command = vim.F.if_nil(opts.git_command, { "git", "ls-files", "--exclude-standard", "--cached" })
opts.git_command = vim.F.if_nil(opts.git_command, git_command({ "ls-files", "--exclude-standard", "--cached" }, opts))

pickers
.new(opts, {
prompt_title = "Git Files",
finder = finders.new_oneshot_job(
vim.tbl_flatten {
git_command,
opts.git_command,
show_untracked and "--others" or nil,
recurse_submodules and "--recurse-submodules" or nil,
},
Expand All @@ -56,12 +61,13 @@ end

git.commits = function(opts)
opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_commits(opts))
local git_command = vim.F.if_nil(opts.git_command, { "git", "log", "--pretty=oneline", "--abbrev-commit", "--", "." })
opts.git_command =
vim.F.if_nil(opts.git_command, git_command({ "log", "--pretty=oneline", "--abbrev-commit", "--", "." }, opts))

pickers
.new(opts, {
prompt_title = "Git Commits",
finder = finders.new_oneshot_job(git_command, opts),
finder = finders.new_oneshot_job(opts.git_command, opts),
previewer = {
previewers.git_commit_diff_to_parent.new(opts),
previewers.git_commit_diff_to_head.new(opts),
Expand All @@ -83,19 +89,12 @@ end
git.stash = function(opts)
opts.show_branch = vim.F.if_nil(opts.show_branch, true)
opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_stash(opts))
opts.git_command = vim.F.if_nil(opts.git_command, git_command({ "--no-pager", "stash", "list" }, opts))

pickers
.new(opts, {
prompt_title = "Git Stash",
finder = finders.new_oneshot_job(
vim.tbl_flatten {
"git",
"--no-pager",
"stash",
"list",
},
opts
),
finder = finders.new_oneshot_job(opts.git_command, opts),
previewer = previewers.git_stash_diff.new(opts),
sorter = conf.file_sorter(opts),
attach_mappings = function()
Expand All @@ -115,15 +114,15 @@ git.bcommits = function(opts)
opts.current_line = (opts.current_file == nil) and get_current_buf_line(opts.winnr) or nil
opts.current_file = vim.F.if_nil(opts.current_file, vim.api.nvim_buf_get_name(opts.bufnr))
opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_commits(opts))
local git_command =
vim.F.if_nil(opts.git_command, { "git", "log", "--pretty=oneline", "--abbrev-commit", "--follow" })
opts.git_command =
vim.F.if_nil(opts.git_command, git_command({ "log", "--pretty=oneline", "--abbrev-commit", "--follow" }, opts))

pickers
.new(opts, {
prompt_title = "Git BCommits",
finder = finders.new_oneshot_job(
vim.tbl_flatten {
git_command,
opts.git_command,
opts.current_file,
},
opts
Expand Down Expand Up @@ -200,10 +199,12 @@ git.branches = function(opts)
.. "%(authorname)"
.. "%(upstream:lstrip=2)"
.. "%(committerdate:format-local:%Y/%m/%d %H:%M:%S)"
local output = utils.get_os_command_output(
{ "git", "for-each-ref", "--perl", "--format", format, "--sort", "-authordate", opts.pattern },
opts.cwd

local output = get_git_command_output(
{ "for-each-ref", "--perl", "--format", format, "--sort", "-authordate", opts.pattern },
opts
)

local show_remote_tracking_branches = vim.F.if_nil(opts.show_remote_tracking_branches, true)

local results = {}
Expand All @@ -217,7 +218,7 @@ git.branches = function(opts)
return string.gsub(v, "\\([\\'])", "%1")
end
local parse_line = function(line)
local fields = vim.split(string.sub(line, 2, -2), "''", true)
local fields = vim.split(string.sub(line, 2, -2), "''")
local entry = {
head = fields[1],
refname = unescape_single_quote(fields[2]),
Expand Down Expand Up @@ -320,7 +321,7 @@ git.status = function(opts)

local gen_new_finder = function()
local expand_dir = vim.F.if_nil(opts.expand_dir, true)
local git_cmd = { "git", "status", "-z", "--", "." }
local git_cmd = git_command({ "status", "-z", "--", "." }, opts)

if expand_dir then
table.insert(git_cmd, #git_cmd - 1, "-u")
Expand All @@ -329,7 +330,6 @@ git.status = function(opts)
local output = utils.get_os_command_output(git_cmd, opts.cwd)

if #output == 0 then
print "No changes found"
utils.notify("builtin.git_status", {
msg = "No changes found",
level = "WARN",
Expand Down Expand Up @@ -379,31 +379,62 @@ git.status = function(opts)
:find()
end

local try_worktrees = function(opts)
local worktrees = conf.git_worktrees

if vim.tbl_isarray(worktrees) then
for _, wt in ipairs(worktrees) do
if vim.startswith(opts.cwd, wt.toplevel) then
opts.toplevel = wt.toplevel
opts.gitdir = wt.gitdir
if opts.use_git_root then
opts.cwd = wt.toplevel
end
return
end
end
end

error(opts.cwd .. " is not a git directory")
end

local current_path_toplevel = function()
local gitdir = vim.fn.finddir(".git", vim.fn.expand "%:p" .. ";")
if gitdir == "" then
return nil
end
return Path:new(gitdir):parent():absolute()
end

local set_opts_cwd = function(opts)
opts.use_git_root = vim.F.if_nil(opts.use_git_root, true)
if opts.cwd then
opts.cwd = vim.fn.expand(opts.cwd)
elseif opts.use_file_path then
opts.cwd = vim.fn.finddir(".git", vim.fn.expand "%:p" .. ";")
opts.cwd = current_path_toplevel()
if not opts.cwd then
opts.cwd = vim.fn.expand "%:p:h"
try_worktrees(opts)
return
end
else
opts.cwd = vim.loop.cwd()
end

-- Find root of git directory and remove trailing newline characters
local git_root, ret = utils.get_os_command_output({ "git", "rev-parse", "--show-toplevel" }, opts.cwd)
local use_git_root = vim.F.if_nil(opts.use_git_root, true)
local toplevel, ret = utils.get_os_command_output({ "git", "rev-parse", "--show-toplevel" }, opts.cwd)

if ret ~= 0 then
local in_worktree = utils.get_os_command_output({ "git", "rev-parse", "--is-inside-work-tree" }, opts.cwd)
local in_bare = utils.get_os_command_output({ "git", "rev-parse", "--is-bare-repository" }, opts.cwd)

if in_worktree[1] ~= "true" and in_bare[1] ~= "true" then
error(opts.cwd .. " is not a git directory")
try_worktrees(opts)
elseif in_worktree[1] ~= "true" and in_bare[1] == "true" then
opts.is_bare = true
end
else
if use_git_root then
opts.cwd = git_root[1]
if opts.use_git_root then
opts.cwd = toplevel[1]
end
end
end
Expand Down
19 changes: 19 additions & 0 deletions lua/telescope/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,25 @@ append(
]]
)

append(
"git_worktrees",
nil,
[[
A table of arrays of detached working trees with keys `gitdir` and `toplevel`.
Used to pass `--git-dir` and `--work-tree` flags to git commands when telescope fails
to infer the top-level directory of a given working tree based on cwd.
Example:
git_worktrees = {
{
toplevel = vim.env.HOME,
gitdir = vim.env.HOME .. '/.cfg'
}
}

Default: nil
]]
)

append(
"file_previewer",
function(...)
Expand Down
20 changes: 11 additions & 9 deletions lua/telescope/previewers/buffer_previewer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ local conf = require("telescope.config").values
local pscan = require "plenary.scandir"

local buf_delete = utils.buf_delete
local git_command = utils.__git_command

local previewers = {}

Expand Down Expand Up @@ -757,16 +758,15 @@ previewers.git_branch_log = defaulter(function(opts)
end,

define_preview = function(self, entry)
local cmd = {
"git",
local cmd = git_command({
"--no-pager",
"log",
"--graph",
"--pretty=format:%h -%d %s (%cr)",
"--abbrev-commit",
"--date=relative",
entry.value,
}
}, opts)

putils.job_maker(cmd, self.state.bufnr, {
value = entry.value,
Expand All @@ -791,7 +791,8 @@ previewers.git_stash_diff = defaulter(function(opts)
end,

define_preview = function(self, entry, _)
putils.job_maker({ "git", "--no-pager", "stash", "show", "-p", entry.value }, self.state.bufnr, {
local cmd = git_command({ "--no-pager", "stash", "show", "-p", entry.value }, opts)
putils.job_maker(cmd, self.state.bufnr, {
value = entry.value,
bufname = self.state.bufname,
cwd = opts.cwd,
Expand All @@ -814,7 +815,7 @@ previewers.git_commit_diff_to_parent = defaulter(function(opts)
end,

define_preview = function(self, entry)
local cmd = { "git", "--no-pager", "diff", entry.value .. "^!" }
local cmd = git_command({ "--no-pager", "diff", entry.value .. "^!" }, opts)
if opts.current_file then
table.insert(cmd, "--")
table.insert(cmd, opts.current_file)
Expand Down Expand Up @@ -845,7 +846,7 @@ previewers.git_commit_diff_to_head = defaulter(function(opts)
end,

define_preview = function(self, entry)
local cmd = { "git", "--no-pager", "diff", "--cached", entry.value }
local cmd = git_command({ "--no-pager", "diff", "--cached", entry.value }, opts)
if opts.current_file then
table.insert(cmd, "--")
table.insert(cmd, opts.current_file)
Expand Down Expand Up @@ -876,7 +877,7 @@ previewers.git_commit_diff_as_was = defaulter(function(opts)
end,

define_preview = function(self, entry)
local cmd = { "git", "--no-pager", "show" }
local cmd = git_command({ "--no-pager", "show" }, opts)
local cf = opts.current_file and Path:new(opts.current_file):make_relative(opts.cwd)
local value = cf and (entry.value .. ":" .. cf) or entry.value
local ft = cf and putils.filetype_detect(value) or "diff"
Expand Down Expand Up @@ -910,7 +911,7 @@ previewers.git_commit_message = defaulter(function(opts)
end,

define_preview = function(self, entry)
local cmd = { "git", "--no-pager", "log", "-n 1", entry.value }
local cmd = git_command({ "--no-pager", "log", "-n 1", entry.value }, opts)

putils.job_maker(cmd, self.state.bufnr, {
value = entry.value,
Expand Down Expand Up @@ -950,7 +951,8 @@ previewers.git_file_diff = defaulter(function(opts)
winid = self.state.winid,
})
else
putils.job_maker({ "git", "--no-pager", "diff", "HEAD", "--", entry.value }, self.state.bufnr, {
local cmd = git_command({ "--no-pager", "diff", "HEAD", "--", entry.value }, opts)
putils.job_maker(cmd, self.state.bufnr, {
value = entry.value,
bufname = self.state.bufname,
cwd = opts.cwd,
Expand Down
18 changes: 18 additions & 0 deletions lua/telescope/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -517,4 +517,22 @@ utils.__warn_no_selection = function(name)
})
end

--- Generate git command optionally with git env variables
---@param args string[]
---@param opts? table
---@return string[]
utils.__git_command = function(args, opts)
opts = opts or {}

local _args = { "git" }
if opts.gitdir then
vim.list_extend(_args, { "--git-dir", opts.gitdir })
end
if opts.toplevel then
vim.list_extend(_args, { "--work-tree", opts.toplevel })
end

return vim.list_extend(_args, args)
end

return utils