Skip to content
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@ neogit.setup {
["y"] = "ShowRefs",
["$"] = "CommandHistory",
["Y"] = "YankSelected",
["gp"] = "GoToParentRepo",
["<c-r>"] = "RefreshBuffer",
["<cr>"] = "GoToFile",
["<s-cr>"] = "PeekFile",
Expand Down
1 change: 1 addition & 0 deletions doc/neogit.txt
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ The following mappings can all be customized via the setup function.
["y"] = "ShowRefs",
["$"] = "CommandHistory",
["Y"] = "YankSelected",
["gp"] = "GoToParentRepo",
["<c-r>"] = "RefreshBuffer",
["<cr>"] = "GoToFile",
["<s-cr>"] = "PeekFile",
Expand Down
18 changes: 18 additions & 0 deletions lua/neogit/buffers/status/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1276,6 +1276,18 @@ M.n_unstage_staged = function(self)
end)
end

---Opens neogit on the parent repo if if we are in a submodule
---@param self StatusBuffer
M.n_goto_parent_repo = function(self)
return function()
local parent = self:parent_repo()
if parent then
self:close()
require("neogit").open { cwd = parent }
end
end
end

---@param self StatusBuffer
---@return fun(): nil
M.n_goto_file = function(self)
Expand All @@ -1284,6 +1296,12 @@ M.n_goto_file = function(self)

-- Goto FILE
if item and item.absolute_path then
if self:has_submodule(item.absolute_path) then
self:close()
require("neogit").open { cwd = item.absolute_path }
return
end

local cursor = translate_cursor_location(self, item)
self:close()
vim.schedule_wrap(open)("edit", item.absolute_path, cursor)
Expand Down
40 changes: 40 additions & 0 deletions lua/neogit/buffers/status/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,52 @@ M.__index = M

local instances = {}

---@class SubmoduleInfo
---@field submodules string[] A list with the relative paths to the project's submodules
---@field parent_repo string? If we are in a submodule, cache the abs path to the parent repo

---@type table<string, SubmoduleInfo>
local submodule_info_per_root = {}

---@return string?
function M:parent_repo()
local info = submodule_info_per_root[self.root]
return info and info.parent_repo
end

---@return string[]
function M:submodules()
local info = submodule_info_per_root[self.root]
return info and info.submodules or {}
end

---@param abs_path string
---@return boolean
function M:has_submodule(abs_path)
local dir = require("plenary.path"):new(abs_path)
if not dir:exists() or not dir:is_dir() then
return false
end
local rel_path = dir:make_relative(self.cwd)
for _, submodule in ipairs(self:submodules()) do
if submodule == rel_path then
return true
end
end
return false
end

---@param instance StatusBuffer
---@param dir string
function M.register(instance, dir)
local dir = vim.fs.normalize(dir)
logger.debug("[STATUS] Registering instance for: " .. dir)

instances[dir] = instance
submodule_info_per_root[instance.root] = {
submodules = git.submodule.list(),
parent_repo = git.rev_parse.parent_repo(),
}
end

---@param dir? string
Expand Down Expand Up @@ -170,6 +209,7 @@ function M:open(kind)
[mappings["Unstage"]] = self:_action("n_unstage"),
[mappings["UnstageStaged"]] = self:_action("n_unstage_staged"),
[mappings["GoToFile"]] = self:_action("n_goto_file"),
[mappings["GoToParentRepo"]] = self:_action("n_goto_parent_repo"),
[mappings["TabOpen"]] = self:_action("n_tab_open"),
[mappings["SplitOpen"]] = self:_action("n_split_open"),
[mappings["VSplitOpen"]] = self:_action("n_vertical_split_open"),
Expand Down
2 changes: 2 additions & 0 deletions lua/neogit/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ end
---| "Untrack"
---| "RefreshBuffer"
---| "GoToFile"
---| "GoToParentRepo",
---| "PeekFile"
---| "VSplitOpen"
---| "SplitOpen"
Expand Down Expand Up @@ -705,6 +706,7 @@ function M.get_default_values()
["y"] = "ShowRefs",
["$"] = "CommandHistory",
["Y"] = "YankSelected",
["gp"] = "GoToParentRepo",
["<c-r>"] = "RefreshBuffer",
["<cr>"] = "GoToFile",
["<s-cr>"] = "PeekFile",
Expand Down
1 change: 1 addition & 0 deletions lua/neogit/lib/git.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
---@field sequencer NeogitGitSequencer
---@field stash NeogitGitStash
---@field status NeogitGitStatus
---@field submodule NeogitGitSubmodule
---@field tag NeogitGitTag
---@field worktree NeogitGitWorktree
---@field hooks NeogitGitHooks
Expand Down
7 changes: 7 additions & 0 deletions lua/neogit/lib/git/cli.lua
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ end
---@field null_separated self
---@field porcelain fun(string): self

