Adds initial gitlocal CLI and core functionality
Introduces the `gitlocal` command-line tool for managing nested Git repositories. Includes the following main commands: - `convert`: Renames `.git` to `.gitlocal`, allowing a parent repository to ignore the converted child repository. Supports recursive scanning and dry-run options. Tracks converted repositories in a global configuration. - `revert`: Restores `.gitlocal` to `.git`. Includes an option to revert all tracked repositories. - `status`: Displays a list of all repositories currently tracked by `gitlocal`, showing their path, conversion time, and original remote/branch. Establishes internal modules for Git operations, configuration management, and recursive repository scanning. Adds a comprehensive test suite covering core command logic and utility functions. Initializes Go module and basic project `.gitignore`.
This commit is contained in:
225
internal/scanner/scanner_test.go
Normal file
225
internal/scanner/scanner_test.go
Normal file
@@ -0,0 +1,225 @@
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"git.membo.co.uk/dtomlinson/gitlocal/internal/git"
|
||||
"git.membo.co.uk/dtomlinson/gitlocal/internal/testutil"
|
||||
)
|
||||
|
||||
func TestFindNestedGitRepos(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(t *testing.T) (rootDir string, expectedPaths []string)
|
||||
expectedCount int
|
||||
}{
|
||||
{
|
||||
name: "no nested repos",
|
||||
setup: func(t *testing.T) (string, []string) {
|
||||
dir := testutil.CreateTempGitRepo(t)
|
||||
return dir, []string{}
|
||||
},
|
||||
expectedCount: 0,
|
||||
},
|
||||
{
|
||||
name: "nested repos structure",
|
||||
setup: func(t *testing.T) (string, []string) {
|
||||
rootDir, nestedDirs := testutil.CreateNestedRepoStructure(t)
|
||||
return rootDir, nestedDirs
|
||||
},
|
||||
expectedCount: 3,
|
||||
},
|
||||
{
|
||||
name: "non-git directory",
|
||||
setup: func(t *testing.T) (string, []string) {
|
||||
dir := t.TempDir()
|
||||
// Create subdirectories without .git
|
||||
os.MkdirAll(filepath.Join(dir, "subdir1"), 0755)
|
||||
os.MkdirAll(filepath.Join(dir, "subdir2", "deep"), 0755)
|
||||
return dir, []string{}
|
||||
},
|
||||
expectedCount: 0,
|
||||
},
|
||||
{
|
||||
name: "mixed nested repos and regular dirs",
|
||||
setup: func(t *testing.T) (string, []string) {
|
||||
dir := testutil.CreateTempGitRepo(t)
|
||||
|
||||
// Create one nested git repo
|
||||
nested1 := filepath.Join(dir, "nested1")
|
||||
os.MkdirAll(nested1, 0755)
|
||||
testutil.CreateTempGitRepo(t) // This creates in temp dir, we need to manually init
|
||||
// Let's use a helper to init git in nested1
|
||||
initGitInDir(t, nested1)
|
||||
|
||||
// Create regular directories
|
||||
os.MkdirAll(filepath.Join(dir, "regular"), 0755)
|
||||
os.MkdirAll(filepath.Join(dir, "another", "deep"), 0755)
|
||||
|
||||
return dir, []string{nested1}
|
||||
},
|
||||
expectedCount: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rootDir, expectedPaths := tt.setup(t)
|
||||
|
||||
repos, err := FindNestedGitRepos(rootDir)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(repos) != tt.expectedCount {
|
||||
t.Errorf("expected %d repos, got %d: %v", tt.expectedCount, len(repos), repos)
|
||||
}
|
||||
|
||||
if tt.expectedCount > 0 {
|
||||
// Sort both slices for comparison
|
||||
sort.Strings(repos)
|
||||
sort.Strings(expectedPaths)
|
||||
|
||||
for i, expected := range expectedPaths {
|
||||
if i >= len(repos) {
|
||||
t.Errorf("missing expected repo: %s", expected)
|
||||
continue
|
||||
}
|
||||
if repos[i] != expected {
|
||||
t.Errorf("expected repo %s, got %s", expected, repos[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindNestedGitReposSkipsRootGit(t *testing.T) {
|
||||
rootDir := testutil.CreateTempGitRepo(t)
|
||||
|
||||
// Create nested repos
|
||||
nested1 := filepath.Join(rootDir, "nested1")
|
||||
os.MkdirAll(nested1, 0755)
|
||||
initGitInDir(t, nested1)
|
||||
|
||||
repos, err := FindNestedGitRepos(rootDir)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Should find nested1 but not the root
|
||||
if len(repos) != 1 {
|
||||
t.Fatalf("expected 1 nested repo, got %d", len(repos))
|
||||
}
|
||||
|
||||
if repos[0] != nested1 {
|
||||
t.Errorf("expected %s, got %s", nested1, repos[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindNestedGitReposWithGitLocal(t *testing.T) {
|
||||
rootDir := testutil.CreateTempGitRepo(t)
|
||||
|
||||
// Create nested repo with .gitlocal
|
||||
nested1 := filepath.Join(rootDir, "nested1")
|
||||
os.MkdirAll(nested1, 0755)
|
||||
initGitInDir(t, nested1)
|
||||
// Convert to .gitlocal
|
||||
if err := git.Convert(nested1); err != nil {
|
||||
t.Fatalf("failed to convert: %v", err)
|
||||
}
|
||||
|
||||
// Create nested repo with .git
|
||||
nested2 := filepath.Join(rootDir, "nested2")
|
||||
os.MkdirAll(nested2, 0755)
|
||||
initGitInDir(t, nested2)
|
||||
|
||||
repos, err := FindNestedGitRepos(rootDir)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Should find both
|
||||
if len(repos) != 2 {
|
||||
t.Fatalf("expected 2 nested repos, got %d", len(repos))
|
||||
}
|
||||
|
||||
sort.Strings(repos)
|
||||
expectedPaths := []string{nested1, nested2}
|
||||
sort.Strings(expectedPaths)
|
||||
|
||||
for i, expected := range expectedPaths {
|
||||
if repos[i] != expected {
|
||||
t.Errorf("expected repo %s, got %s", expected, repos[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindNestedGitReposWithRootGitLocal(t *testing.T) {
|
||||
rootDir := testutil.CreateTempGitRepo(t)
|
||||
|
||||
// Convert root to .gitlocal
|
||||
if err := git.Convert(rootDir); err != nil {
|
||||
t.Fatalf("failed to convert root: %v", err)
|
||||
}
|
||||
|
||||
// Create nested repo
|
||||
nested1 := filepath.Join(rootDir, "nested1")
|
||||
os.MkdirAll(nested1, 0755)
|
||||
initGitInDir(t, nested1)
|
||||
|
||||
repos, err := FindNestedGitRepos(rootDir)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Should find nested1 but not the root .gitlocal
|
||||
if len(repos) != 1 {
|
||||
t.Fatalf("expected 1 nested repo, got %d", len(repos))
|
||||
}
|
||||
|
||||
if repos[0] != nested1 {
|
||||
t.Errorf("expected %s, got %s", nested1, repos[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindNestedGitReposDeepNesting(t *testing.T) {
|
||||
rootDir := testutil.CreateTempGitRepo(t)
|
||||
|
||||
// Create deeply nested structure
|
||||
deep := filepath.Join(rootDir, "a", "b", "c", "d", "nested")
|
||||
os.MkdirAll(deep, 0755)
|
||||
initGitInDir(t, deep)
|
||||
|
||||
repos, err := FindNestedGitRepos(rootDir)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(repos) != 1 {
|
||||
t.Fatalf("expected 1 nested repo, got %d", len(repos))
|
||||
}
|
||||
|
||||
if repos[0] != deep {
|
||||
t.Errorf("expected %s, got %s", deep, repos[0])
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to initialize git in a directory
|
||||
func initGitInDir(t *testing.T, dir string) {
|
||||
t.Helper()
|
||||
|
||||
// We can't use testutil.CreateTempGitRepo since it creates a new temp dir
|
||||
// So we manually init git here
|
||||
gitDir := filepath.Join(dir, git.GitDir)
|
||||
if err := os.MkdirAll(gitDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create .git directory: %v", err)
|
||||
}
|
||||
|
||||
// Create minimal git structure
|
||||
// For the scanner, we just need the .git directory to exist
|
||||
// The scanner doesn't validate the git repo structure
|
||||
}
|
||||
Reference in New Issue
Block a user