chore(repo): reinitialize repository
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
package workspaceruntime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"inbox/internal/app/lanegit"
|
||||
)
|
||||
|
||||
type gitWorktree struct {
|
||||
Path string
|
||||
Branch string
|
||||
}
|
||||
|
||||
type gitWorktreeManager struct {
|
||||
projectRoot string
|
||||
runner lanegit.Runner
|
||||
}
|
||||
|
||||
func (g *gitWorktreeManager) ensureRepository(ctx context.Context, projectDir string) (string, error) {
|
||||
isRepo, err := g.isRepository(ctx, projectDir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if isRepo {
|
||||
branch, err := g.currentBranch(ctx, projectDir)
|
||||
if err == nil && strings.TrimSpace(branch) != "" {
|
||||
return strings.TrimSpace(branch), nil
|
||||
}
|
||||
return "main", nil
|
||||
}
|
||||
|
||||
out, err := g.runner.Run(ctx, g.projectRoot, nil, "git", "-C", projectDir, "init", "-b", "main")
|
||||
if err != nil {
|
||||
return "", commandError("git init "+projectDir, out, err)
|
||||
}
|
||||
env := map[string]string{
|
||||
"GIT_AUTHOR_NAME": "Inbox",
|
||||
"GIT_AUTHOR_EMAIL": "inbox@local",
|
||||
"GIT_COMMITTER_NAME": "Inbox",
|
||||
"GIT_COMMITTER_EMAIL": "inbox@local",
|
||||
}
|
||||
out, err = g.runner.Run(ctx, g.projectRoot, env, "git", "-C", projectDir, "commit", "--allow-empty", "-m", "Initialize workspace repository")
|
||||
if err != nil {
|
||||
return "", commandError("create initial empty commit", out, err)
|
||||
}
|
||||
return "main", nil
|
||||
}
|
||||
|
||||
func (g *gitWorktreeManager) ensureWorktree(ctx context.Context, projectDir, worktreePath, baseBranch, worktreeBranch string) error {
|
||||
worktreePath = filepath.Clean(worktreePath)
|
||||
entries, err := g.listWorktrees(ctx, projectDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stale, err := hasMissingWorktreePaths(entries); err != nil {
|
||||
return err
|
||||
} else if stale {
|
||||
if err := g.pruneWorktrees(ctx, projectDir); err != nil {
|
||||
return err
|
||||
}
|
||||
entries, err = g.listWorktrees(ctx, projectDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if filepath.Clean(entry.Path) == worktreePath {
|
||||
expected := "refs/heads/" + worktreeBranch
|
||||
if strings.TrimSpace(entry.Branch) != "" && entry.Branch != expected {
|
||||
return fmt.Errorf("worktree path %s is attached to %s, want %s", worktreePath, entry.Branch, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if entry.Branch == "refs/heads/"+worktreeBranch && filepath.Clean(entry.Path) != worktreePath {
|
||||
return fmt.Errorf("worktree branch %s already attached at %s", worktreeBranch, entry.Path)
|
||||
}
|
||||
}
|
||||
|
||||
if info, err := os.Stat(worktreePath); err == nil && info.IsDir() {
|
||||
return fmt.Errorf("worktree path already exists but is not registered: %s", worktreePath)
|
||||
} else if err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("stat worktree path: %w", err)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(worktreePath), 0755); err != nil {
|
||||
return fmt.Errorf("create worktree parent: %w", err)
|
||||
}
|
||||
|
||||
branchExists, err := g.branchExists(ctx, projectDir, worktreeBranch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args := []string{"-C", projectDir, "worktree", "add"}
|
||||
if !branchExists {
|
||||
args = append(args, "-b", worktreeBranch)
|
||||
}
|
||||
args = append(args, worktreePath)
|
||||
if branchExists {
|
||||
args = append(args, worktreeBranch)
|
||||
} else {
|
||||
args = append(args, baseBranch)
|
||||
}
|
||||
out, err := g.runner.Run(ctx, g.projectRoot, nil, "git", args...)
|
||||
if err != nil {
|
||||
return commandError("create worktree "+worktreePath, out, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *gitWorktreeManager) isRepository(ctx context.Context, projectDir string) (bool, error) {
|
||||
out, err := g.runner.Run(ctx, g.projectRoot, nil, "git", "-C", projectDir, "rev-parse", "--is-inside-work-tree")
|
||||
if err != nil {
|
||||
if strings.Contains(out, "not a git repository") {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("detect git repository: %w", err)
|
||||
}
|
||||
return strings.TrimSpace(out) == "true", nil
|
||||
}
|
||||
|
||||
func (g *gitWorktreeManager) currentBranch(ctx context.Context, projectDir string) (string, error) {
|
||||
out, err := g.runner.Run(ctx, g.projectRoot, nil, "git", "-C", projectDir, "symbolic-ref", "--quiet", "--short", "HEAD")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(out), nil
|
||||
}
|
||||
|
||||
func (g *gitWorktreeManager) listWorktrees(ctx context.Context, projectDir string) ([]gitWorktree, error) {
|
||||
out, err := g.runner.Run(ctx, g.projectRoot, nil, "git", "-C", projectDir, "worktree", "list", "--porcelain")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list git worktrees: %w", err)
|
||||
}
|
||||
lines := strings.Split(out, "\n")
|
||||
items := make([]gitWorktree, 0)
|
||||
var current gitWorktree
|
||||
flush := func() {
|
||||
if strings.TrimSpace(current.Path) == "" {
|
||||
return
|
||||
}
|
||||
items = append(items, current)
|
||||
current = gitWorktree{}
|
||||
}
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case strings.HasPrefix(line, "worktree "):
|
||||
flush()
|
||||
current.Path = strings.TrimSpace(strings.TrimPrefix(line, "worktree "))
|
||||
case strings.HasPrefix(line, "branch "):
|
||||
current.Branch = strings.TrimSpace(strings.TrimPrefix(line, "branch "))
|
||||
}
|
||||
}
|
||||
flush()
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (g *gitWorktreeManager) branchExists(ctx context.Context, projectDir, branch string) (bool, error) {
|
||||
out, err := g.runner.Run(ctx, g.projectRoot, nil, "git", "-C", projectDir, "show-ref", "--verify", "--quiet", "refs/heads/"+branch)
|
||||
if err != nil {
|
||||
if out == "" {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("check git branch %s: %w", branch, err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (g *gitWorktreeManager) pruneWorktrees(ctx context.Context, projectDir string) error {
|
||||
out, err := g.runner.Run(ctx, g.projectRoot, nil, "git", "-C", projectDir, "worktree", "prune", "--expire", "now")
|
||||
if err != nil {
|
||||
return commandError("prune stale worktrees", out, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasMissingWorktreePaths(entries []gitWorktree) (bool, error) {
|
||||
for _, entry := range entries {
|
||||
info, err := os.Stat(filepath.Clean(entry.Path))
|
||||
if err == nil {
|
||||
if !info.IsDir() {
|
||||
return false, fmt.Errorf("worktree path is not a directory: %s", entry.Path)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("stat registered worktree path %s: %w", entry.Path, err)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
Reference in New Issue
Block a user