---@class GitCommandSubmodule: GitCommandBuilder

---@class GitCommandLog: GitCommandBuilder
---@field oneline self
---@field branches self
Expand Down Expand Up @@ -321,6 +323,7 @@ end
---@field no_flags self
---@field symbolic self
---@field symbolic_full_name self
---@field show_superproject_working_tree self
---@field abbrev_ref fun(ref: string): self

---@class GitCommandCherryPick: GitCommandBuilder
Expand Down Expand Up @@ -374,6 +377,7 @@ end
---@field show-ref GitCommandShowRef
---@field stash GitCommandStash
---@field status GitCommandStatus
---@field submodule GitCommandSubmodule
---@field tag GitCommandTag
---@field update-index GitCommandUpdateIndex
---@field update-ref GitCommandUpdateRef
Expand Down Expand Up @@ -468,6 +472,8 @@ local configurations = {
},
},

submodule = config {},

log = config {
flags = {
oneline = "--oneline",
Expand Down Expand Up @@ -971,6 +977,7 @@ local configurations = {
no_flags = "--no-flags",
symbolic = "--symbolic",
symbolic_full_name = "--symbolic-full-name",
show_superproject_working_tree = "--show-superproject-working-tree",
},
options = {
abbrev_ref = "--abbrev-ref",
Expand Down
5 changes: 5 additions & 0 deletions lua/neogit/lib/git/rev_parse.lua
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,9 @@ function M.full_name(rev)
.call({ hidden = true, ignore_error = true }).stdout[1]
end

---@return string?
function M.parent_repo()
return git.cli["rev-parse"].show_superproject_working_tree.call({ hidden = true, ignore_error = true }).stdout[1]
end

return M
14 changes: 14 additions & 0 deletions lua/neogit/lib/git/submodule.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
local git = require("neogit.lib.git")

---@class NeogitGitSubmodule
local M = {}

---@return string[]
function M.list()
local result = git.cli.submodule.call({ hidden = true, ignore_error = true }).stdout
return vim.tbl_map(function(el)
return vim.split(vim.trim(el), " +", { trimempty = true })[2]
end, result)
end

return M
79 changes: 79 additions & 0 deletions spec/buffers/status_buffer_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require "spec_helper"
require "fileutils"

RSpec.describe "Status Buffer", :git, :nvim do
it "renders, raising no errors" do
Expand Down Expand Up @@ -82,4 +83,82 @@
# context "with tracked file" do
# end
end

describe "submodule navigation" do
let(:submodule_path) { File.join("deps", "nested-submodule") }
let(:submodule_repo_root) { File.expand_path(submodule_path) }
let!(:submodule_source_dir) { Dir.mktmpdir("neogit-submodule-source") }

before do
initialize_submodule_source

git.config("protocol.file.allow", "always")
unless system("git", "-c", "protocol.file.allow=always", "submodule", "add", submodule_source_dir, submodule_path)
raise "Failed to add submodule"
end

git.commit("Add submodule")

File.open(File.join(submodule_path, "file.txt"), "a") { _1.puts("local change") }
nvim.lua(<<~LUA)
local status = require("neogit.buffers.status")
local instance = status.instance()
if instance then
status.register(instance, vim.uv.cwd())
end
LUA
nvim.refresh
end

after do
FileUtils.remove_entry(submodule_source_dir) if File.directory?(submodule_source_dir)
end

it "opens submodule status and returns to the parent repo twice" do
# First jump and back
await do
expect(nvim.screen.join("\n")).to include("#{submodule_path} (modified content)")
end

nvim.move_to_line(submodule_path)
nvim.keys("<cr>")

await do
expect(nvim.fn("getcwd", [])).to eq(submodule_repo_root)
expect(nvim.screen.join("\n")).to include("modified file.txt")
end

nvim.keys("gp")

await do
expect(nvim.fn("getcwd", [])).to eq(Dir.pwd)
expect(nvim.screen.join("\n")).to include("#{submodule_path} (modified content)")
end

# Second jump and back
nvim.move_to_line(submodule_path)
nvim.keys("<cr>")

await do
expect(nvim.fn("getcwd", [])).to eq(submodule_repo_root)
expect(nvim.screen.join("\n")).to include("modified file.txt")
end

nvim.keys("gp")

await do
expect(nvim.fn("getcwd", [])).to eq(Dir.pwd)
expect(nvim.screen.join("\n")).to include("#{submodule_path} (modified content)")
end
end

def initialize_submodule_source
repo = Git.init(submodule_source_dir)
repo.config("user.email", "test@example.com")
repo.config("user.name", "tester")
File.write(File.join(submodule_source_dir, "file.txt"), "submodule file\n")
repo.add("file.txt")
repo.commit("Initial submodule commit")
end
end
end
Loading