linrongbin16 / gitlinker.nvim

Maintained fork of ruifm's gitlinker, refactored with bug fixes, ssh aliases, blame support and other improvements.
GNU General Public License v3.0
163 stars 9 forks source link

Question: Working with fzf-lua's bcommit #237

Closed hqkhan closed 6 months ago

hqkhan commented 6 months ago

Details

This is more about asking for guidance on how I can make gitlinker work with fzf-lua's bcommit. With bcommit, I can open a file in a new buffer at the selected commit. This newly created buffer has the following naming format: <file_name>[commit_hash]. Example: editorconfig[7ac8301].

This doesn't work currently as we don't move commits or change branches from wherever we're at. It simply opens a new buffer with changes from that commit.

Now, if gitlinker went through all the checks auto-magically and put me in my router function, I can do a check to see if the name follows the naming format described above and create my URL accordingly. However, we run into an error that the file (newly created buffer) doesn't exist in the commit which makes sense. I've provided my config and error message below from log file.

I wanted to ask if there's a way for me to bypass this check of git cat-file -e <commit_hash>:<file_name>. The rest of the info gathering git calls can remain as I've already specified a remote in my keybind (see below). If I can get to my router function, I should be able to get it to work.

Config

return {
  "linrongbin16/gitlinker.nvim",
  cmd = "GitLink",
  -- dev = true,
  opts = { },
  keys = {
    { "<leader>gy", "<cmd>GitLink remote=origin<cr>", mode = { "n", "v" }, desc = "Yank git link" },
  },
  config = function(opts)
    opts = opts or {}
    opts.debug = true
    opts.file_log = true
    opts.router = {
      browse = {
        ["^git.**"] = your_router
      },
    }
    require "gitlinker".setup(opts)
  end
}

Error

2024-04-18 03:30:05,985539 [@/home/hqkhan/.local/share/nvim/lazy/gitlinker.nvim/lua/gitlinker/git.lua:48] DEBUG: |_run_cmd| args:{ "git", "cat-file", "-e", "30ee1626632ce34a55d6a8c17cd2fcd9d984268f:.editorconfig[7ac8301]" }, cwd:"/local/home/hqkhan/Sources/nvim/gitlinker.nvim"
2024-04-18 03:30:05,990855 [@/home/hqkhan/.local/share/nvim/lazy/gitlinker.nvim/lua/gitlinker/git.lua:63] DEBUG: |_run_cmd| result:{
  stderr = { "fatal: path '.editorconfig[7ac8301]' does not exist in '30ee1626632ce34a55d6a8c17cd2fcd9d984268f'" },
  stdout = {},
  <metatable> = <1>{
    __index = <table 1>,
    has_err = <function 1>,
    has_out = <function 2>,
    new = <function 3>,
    print_err = <function 4>
  }
}
2024-04-18 03:30:05,991058 [@/home/hqkhan/.local/share/nvim/lazy/gitlinker.nvim/lua/gitlinker/git.lua:37] ERROR: fatal: path '.editorconfig[7ac8301]' does not exist in '30ee1626632ce34a55d6a8c17cd2fcd9d984268f'
linrongbin16 commented 6 months ago

I could add an option, for example (something like this):

require("gitlinker").setup({
  filename_formatter = function(bufnr, git_root_dir)
    local bufname = vim.api.nvim_buf_get_name(bufnr)
    local rel_filename = get relative path of `bufname` based on `git_root_dir`
    return rel_filename
  end
})

By default, the filename_formatter will always returns the current buffer's relative path. Which is exactly what we have now.

But for your use case, you can overwrite the default configs with:


--- @param src The string value
--- @param target The `target` string to be found
--- @return integer? The `target` string index been found in `src`
local function string_find(src, target)
  ...
end

