mirror of
https://github.com/x-motemen/ghq.git
synced 2026-09-10 07:26:27 -04:00
376 lines
9.8 KiB
Go
376 lines
9.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/x-motemen/ghq/cmdutil"
|
|
)
|
|
|
|
func TestRmCommand(t *testing.T) {
|
|
defer func(orig func(cmd *exec.Cmd) error) {
|
|
cmdutil.CommandRunner = orig
|
|
}(cmdutil.CommandRunner)
|
|
commandRunner := func(cmd *exec.Cmd) error {
|
|
return nil
|
|
}
|
|
defer func(orig string) { _home = orig }(_home)
|
|
_home = ""
|
|
homeOnce = &sync.Once{}
|
|
tmpd := newTempDir(t)
|
|
defer func(orig []string) { _localRepositoryRoots = orig }(_localRepositoryRoots)
|
|
setEnv(t, envGhqRoot, tmpd)
|
|
_localRepositoryRoots = nil
|
|
localRepoOnce = &sync.Once{}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
input []string
|
|
setup func(t *testing.T)
|
|
expectErr bool
|
|
cmdRun func(cmd *exec.Cmd) error
|
|
skipOnWin bool
|
|
}{
|
|
{
|
|
name: "simple",
|
|
input: []string{"rm", "motemen/ghqq"},
|
|
setup: func(t *testing.T) {
|
|
if err := os.MkdirAll(filepath.Join(tmpd, "github.com", "motemen", "ghqq", ".git"), 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
expectErr: false,
|
|
},
|
|
{
|
|
name: "empty directory",
|
|
input: []string{"rm", "motemen/ghqqq"},
|
|
setup: func(t *testing.T) {
|
|
if err := os.MkdirAll(filepath.Join(tmpd, "github.com", "motemen", "ghqqq"), 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
expectErr: true,
|
|
},
|
|
{
|
|
name: "missing directory",
|
|
input: []string{"rm", "motemen/missing"},
|
|
setup: func(t *testing.T) {},
|
|
expectErr: true,
|
|
},
|
|
{
|
|
name: "incorrect repository name",
|
|
input: []string{"rm", "example.com/goooo/gooo"},
|
|
setup: func(t *testing.T) {
|
|
os.MkdirAll(filepath.Join(tmpd, "github.com", "motemen", "ghqq"), 0755)
|
|
},
|
|
expectErr: true,
|
|
},
|
|
{
|
|
name: "permission denied",
|
|
input: []string{"rm", "motemen/ghqq"},
|
|
setup: func(t *testing.T) {
|
|
f := filepath.Join(tmpd, "github.com", "motemen", "ghqq")
|
|
os.MkdirAll(f, 0000)
|
|
t.Cleanup(func() {
|
|
os.Chmod(f, 0755)
|
|
})
|
|
},
|
|
expectErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if tc.skipOnWin && runtime.GOOS == "windows" {
|
|
t.SkipNow()
|
|
}
|
|
|
|
if tc.setup != nil {
|
|
tc.setup(t)
|
|
}
|
|
|
|
cmdutil.CommandRunner = commandRunner
|
|
if tc.cmdRun != nil {
|
|
cmdutil.CommandRunner = tc.cmdRun
|
|
}
|
|
|
|
var runErr error
|
|
_, _, err := captureWithInput([]string{"y"}, func() {
|
|
a := newApp()
|
|
args := append([]string{"ghq"}, tc.input...)
|
|
runErr = a.Run(context.Background(), args)
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if gotErr := runErr != nil; gotErr != tc.expectErr {
|
|
t.Fatalf("error = %v, expectErr = %v", runErr, tc.expectErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRmDryRunCommand(t *testing.T) {
|
|
defer func(orig func(cmd *exec.Cmd) error) {
|
|
cmdutil.CommandRunner = orig
|
|
}(cmdutil.CommandRunner)
|
|
commandRunner := func(cmd *exec.Cmd) error {
|
|
return nil
|
|
}
|
|
defer func(orig string) { _home = orig }(_home)
|
|
_home = ""
|
|
homeOnce = &sync.Once{}
|
|
tmpd := newTempDir(t)
|
|
defer func(orig []string) { _localRepositoryRoots = orig }(_localRepositoryRoots)
|
|
setEnv(t, envGhqRoot, tmpd)
|
|
_localRepositoryRoots = nil
|
|
localRepoOnce = &sync.Once{}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
input []string
|
|
setup func(t *testing.T)
|
|
expectErr bool
|
|
cmdRun func(cmd *exec.Cmd) error
|
|
skipOnWin bool
|
|
}{
|
|
{
|
|
name: "simple",
|
|
input: []string{"rm", "--dry-run", "motemen/ghqq"},
|
|
setup: func(t *testing.T) {
|
|
if err := os.MkdirAll(filepath.Join(tmpd, "github.com", "motemen", "ghqq", ".git"), 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
expectErr: false,
|
|
},
|
|
{
|
|
name: "empty directory",
|
|
input: []string{"rm", "--dry-run", "motemen/ghqqq"},
|
|
setup: func(t *testing.T) {},
|
|
expectErr: true,
|
|
},
|
|
{
|
|
name: "incorrect repository name",
|
|
input: []string{"rm", "--dry-run", "example.com/goooo/gooo"},
|
|
setup: func(t *testing.T) {
|
|
os.MkdirAll(filepath.Join(tmpd, "github.com", "motemen", "ghqq"), 0755)
|
|
},
|
|
expectErr: true,
|
|
},
|
|
{
|
|
name: "permission denied",
|
|
input: []string{"rm", "--dry-run", "motemen/ghq-notpermitted"},
|
|
setup: func(t *testing.T) {
|
|
f := filepath.Join(tmpd, "github.com", "motemen", "ghq-notpermitted")
|
|
os.MkdirAll(f, 0000)
|
|
t.Cleanup(func() {
|
|
os.Chmod(f, 0755)
|
|
})
|
|
},
|
|
expectErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if tc.skipOnWin && runtime.GOOS == "windows" {
|
|
t.SkipNow()
|
|
}
|
|
|
|
if tc.setup != nil {
|
|
tc.setup(t)
|
|
}
|
|
|
|
cmdutil.CommandRunner = commandRunner
|
|
if tc.cmdRun != nil {
|
|
cmdutil.CommandRunner = tc.cmdRun
|
|
}
|
|
|
|
var runErr error
|
|
_, _, err := capture(func() {
|
|
a := newApp()
|
|
args := append([]string{"ghq"}, tc.input...)
|
|
runErr = a.Run(context.Background(), args)
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if gotErr := runErr != nil; gotErr != tc.expectErr {
|
|
t.Fatalf("error = %v, expectErr = %v", runErr, tc.expectErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRmWorktree(t *testing.T) {
|
|
defer func(orig func(cmd *exec.Cmd) error) {
|
|
cmdutil.CommandRunner = orig
|
|
}(cmdutil.CommandRunner)
|
|
cmdutil.CommandRunner = func(cmd *exec.Cmd) error { return nil }
|
|
|
|
defer func(orig string) { _home = orig }(_home)
|
|
_home = ""
|
|
homeOnce = &sync.Once{}
|
|
tmpd := newTempDir(t)
|
|
defer func(orig []string) { _localRepositoryRoots = orig }(_localRepositoryRoots)
|
|
setEnv(t, envGhqRoot, tmpd)
|
|
_localRepositoryRoots = nil
|
|
localRepoOnce = &sync.Once{}
|
|
|
|
t.Run("rm_linked_worktree", func(t *testing.T) {
|
|
// Create main repo inside ghq root
|
|
mainDir := initGitRepo(t, filepath.Join(tmpd, "github.com", "wt-rm", "main"),
|
|
"https://github.com/wt-rm/main.git")
|
|
|
|
// Create worktree registered under ghq root so ghq rm can resolve it
|
|
wtDir := filepath.Join(tmpd, "github.com", "wt-rm", "wt-linked")
|
|
addWorktree(t, mainDir, wtDir, "wt-rm-branch")
|
|
|
|
_, _, err := captureWithInput([]string{"y"}, func() {
|
|
a := newApp()
|
|
if e := a.Run(context.Background(), []string{"ghq", "rm", "wt-rm/wt-linked"}); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Worktree directory should be gone
|
|
if _, err := os.Stat(wtDir); !os.IsNotExist(err) {
|
|
t.Error("worktree directory should be removed")
|
|
}
|
|
|
|
// Parent repo's .git/worktrees/<name> should be cleaned up
|
|
wtEntry := filepath.Join(mainDir, ".git", "worktrees", "wt-linked")
|
|
if _, err := os.Stat(wtEntry); !os.IsNotExist(err) {
|
|
t.Error("parent repo's worktree entry should be cleaned up")
|
|
}
|
|
|
|
// Parent repo should still work
|
|
c := exec.Command("git", "status")
|
|
c.Dir = mainDir
|
|
if out, err := c.CombinedOutput(); err != nil {
|
|
t.Errorf("git status in parent repo failed: %v\n%s", err, out)
|
|
}
|
|
})
|
|
|
|
t.Run("rm_dryrun_worktree", func(t *testing.T) {
|
|
mainDir := initGitRepo(t, filepath.Join(tmpd, "github.com", "wt-dry", "main"),
|
|
"https://github.com/wt-dry/main.git")
|
|
wtDir := filepath.Join(tmpd, "github.com", "wt-dry", "wt-linked")
|
|
addWorktree(t, mainDir, wtDir, "wt-dry-branch")
|
|
|
|
out, _, err := capture(func() {
|
|
a := newApp()
|
|
if e := a.Run(context.Background(), []string{"ghq", "rm", "--dry-run", "wt-dry/wt-linked"}); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(out, "Would remove worktree") {
|
|
t.Errorf("expected 'Would remove worktree' in output, got: %s", out)
|
|
}
|
|
if _, err := os.Stat(wtDir); os.IsNotExist(err) {
|
|
t.Error("worktree should still exist after dry-run")
|
|
}
|
|
})
|
|
|
|
t.Run("rm_repo_with_linked_worktrees", func(t *testing.T) {
|
|
mainDir := initGitRepo(t, filepath.Join(tmpd, "github.com", "wt-parent", "repo"),
|
|
"https://github.com/wt-parent/repo.git")
|
|
|
|
// Create two external worktrees
|
|
wt1 := filepath.Join(tmpd, "external-wt1")
|
|
wt2 := filepath.Join(tmpd, "external-wt2")
|
|
addWorktree(t, mainDir, wt1, "branch1")
|
|
addWorktree(t, mainDir, wt2, "branch2")
|
|
|
|
_, _, err := captureWithInput([]string{"y"}, func() {
|
|
a := newApp()
|
|
if e := a.Run(context.Background(), []string{"ghq", "rm", "wt-parent/repo"}); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Main repo should be gone
|
|
if _, err := os.Stat(mainDir); !os.IsNotExist(err) {
|
|
t.Error("main repo should be removed")
|
|
}
|
|
|
|
// Both worktree directories should be gone
|
|
if _, err := os.Stat(wt1); !os.IsNotExist(err) {
|
|
t.Error("worktree 1 should be removed")
|
|
}
|
|
if _, err := os.Stat(wt2); !os.IsNotExist(err) {
|
|
t.Error("worktree 2 should be removed")
|
|
}
|
|
})
|
|
|
|
t.Run("rm_dryrun_with_linked_worktrees", func(t *testing.T) {
|
|
mainDir := initGitRepo(t, filepath.Join(tmpd, "github.com", "wt-dry2", "repo"),
|
|
"https://github.com/wt-dry2/repo.git")
|
|
|
|
wt1 := filepath.Join(tmpd, "dry-wt1")
|
|
addWorktree(t, mainDir, wt1, "dry-branch1")
|
|
|
|
out, _, err := capture(func() {
|
|
a := newApp()
|
|
if e := a.Run(context.Background(), []string{"ghq", "rm", "--dry-run", "wt-dry2/repo"}); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(out, "linked worktree") {
|
|
t.Errorf("expected 'linked worktree' in output, got: %s", out)
|
|
}
|
|
if _, err := os.Stat(mainDir); os.IsNotExist(err) {
|
|
t.Error("repo should still exist after dry-run")
|
|
}
|
|
})
|
|
|
|
t.Run("rm_repo_with_already_deleted_worktree", func(t *testing.T) {
|
|
mainDir := initGitRepo(t, filepath.Join(tmpd, "github.com", "wt-gone", "repo"),
|
|
"https://github.com/wt-gone/repo.git")
|
|
|
|
wt := filepath.Join(tmpd, "gone-wt")
|
|
addWorktree(t, mainDir, wt, "gone-branch")
|
|
|
|
// Manually delete the worktree directory (simulating user deleting it)
|
|
os.RemoveAll(wt)
|
|
|
|
_, _, err := captureWithInput([]string{"y"}, func() {
|
|
a := newApp()
|
|
if e := a.Run(context.Background(), []string{"ghq", "rm", "wt-gone/repo"}); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Main repo should be gone regardless
|
|
if _, err := os.Stat(mainDir); !os.IsNotExist(err) {
|
|
t.Error("main repo should be removed even with pre-deleted worktree")
|
|
}
|
|
})
|
|
}
|