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:
2026-04-11 14:48:01 +01:00
parent c9867f410f
commit b5f1495680
16 changed files with 2263 additions and 0 deletions

View File

@@ -0,0 +1,158 @@
package testutil
import (
"os"
"os/exec"
"path/filepath"
"testing"
)
// CreateTempGitRepo creates a temporary directory with a git repository
func CreateTempGitRepo(t *testing.T) string {
t.Helper()
dir := t.TempDir()
// Initialize git repo
cmd := exec.Command("git", "init")
cmd.Dir = dir
if err := cmd.Run(); err != nil {
t.Fatalf("failed to init git repo: %v", err)
}
// Configure git user for the test repo
configName := exec.Command("git", "config", "user.name", "Test User")
configName.Dir = dir
if err := configName.Run(); err != nil {
t.Fatalf("failed to configure git user.name: %v", err)
}
configEmail := exec.Command("git", "config", "user.email", "test@example.com")
configEmail.Dir = dir
if err := configEmail.Run(); err != nil {
t.Fatalf("failed to configure git user.email: %v", err)
}
// Create initial commit
readmePath := filepath.Join(dir, "README.md")
if err := os.WriteFile(readmePath, []byte("# Test Repo\n"), 0644); err != nil {
t.Fatalf("failed to create README: %v", err)
}
add := exec.Command("git", "add", "README.md")
add.Dir = dir
if err := add.Run(); err != nil {
t.Fatalf("failed to git add: %v", err)
}
commit := exec.Command("git", "commit", "-m", "Initial commit")
commit.Dir = dir
if err := commit.Run(); err != nil {
t.Fatalf("failed to git commit: %v", err)
}
return dir
}
// CreateTempGitRepoWithRemote creates a temporary git repo with a remote URL configured
func CreateTempGitRepoWithRemote(t *testing.T, remoteURL string) string {
t.Helper()
dir := CreateTempGitRepo(t)
// Add remote
cmd := exec.Command("git", "remote", "add", "origin", remoteURL)
cmd.Dir = dir
if err := cmd.Run(); err != nil {
t.Fatalf("failed to add git remote: %v", err)
}
return dir
}
// CreateTempGitRepoWithBranch creates a temporary git repo on a specific branch
func CreateTempGitRepoWithBranch(t *testing.T, branch string) string {
t.Helper()
dir := CreateTempGitRepo(t)
// Create and checkout branch
cmd := exec.Command("git", "checkout", "-b", branch)
cmd.Dir = dir
if err := cmd.Run(); err != nil {
t.Fatalf("failed to create branch: %v", err)
}
return dir
}
// CreateNestedRepoStructure creates a directory structure with nested git repos
// Returns the root directory path
func CreateNestedRepoStructure(t *testing.T) (rootDir string, nestedDirs []string) {
t.Helper()
rootDir = CreateTempGitRepo(t)
// Create nested repos
nested1 := filepath.Join(rootDir, "project1")
if err := os.MkdirAll(nested1, 0755); err != nil {
t.Fatalf("failed to create nested dir: %v", err)
}
initGit(t, nested1)
nested2 := filepath.Join(rootDir, "subdir", "project2")
if err := os.MkdirAll(nested2, 0755); err != nil {
t.Fatalf("failed to create nested dir: %v", err)
}
initGit(t, nested2)
nested3 := filepath.Join(rootDir, "subdir", "deep", "project3")
if err := os.MkdirAll(nested3, 0755); err != nil {
t.Fatalf("failed to create nested dir: %v", err)
}
initGit(t, nested3)
return rootDir, []string{nested1, nested2, nested3}
}
// initGit initializes a git repo in the given directory
func initGit(t *testing.T, dir string) {
t.Helper()
cmd := exec.Command("git", "init")
cmd.Dir = dir
if err := cmd.Run(); err != nil {
t.Fatalf("failed to init git repo: %v", err)
}
// Configure git user
configName := exec.Command("git", "config", "user.name", "Test User")
configName.Dir = dir
if err := configName.Run(); err != nil {
t.Fatalf("failed to configure git user.name: %v", err)
}
configEmail := exec.Command("git", "config", "user.email", "test@example.com")
configEmail.Dir = dir
if err := configEmail.Run(); err != nil {
t.Fatalf("failed to configure git user.email: %v", err)
}
// Create initial commit
readmePath := filepath.Join(dir, "README.md")
if err := os.WriteFile(readmePath, []byte("# Test\n"), 0644); err != nil {
t.Fatalf("failed to create README: %v", err)
}
add := exec.Command("git", "add", "README.md")
add.Dir = dir
if err := add.Run(); err != nil {
t.Fatalf("failed to git add: %v", err)
}
commit := exec.Command("git", "commit", "-m", "Initial commit")
commit.Dir = dir
if err := commit.Run(); err != nil {
t.Fatalf("failed to git commit: %v", err)
}
}