require("gitlinker").setup({
  filename_formatter = function(bufnr)
    local bufname = vim.api.nvim_buf_get_name(bufnr)
    local filename_end_pos = string_find(bufname, '[')
    if type(filename_end_pos) == 'number' and filename_end_pos > 0 then
      return string.sub(bufname, 1, filename_end_pos-1)
    else
      -- for other cases, still use the old method
      local rel_filename = get relative path of `bufname` based on `git_root_dir`
      return rel_filename
    end
  end
})

Does that look good for your use case?

hqkhan commented 6 months ago

Hi @linrongbin16 . Thank you for such a quick response!

That does look good to me for this case! To confirm this will still be running git cat-file -e <commit_hash_of_head>:<file_name> even though the current buffer is checking out changes from a different commit hash. That said, the file is expected to exist by definition of bcommit so we should be good there.

linrongbin16 commented 6 months ago

oh......., It seems cannot use the commit ID from fzf-lua's bcommit result, unless we add 1 more parameter (for example) rev to allow user input a customize commit ID to overwrite the auto detected.

linrongbin16 commented 6 months ago

RFC about these 2 parameters

Based on current naming style of this plugin, I will name them:

Note: these 2 parameters will be add to both user command GitLink and the API require("gitlinker").link.

So you can use with either:

  1. :GitLink file=xxx rev=xxx
  2. require("gitlinker").link({file="xxx", rev="xxx"})

Once done, you should be able to define a key mapping like this:


--- @param s The string value to be find
--- @param c The target string to find
--- @return integer? The first index of target `c` been found in `s`, return `nil` if not found
local function string_find(s, c)
  -- implement find function
end

local function file_on_bcommit()
  local bufnr = vim.api.nvim_get_current_buf()
  local bufname = vim.api.nvim_buf_get_name()
  local first_left_bracket_pos = string_find(bufname, '[')
  if type(first_left_bracket_pos) == 'number' and first_left_bracket_pos > 0 then
    local relpath = string.sub(bufname, 1, first_left_bracket_pos-1)
    return relpath
  end

  -- return nil, this plugin will use the default generated file path
  return nil
end

local function rev_on_bcommit()
  local bufnr = vim.api.nvim_get_current_buf()
  local bufname = vim.api.nvim_buf_get_name()
  local first_left_bracket_pos = string_find(bufname, '[')
  if type(first_left_bracket_pos) == 'number' and first_left_bracket_pos > 0 then
    local rev = string.sub(bufname, first_left_bracket_pos+1, string.len(bufname)-1)
    return rev
  end

  -- return nil, so this plugin will use the default generated commit ID
  return nil
end

vim.keymap.set(
  {"n", 'v'},
  "<leader>gF",
  function()
    require("gitlinker").link({
      file = file_on_bcommit(),
      rev = rev_on_bcommit(),
    })
  end,
  { silent = true, noremap = true, desc = "GitLink on fzf-lua bcommit" }
)
hqkhan commented 6 months ago

Oo this is interesting. I think that should work. I think I'll also wrap GitLink command with my own command. My own command will simply look at the buffer path to see if it's of bcommit. If it is, then it'll call gitlinker.link underneath or simply continue on to call GitLink the normal way.

That way, there's only one keybind I need to remember and it should work for both cases.

linrongbin16 commented 6 months ago

hi @hqkhan ,

New feature is merged into master, have a try!

You can use something like :GitLink! file=lua/gitlinker.lua, and :GitLink! blame rev=839215b.

hqkhan commented 6 months ago

Awesome! Ty! Will be trying soon and I'll post back here with results :D

hqkhan commented 6 months ago

Hi @linrongbin16. I may be missing something but the path checker is still causing issues.

