Skip to content
Open
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
134 changes: 22 additions & 112 deletions internal/actions/get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package actions_test
import (
"testing"

"github.com/google/go-github/v62/github"
"github.com/stretchr/testify/require"

"github.com/getstackit/stackit/internal/actions"
Expand All @@ -12,126 +11,37 @@ import (
)

func TestGetAction(t *testing.T) {
t.Run("resolves PR number and fetches branches", func(t *testing.T) {
s := scenario.NewScenario(t, testhelpers.BasicSceneSetup)
s.WithInitialCommit()
t.Run("reparents an existing branch using GitHub base info without carrying old parent commits", func(t *testing.T) {
s := scenario.NewScenario(t, testhelpers.BasicSceneSetup).
WithLinearStack("a", "b")

// Mock GitHub setup
ghConfig := testhelpers.NewMockGitHubServerConfig()
pr := &github.PullRequest{
Number: new(123),
Head: &github.PullRequestBranch{Ref: new("feature-a")},
Base: &github.PullRequestBranch{Ref: new("main")},
Title: new("Feature A"),
}
ghConfig.PRs["feature-a"] = pr
ghConfig.CreatedPRs = append(ghConfig.CreatedPRs, pr)
ghClient, owner, repo := testhelpers.NewMockGitHubClient(t, ghConfig)
s.Context.GitHubClient = testhelpers.NewMockGitHubClientInterface(ghClient, owner, repo, ghConfig)

// Create a bare repository to act as the remote
remoteDir := t.TempDir()
s.RunGit("init", "--bare", remoteDir)

// Create remote branch feature-a by creating it and pushing it to origin
s.RunGit("checkout", "-b", "feature-a").
RunGit("remote", "add", "origin", remoteDir).
RunGit("push", "-u", "origin", "feature-a").
RunGit("checkout", "main").
RunGit("branch", "-D", "feature-a")

// Run GetAction with PR number
err := actions.GetAction(s.Context, "123", actions.GetOptions{}, &actions.GetNullHandler{})
_, err := s.Scene.Repo.CreateBareRemote("origin")
require.NoError(t, err)

// Verify branch feature-a exists and is tracked
require.True(t, s.Engine.GetBranch("feature-a").IsTracked())
require.Equal(t, "main", s.Engine.GetBranch("feature-a").GetParent().GetName())
})

t.Run("crawls ancestors via GitHub PRs", func(t *testing.T) {
s := scenario.NewScenario(t, testhelpers.BasicSceneSetup)
s.WithInitialCommit()

// Mock GitHub setup: feature-b -> feature-a -> main
ghConfig := testhelpers.NewMockGitHubServerConfig()
prB := &github.PullRequest{
Number: new(2),
Head: &github.PullRequestBranch{Ref: new("feature-b")},
Base: &github.PullRequestBranch{Ref: new("feature-a")},
for _, branch := range []string{"main", "a", "b"} {
require.NoError(t, s.Scene.Repo.PushBranch("origin", branch))
}
prA := &github.PullRequest{
Number: new(1),
Head: &github.PullRequestBranch{Ref: new("feature-a")},
Base: &github.PullRequestBranch{Ref: new("main")},
}
ghConfig.PRs["feature-b"] = prB
ghConfig.PRs["feature-a"] = prA
ghClient, owner, repo := testhelpers.NewMockGitHubClient(t, ghConfig)
s.Context.GitHubClient = testhelpers.NewMockGitHubClientInterface(ghClient, owner, repo, ghConfig)

// Create a bare repository to act as the remote
remoteDir := t.TempDir()
s.RunGit("init", "--bare", remoteDir)
mockConfig := testhelpers.NewMockGitHubServerConfig()
rawClient, owner, repo := testhelpers.NewMockGitHubClient(t, mockConfig)
s.Context.GitHubClient = testhelpers.NewMockGitHubClientInterface(rawClient, owner, repo, mockConfig)

// Create remote branches
s.RunGit("checkout", "-b", "feature-a").
RunGit("checkout", "-b", "feature-b").
RunGit("remote", "add", "origin", remoteDir).
RunGit("push", "-u", "origin", "feature-a").
RunGit("push", "-u", "origin", "feature-b").
RunGit("checkout", "main").
RunGit("branch", "-D", "feature-a").
RunGit("branch", "-D", "feature-b")
prData := testhelpers.DefaultPRData()
prData.Number = 101
prData.Head = "b"
prData.Base = "main"
mockConfig.PRs["b"] = testhelpers.NewSamplePullRequest(prData)

// Run GetAction for feature-b
err := actions.GetAction(s.Context, "feature-b", actions.GetOptions{}, &actions.GetNullHandler{})
require.NoError(t, err)
s.Checkout("b")

// Verify both branches are tracked correctly
require.True(t, s.Engine.GetBranch("feature-a").IsTracked())
require.True(t, s.Engine.GetBranch("feature-b").IsTracked())
require.Equal(t, "main", s.Engine.GetBranch("feature-a").GetParent().GetName())
require.Equal(t, "feature-a", s.Engine.GetBranch("feature-b").GetParent().GetName())
})

t.Run("identifies and syncs local descendants", func(t *testing.T) {
s := scenario.NewScenario(t, testhelpers.BasicSceneSetup)
s.WithInitialCommit().
CreateBranch("feature-a").
Commit("a1").
CreateBranch("feature-b").
Commit("b1").
TrackBranch("feature-a", "main").
TrackBranch("feature-b", "feature-a")

// Mock GitHub: just feature-a
ghConfig := testhelpers.NewMockGitHubServerConfig()
ghConfig.PRs["feature-a"] = &github.PullRequest{
Number: new(1),
Head: &github.PullRequestBranch{Ref: new("feature-a")},
Base: &github.PullRequestBranch{Ref: new("main")},
}
ghClient, owner, repo := testhelpers.NewMockGitHubClient(t, ghConfig)
s.Context.GitHubClient = testhelpers.NewMockGitHubClientInterface(ghClient, owner, repo, ghConfig)

s.RunGit("remote", "add", "origin", s.Scene.Dir)

// Run GetAction for feature-a
err := actions.GetAction(s.Context, "feature-a", actions.GetOptions{}, &actions.GetNullHandler{})
err = actions.GetAction(s.Context, "b", actions.GetOptions{Restack: true}, &actions.GetNullHandler{})
require.NoError(t, err)

// feature-b should also have been refreshed/checked out because it's upstack of a
// (though in this unit test it might not do much since remote == local)
})

t.Run("fails if uncommitted changes exist", func(t *testing.T) {
s := scenario.NewScenario(t, testhelpers.BasicSceneSetup)
s.WithInitialCommit().
WithUncommittedChange("dirty.txt")
parent := s.Engine.GetBranch("b").GetParent()
require.NotNil(t, parent)
require.Equal(t, "main", parent.GetName())

err := actions.GetAction(s.Context, "some-branch", actions.GetOptions{}, &actions.GetNullHandler{})
require.Error(t, err)
require.Contains(t, err.Error(), "uncommitted changes")
commitCount, err := s.Scene.Repo.GetCommitCount("main", "b")
require.NoError(t, err)
require.Equal(t, 1, commitCount)
})
}
34 changes: 34 additions & 0 deletions internal/integration/worktree_attach_detach_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,40 @@ func TestWorktreeDetach(t *testing.T) {
sh.ExpectBranchParent("feature", "main")
})

run("detach created worktree preserves commit counts for multiple anchor children", func(t *testing.T, sh *TestShell) {
sh.Run("worktree create my-wt")
sh.Run("worktree open my-wt")
worktreePath := strings.TrimSpace(sh.Output())
shW := sh.InWorktree(worktreePath)

shW.Git("branch --show-current")
anchorBranch := strings.TrimSpace(shW.Output())

shW.WriteFile("feature-a.txt", "feature a").
Run("create feature-a -m 'feature-a branch'")

shW.Git("checkout "+anchorBranch).
WriteFile("feature-b.txt", "feature b").
Run("create feature-b -m 'feature-b branch'")

sh.Run("worktree detach my-wt")

if _, err := os.Stat(worktreePath); !os.IsNotExist(err) {
t.Errorf("Worktree directory should be removed at %s", worktreePath)
}

sh.HasBranches("main", "feature-a", "feature-b")
sh.ExpectBranchParent("feature-a", "main").
ExpectBranchParent("feature-b", "main")

sh.Checkout("feature-a").
Run("restack").
CommitCount("main", "feature-a", 1)
sh.Checkout("feature-b").
Run("restack").
CommitCount("main", "feature-b", 1)
})

run("detach fails with uncommitted changes without force", func(t *testing.T, sh *TestShell) {
// Create a stack and attach
sh.WriteFile("feature.txt", "feature").
Expand Down
Loading