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
13 changes: 13 additions & 0 deletions lua/diffview/scene/views/diff/diff_view.lua
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,19 @@ local update_files_impl = debounce.debounce_trailing(
if new_head then
old_file:update_heads(new_head)
end

-- Give the adapter a chance to refresh LOCAL buffer content in
-- place. Jj rewrites working-copy files even when the LOCAL
-- rev's `object_name()` is stable (it's always "UNKNOWN"), so
-- `refresh_revs` can't catch that drift and the NOOP branch
-- would otherwise leave the LOCAL side showing stale content.
-- `on_local_buffer_reused` is a no-op for adapters that don't
-- rewrite working-copy files (git, hg).
for _, f in ipairs(old_file.layout:files()) do
if f.rev.type == RevType.LOCAL and f.bufnr and api.nvim_buf_is_valid(f.bufnr) then
self.adapter:on_local_buffer_reused(f.bufnr)
end
end
end
end

Expand Down
93 changes: 93 additions & 0 deletions lua/diffview/tests/functional/diff_view_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,99 @@ describe("diffview.scene.views.diff.DiffView", function()
end)
)

-- The NOOP-keep path routes LOCAL sides through
-- `adapter:on_local_buffer_reused` so working-copy content is refreshed
-- in place, without destroying and re-loading the diff buffers. Jj's
-- adapter relies on this (its LOCAL `object_name()` is always "UNKNOWN",
-- so `refresh_revs` can't detect a rewritten working copy) and dropped
-- its `force_entry_refresh_on_noop` override so this path is taken
-- instead of the entry-swap path that briefly attaches diff windows to
-- `diffview://null`.
it(
"calls on_local_buffer_reused for LOCAL files kept across a NOOP refresh",
test_utils.async_test(function()
local repo = make_repo()
local view

local ok, err = pcall(function()
local file_spec = {
working = {
{ path = "init.txt", status = "M", stats = { additions = 0, deletions = 0 } },
},
staged = {},
conflicting = {},
}

view = CDiffView({
git_root = repo,
left = Rev(RevType.COMMIT, run({ "git", "rev-parse", "HEAD" }, repo), true),
right = Rev(RevType.LOCAL),
files = file_spec,
update_files = function()
return file_spec
end,
get_file_data = function()
return {}
end,
})

view:open()
vim.wait(2000, function()
return view.initialized
end, 10)

local entry = view.files.working[1]
assert.is_truthy(entry)

-- Stamp a scratch bufnr on the LOCAL side. The CDiffView test
-- harness's `get_file_data` is a no-op and the async `set_file`
-- that would normally load LOCAL buffers is fire-and-forget
-- from `update_files_impl`, so `bufnr` may still be nil at this
-- point. Stamping it deterministically makes the
-- `nvim_buf_is_valid` gate pass and gives us a stable value to
-- assert on.
local local_file
for _, f in ipairs(entry.layout:files()) do
if f.rev.type == RevType.LOCAL then
local_file = f
break
end
end
assert.is_truthy(local_file)
local scratch = vim.api.nvim_create_buf(false, true)
local_file.bufnr = scratch

local reused_bufnrs = {}
view.adapter.on_local_buffer_reused = function(_, bufnr)
table.insert(reused_bufnrs, bufnr)
end

local refresh_done = false
view:update_files(function()
refresh_done = true
end)
vim.wait(2000, function()
return refresh_done
end, 10)
assert.is_true(refresh_done)

-- Kept-entry path: the entry survives (git's default) and the
-- LOCAL bufnr was handed to `on_local_buffer_reused`. Jj drops
-- its `force_entry_refresh_on_noop` override so this is the
-- path it takes too.
eq(entry, view.files.working[1])
assert.is_true(#reused_bufnrs >= 1)
eq(scratch, reused_bufnrs[#reused_bufnrs])
end)

close_view(view)
cleanup_repo(repo)
if not ok then
error(err)
end
end)
)

-- Regression: the wrapped impl signature were changed from
-- (self, callback) to (self, opts, callback). Legacy callers using
-- update_files(callback) would otherwise dereference opts.force on a
Expand Down
33 changes: 18 additions & 15 deletions lua/diffview/tests/functional/jj_adapter_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -145,24 +145,27 @@ describe("diffview.vcs.adapters.jj", function()
end)

describe("force_entry_refresh_on_noop()", function()
it("returns true for ranges that include LOCAL", function()
-- Jj no longer forces an entry rebuild on NOOP: the diff view's
-- NOOP-keep path now routes LOCAL sides through `on_local_buffer_reused`
-- (checktime), which reloads any jj-rewritten working-copy content in
-- place without briefly attaching diff windows to `diffview://null`.
it("inherits the base 'false' for every rev pair", function()
local adapter = new_adapter()
local ok = adapter:force_entry_refresh_on_noop(
adapter.Rev(RevType.COMMIT, "left_hash"),
adapter.Rev(RevType.LOCAL)
)

eq(true, ok)
end)

it("returns false for commit-to-commit ranges", function()
local adapter = new_adapter()
local ok = adapter:force_entry_refresh_on_noop(
adapter.Rev(RevType.COMMIT, "left_hash"),
adapter.Rev(RevType.COMMIT, "right_hash")
eq(
false,
adapter:force_entry_refresh_on_noop(
adapter.Rev(RevType.COMMIT, "left_hash"),
adapter.Rev(RevType.LOCAL)
)
)
eq(
false,
adapter:force_entry_refresh_on_noop(
adapter.Rev(RevType.COMMIT, "left_hash"),
adapter.Rev(RevType.COMMIT, "right_hash")
)
)

eq(false, ok)
end)
end)

Expand Down
15 changes: 6 additions & 9 deletions lua/diffview/vcs/adapters/jj/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -677,15 +677,12 @@ function JjAdapter:refresh_revs(rev_arg, left, right)
return new_left, new_right
end

---@param left Rev
---@param right Rev
---@return boolean
function JjAdapter:force_entry_refresh_on_noop(left, right)
return self:has_local(left, right)
end

---Jj may rewrite working-copy files when revisions change, so reload the
---buffer from disk when it is reused.
---Jj may rewrite working-copy files (its working copy is a commit) even
---when the LOCAL rev's `object_name()` looks unchanged, so the diff view's
---NOOP-keep path routes every LOCAL side through this hook to catch the
---rewrite via `checktime`. Overriding `force_entry_refresh_on_noop` to
---force a full entry rebuild would also work but tears down and re-loads
---the diff buffers, briefly attaching them to `diffview://null`.
---@param bufnr integer
function JjAdapter:on_local_buffer_reused(bufnr)
local api = vim.api
Expand Down
Loading