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: Spin out/off branch #676

Merged
merged 22 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
abb1f0f
feat: Spin off branch
morgsmccauley Jul 28, 2023
affcc98
feat: Spin out branch
morgsmccauley Jul 28, 2023
0765fa6
test: Spin out/off branch
morgsmccauley Jul 29, 2023
a3ef9e9
Merge branch 'master' into feat/spin-off-branch
CKolkey Jul 29, 2023
de63179
refactor: Extract dirty check to own function
morgsmccauley Jul 30, 2023
cdae697
fix: Remove unneeded dirty check from `spin_off`
morgsmccauley Jul 30, 2023
275d5ce
feat: Checkout spin out branch if uncommitted changes
morgsmccauley Jul 30, 2023
34cbffa
feat: Handle uncommitted changes during branch spin out
morgsmccauley Jul 30, 2023
408c6ae
feat: Handle uncommitted changes during branch spin off
morgsmccauley Jul 30, 2023
5ad234d
refactor: Use `git.branch` rather than repo state directly
morgsmccauley Jul 30, 2023
13bc72f
fix: Get branch name before spin out/off
morgsmccauley Jul 30, 2023
ffbe847
refactor: Move `status.is_dirty()` to lib
morgsmccauley Jul 31, 2023
d4f786d
Merge branch 'master' into feat/spin-off-branch
morgsmccauley Jul 31, 2023
3fa72e0
refactor: Extract spin out/off logic to common function
morgsmccauley Jul 31, 2023
d16bb74
refactor: Skip dirty check when checkout is intended
morgsmccauley Jul 31, 2023
aa12245
refactor: Make spin off branch reset async
morgsmccauley Jul 31, 2023
86a9dac
fix: Use correct module for `is_dirty()` check
morgsmccauley Aug 2, 2023
d608f35
refactor: Remove unused return values
morgsmccauley Aug 2, 2023
715c6cf
fix: Emit correct operation for spin out/off
morgsmccauley Aug 3, 2023
d339e3a
test: Spin out/off branch
morgsmccauley Aug 3, 2023
d98aca4
Merge remote-tracking branch 'origin/master' into feat/spin-off-branch
morgsmccauley Aug 3, 2023
5657ecf
fix: lint
morgsmccauley Aug 3, 2023
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
4 changes: 4 additions & 0 deletions lua/neogit/lib/git/status.lua
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ local status = {
unstage_all = function()
git.cli.reset.call()
end,
is_dirty = function()
local repo = require("neogit.lib.git.repository")
morgsmccauley marked this conversation as resolved.
Show resolved Hide resolved
return #repo.staged.items > 0 or #repo.unstaged.items > 0
end
}

status.register = function(meta)
Expand Down
40 changes: 40 additions & 0 deletions lua/neogit/popups/branch/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,46 @@ local function parse_remote_branch_name(ref)
return remote, branch_name
end

local function spin_off_branch(checkout)
if git.status.is_dirty() and not checkout then
notif.create("Staying on HEAD due to uncommitted changes", vim.log.levels.INFO)
morgsmccauley marked this conversation as resolved.
Show resolved Hide resolved
checkout = true
end

local name = input.get_user_input("branch > ")
if not name or name == "" then
return
end

name, _ = name:gsub("%s", "-")
morgsmccauley marked this conversation as resolved.
Show resolved Hide resolved
git.branch.create(name)

local current_branch_name = git.branch.current_full_name()

if checkout then
git.cli.checkout.branch(name).call_sync()
end

local upstream = git.branch.upstream()
if upstream then
if checkout then
git.log.update_ref(current_branch_name, upstream)
else
git.cli.reset.hard.args(upstream).call()
end
end
end

M.spin_off_branch = operation("spin_off_branch", function()
spin_off_branch(true)
status.refresh(true, "spin_off_branch")
end)

M.spin_out_branch = operation("spin_out_branch", function()
spin_off_branch(false)
status.refresh(true, "spin_out_branch")
end)

M.checkout_branch_revision = operation("checkout_branch_revision", function(popup)
local selected_branch = FuzzyFinderBuffer.new(git.branch.get_all_branches()):open_async()
if not selected_branch then
Expand Down
4 changes: 2 additions & 2 deletions lua/neogit/popups/branch/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ function M.create()
:action("l", "local branch", actions.checkout_local_branch)
:new_action_group()
:action("c", "new branch", actions.checkout_create_branch)
:action("s", "new spin-off")
:action("s", "new spin-off", actions.spin_off_branch)
:action("w", "new worktree")
:new_action_group("Create")
:action("n", "new branch", actions.create_branch)
:action("S", "new spin-out")
:action("S", "new spin-out", actions.spin_out_branch)
:action("W", "new worktree")
:new_action_group("Do")
:action("C", "Configure...", actions.configure_branch)
Expand Down
99 changes: 98 additions & 1 deletion tests/specs/neogit/operations_spec.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require("plenary.async").tests.add_to_env()
local async = require("plenary.async")
async.tests.add_to_env()
local eq = assert.are.same
local operations = require("neogit.operations")
local harness = require("tests.util.git_harness")
Expand Down Expand Up @@ -110,4 +111,100 @@ describe("branch popup", function()
assert.False(vim.tbl_contains(get_git_branches(), "second-branch"))
end)
)

describe("spin out", function()
it(
"moves unpushed commits to a new branch unchecked out branch",
in_prepared_repo(function()
util.system([[
git reset --hard origin/master
touch feature.js
git add .
git commit -m 'some feature'
]])
async.util.block_on(status.reset)

local input_branch = "spin-out-branch"
input.values = { input_branch }

local branch_before = get_current_branch()
local commit_before = get_git_rev(branch_before)

local remote_commit = get_git_rev("origin/" .. branch_before)

act("bS<cr><cr>")
operations.wait("spin_out_branch")

local branch_after = get_current_branch()

eq(branch_after, branch_before)
eq(get_git_rev(input_branch), commit_before)
eq(get_git_rev(branch_before), remote_commit)
end)
)

it(
"checks out the new branch if uncommitted changes present",
in_prepared_repo(function()
util.system([[
git reset --hard origin/master
touch feature.js
git add .
git commit -m 'some feature'
touch wip.js
git add .
]])
async.util.block_on(status.reset)

local input_branch = "spin-out-branch"
input.values = { input_branch }

local branch_before = get_current_branch()
local commit_before = get_git_rev(branch_before)

local remote_commit = get_git_rev("origin/" .. branch_before)

act("bS<cr><cr>")
operations.wait("spin_out_branch")

local branch_after = get_current_branch()

eq(branch_after, input_branch)
eq(get_git_rev(branch_after), commit_before)
eq(get_git_rev(branch_before), remote_commit)
end)
)
end)

describe("spin off", function()
it(
"moves unpushed commits to a new checked out branch",
in_prepared_repo(function()
util.system([[
git reset --hard origin/master
touch feature.js
git add .
git commit -m 'some feature'
]])
async.util.block_on(status.reset)

local input_branch = "spin-off-branch"
input.values = { input_branch }

local branch_before = get_current_branch()
local commit_before = get_git_rev(branch_before)

local remote_commit = get_git_rev("origin/" .. branch_before)

act("bs<cr><cr>")
operations.wait("spin_off_branch")

local branch_after = get_current_branch()

eq(branch_after, input_branch)
eq(get_git_rev(branch_after), commit_before)
eq(get_git_rev(branch_before), remote_commit)
end)
)
end)
end)