lazy.nvim icon indicating copy to clipboard operation
lazy.nvim copied to clipboard

bug: Duplicated plugins when case mismatch

Open Ram-Z opened this issue 9 months ago • 7 comments

Did you check docs and existing issues?

  • [x] I have read all the lazy.nvim docs
  • [x] I have updated the plugin to the latest version before submitting this issue
  • [x] I have searched the existing issues of lazy.nvim
  • [x] I have searched the existing issues of plugins related to this issue

Neovim version (nvim -v)

NVIM v0.10.4 Build type: RelWithDebInfo LuaJIT 2.1.1736781742

Operating system/version

Arch Linux

Describe the bug

When a plugin is declared a second time with misspelled case, the plugin is duplicated without warning. This can cause some weird behaviour that isn't immediately obvious how to resolve.

I suppose this is an issue only on case-sensitive filesystems.

Steps To Reproduce

  1. nvim -u repro.lua (see below)
  2. ls .repro/data/nvim/lazy
    • notice two checkouts of "tokyonight.nvim"

Expected Behavior

Preferably, no duplication is allowed and lazy would just understand to look in the correct folder regardless of the case. This could be done by:

  1. canonicalising all plugin checkouts to lower case (probably breaking change)
  2. doing a case-insensitive search in the data directory and selecting the already checked out folder

At the very least a warning should be emitted, maybe even an error.

Repro

vim.env.LAZY_STDPATH = ".repro"
load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))()

require("lazy.minit").repro({
  spec = {
    { "folke/tokyonight.nvim" },
    { "folke/Tokyonight.nvim" },
  },
})

Ram-Z avatar Mar 02 '25 14:03 Ram-Z

Duplicate #1228

dpetka2001 avatar Mar 03 '25 10:03 dpetka2001

The should be case-sensitive. Won't change this. Could lead to issues on case-sensitive file systems otherwise

The issue though is that the lack of case-insensitivity (CI) is causing issues on case-sensitive (CS) filesystems.

Given two plugin names;

require("lazy.minit").repro({
  spec = {
    { name = "user/plugin.nvim" },
    { name = "user/PLUGIN.NVIM" },
  },
})

The github urls are CI, so both of these resolve to the same repo. It's not possible to use case do differentiate them. When cloning, git will use the last path as the target folder, respecting the case. On CS filesystems, this results in two different folders, on CI systems it results in one, the case of which is determined by the first one cloned.

I don't know how the internals of lazy.nvim work, but I see a couple of problematic situations, assuming the created folder is created via git clone <url>:

  1. fs is CS and lazy does CS matching of names
    • there are two clone
    • there are two plugin configurations
    • lazy.nvim will consider that "user/plugin.nvim" and "user/PLUGIN.nvim" are two separate plugins
      • this is unexpected
  2. fs is CI and lazy does CS matching of namess
    • there is one clone
    • there are two plugin configurations
    • although there's only one checkout, the configurations are not merged
      • this is unexpected
  3. fs is CS and lazy does CI matching of names
    • there are two clone
    • there is one merged plugin configuration
    • lazy will properly merge the configurations, but an unused repo is also cloned
      • although working as expected, creates an unused checkout
  4. fs is CI and lazy does CI matching of names
    • there is one clone
    • there is one merged plugin configuration
    • lazy will work as expected

IMO, this should be handled somehow. Even if it is just a warning that says "duplicate plugin name detected" and then carries on.

Ram-Z avatar Mar 03 '25 12:03 Ram-Z

Doubt it based on maintainer's last reply regarding this. In the end, it's the user's responsibility in my personal opinion to not make spelling mistakes.

When maintainer is back from vacation (#1934), he will give his personal opinion on this.

PS: also doing git clone https://github.com/L3MON4D3/LuaSnip and https://github.com/L3MON4D3/luasnip results in 2 folders, so I guess lazy.nvim operates accordingly. (but i guess that might have to do with my filesystem)

dpetka2001 avatar Mar 03 '25 13:03 dpetka2001

PS: also doing git clone https://github.com/L3MON4D3/LuaSnip and https://github.com/L3MON4D3/luasnip results in 2 folders, so I guess lazy.nvim operates accordingly.

Indeed, but only on CS filesystems. And having two clones is not an issue, it is then adding both of them to the rtp that is.

But still, on CI fs, the merged configuration issue remains and is just as hard to debug.

When maintainer is back from vacation (#1934), he will give his personal opinion on this.

Yep. And thanks for triaging this, I didn't see the other issue.

Ram-Z avatar Mar 03 '25 13:03 Ram-Z

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.

github-actions[bot] avatar Apr 03 '25 02:04 github-actions[bot]

Waiting for @folke to comment.

Ram-Z avatar Apr 03 '25 07:04 Ram-Z

I have experienced the similar problem from a different perspective.

The current state of things

  1. Plugins are indexed using name property from the Spec;
  2. If the name is not provided, it is generated from the repository name in form nvim-telescope/telescope.nvim becomes telescope_nvim.

The problem

According to the documentation, name is very handy to use throughout the config enabling short user-friendly references like the following:

return {
  { "nvim-telescope/telescope.nvim", name = "telescope" },
  { "chrisgrieser/nvim-tinygit", dependencies = { "telescope" } },
}

However, a plugin may provide a dependency configuration in its own. In this example, the chrisgrieser/nvim-tinygit provides dependencies through lazy.lua in a simple manner, without a name; as it would be expected.

This leads to two installations of Telescope, one in the telescope directory as the name states, and another one in telescope.nvim directory as the Tinygit's lazy.lua states, and Lazy would also show telescope and telescope.nvim in the UI.

Depending on various lazy loading handlers (cmd and keys) on of this plugins will load first. If the first called is Tinygit, Telescope loads with the default configuration.

Repro

This is bare minimum reproduction that shows only two plugins cloned but the loading appears to be correct.

vim.env.LAZY_STDPATH = ".repro"
load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))()

require("lazy.minit").repro({
  spec = {
    { "nvim-telescope/telescope.nvim", name = "telescope" },
    { "chrisgrieser/nvim-tinygit" },
  },
})

Below is a more complex example but it does not really make the point as Tinygit used Telescope in an unknown to me way (lazy does not track importing from either plugin):

vim.env.LAZY_STDPATH = ".repro"
load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))()

require("lazy.minit").repro({
  spec = {
    {
      "nvim-telescope/telescope.nvim",
      name = "telescope",
      cmd = "Telescope",
      opts = {
        defaults = {
          mappings = {
            i = {
              ["<esc>"] = "close",
            },
          },
        },
      },
    },
    {
      "chrisgrieser/nvim-tinygit",
      cmd = "Tinygit",
    },
  },
})

Conclusion

I agree with the argument that it's the responsibility of the user to provide uniform configuration. In this example, parts of the configuration are provided by third parties. There is no way to instruct Lazy that the plugin in dependencies of other plugin is the same as in the user's config if the name was changed. On the other hand, Lazy does not create a uniform index of plugins taking into account all keys (to the best of my knowledge, url and version) but relies purely on name.

viktor-yakubiv avatar Apr 10 '25 14:04 viktor-yakubiv

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.

github-actions[bot] avatar May 11 '25 02:05 github-actions[bot]