2024-04-24 04:05:29,369436 [@/home/hqkhan/.local/share/nvim/lazy/gitlinker.nvim/lua/gitlinker/git.lua:48] DEBUG: |_run_cmd| args:{ "git", "cat-file", "-e", "94d726ea019be2b6b59e2a9f42fc90010a36209b:.editorconfig[7ac8301]" }, cwd:"/local/home/hqkhan/Sources/nvim/gitlinker.nvim"
2024-04-24 04:05:29,374767 [@/home/hqkhan/.local/share/nvim/lazy/gitlinker.nvim/lua/gitlinker/git.lua:63] DEBUG: |_run_cmd| result:{
  stderr = { "fatal: path '.editorconfig[7ac8301]' does not exist in '94d726ea019be2b6b59e2a9f42fc90010a36209b'" },
  stdout = {},
  <metatable> = <1>{
    __index = <table 1>,
    has_err = <function 1>,
    has_out = <function 2>,
    new = <function 3>,
    print_err = <function 4>
  }
}
2024-04-24 04:05:29,375184 [@/home/hqkhan/.local/share/nvim/lazy/gitlinker.nvim/lua/gitlinker/git.lua:37] ERROR: fatal: path '.editorconfig[7ac8301]' does not exist in '94d726ea019be2b6b59e2a9f42fc90010a36209b'

In the case of rev being provided, shouldn't gitlink be checking if the file path exists in that rev instead of checking where it's currently at?

linrongbin16 commented 6 months ago

it looks like my implementation forgot to skip these checkings.

I may fix these.


Update: I fixed this issue in #240 , @hqkhan would you please take a look?

hqkhan commented 6 months ago

Hi @linrongbin16. It's working! I just need to parse the file name better but other than that, it looks good from GitLinker's side as far as I can tell.

hqkhan commented 6 months ago

Wanted to post my final working solution that I've tested.

local function string_find(s, c)
  return string.find(s, c)
end

local function get_git_filename(filename)
  local output = vim.fn.system { 'git', 'ls-files', '--full-name', filename }
  if output == "" then
    return nil
  end
  output = output:gsub("\n", "")
  return output
end

local function file_on_bcommit()
  local bufnr = vim.api.nvim_get_current_buf()
  local bufname = vim.api.nvim_buf_get_name(bufnr)
  local first_left_bracket_pos = string_find(bufname, "%[")
  if type(first_left_bracket_pos) == 'number' and first_left_bracket_pos > 1 then
    local relpath = string.sub(bufname, 1, first_left_bracket_pos - 1)
    local git_path = get_git_filename(relpath)
    -- Make sure it returned something, otherwise let's return nil so gitlinker can do default stuff
    return git_path
  end
  -- return nil, this plugin will use the default generated file path
  return nil
end

local function rev_on_bcommit()
  local bufnr = vim.api.nvim_get_current_buf()
  local bufname = vim.api.nvim_buf_get_name(bufnr)
  local first_left_bracket_pos = string_find(bufname, "%[")
  if type(first_left_bracket_pos) == 'number' and first_left_bracket_pos > 1 then
    local rev = string.sub(bufname, first_left_bracket_pos + 1, string.len(bufname) - 1)
    return rev
  end
  -- return nil, so this plugin will use the default generated commit ID
  return nil
end

local function has_value(tab, val)
  for _, value in ipairs(tab) do
    if value == val then
      return true
    end
  end
  return false
end

local function get_remote()
  local output = vim.fn.system { 'git', 'remote' }
  local remotes = {}
  if output ~= nil then
    for s in output:gmatch("[^\r\n]+") do
      table.insert(remotes, s)
    end
  end
  if #remotes == 0 or not has_value(remotes, "origin") then
    return nil
  end
  return "origin"
end

return {
  "linrongbin16/gitlinker.nvim",
  cmd = "GitLink",
  opts = {},
  keys = {
    {
      "<leader>gy",
      function()
        require("gitlinker").link({
          file = file_on_bcommit(),
          rev = rev_on_bcommit(),
          remote = get_remote(),
        })
      end,
      mode = { "n", "v" },
      desc = "Yank git link or fzf-lua bcommit"
    },
  },
  config = function(opts)
    opts = opts or {}
    opts.debug = true
    opts.file_log = true
    opts.router = {
      browse = {
        ["^git.**"] = your_router
      },
    }
    require "gitlinker".setup(opts)
  end
}

Might have some rough edges that I'll find out about later on but overall, it's working nicely. Thanks again for your help :)