Skip to content
Merged
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
2 changes: 1 addition & 1 deletion cmd/addrepo.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ var addRepoCmd = &cobra.Command{
return err
}

err = workspace.AddRepo(ws, repoURL, "", config)
err = ws.AddRepo(repoURL, "", config)
if err != nil {
return err
}
Expand Down
18 changes: 9 additions & 9 deletions internal/workspace/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func TestAddRepoIntegration(t *testing.T) {
ws, config := createTestWorkspace(t, "test-ws")

remoteRepo := createTestRepo(t)
err := AddRepo(ws, remoteRepo, "", config)
err := ws.AddRepo(remoteRepo, "", config)
if err != nil {
t.Fatalf("AddRepo: %v", err)
}
Expand Down Expand Up @@ -89,7 +89,7 @@ func TestAddRepoIntegration(t *testing.T) {
t.Fatalf("cloneBareRepo: %v", err)
}

err = AddRepo(ws, remoteRepo, "", config)
err = ws.AddRepo(remoteRepo, "", config)
if err != nil {
t.Fatalf("AddRepo: %v", err)
}
Expand All @@ -105,12 +105,12 @@ func TestAddRepoIntegration(t *testing.T) {
remoteRepo := createTestRepo(t)

// create a branch matching our ws branch
err := exec.Command("git", "-C", remoteRepo, "branch", DefaultBranchName(ws, config)).Run()
err := exec.Command("git", "-C", remoteRepo, "branch", defaultBranchName(ws, config)).Run()
if err != nil {
t.Fatalf("git remote branch creation failed: %v", err)
}

err = AddRepo(ws, remoteRepo, "", config)
err = ws.AddRepo(remoteRepo, "", config)
if err != nil {
t.Fatalf("AddRepo: %v", err)
}
Expand All @@ -131,12 +131,12 @@ func TestAddRepoIntegration(t *testing.T) {
localBare, err := cloneBareRepo(remoteRepo, config)

// create a branch matching our ws branch
err = exec.Command("git", "-C", localBare, "branch", DefaultBranchName(ws, config)).Run()
err = exec.Command("git", "-C", localBare, "branch", defaultBranchName(ws, config)).Run()
if err != nil {
t.Fatalf("git local branch creation failed: %v", err)
}

err = AddRepo(ws, remoteRepo, "", config)
err = ws.AddRepo(remoteRepo, "", config)
if err != nil {
t.Fatalf("AddRepo: %v", err)
}
Expand All @@ -151,7 +151,7 @@ func TestAddRepoIntegration(t *testing.T) {

remoteRepo := t.TempDir()

err := AddRepo(ws, remoteRepo, "", config)
err := ws.AddRepo(remoteRepo, "", config)
if err == nil {
t.Fatalf("AddRepo: expected error, got <nil>")
}
Expand All @@ -168,12 +168,12 @@ func TestAddRepoIntegration(t *testing.T) {

// create a worktree with branch matching our ws branch
worktreeDest := t.TempDir()
err = exec.Command("git", "-C", localBare, "worktree", "add", worktreeDest, "-B", DefaultBranchName(ws, config)).Run()
err = exec.Command("git", "-C", localBare, "worktree", "add", worktreeDest, "-B", defaultBranchName(ws, config)).Run()
if err != nil {
t.Fatalf("git worktree creation failed: %v", err)
}

err = AddRepo(ws, remoteRepo, "", config)
err = ws.AddRepo(remoteRepo, "", config)
if err == nil {
t.Fatalf("AddRepo: expected error, got <nil>")
}
Expand Down
1 change: 1 addition & 0 deletions internal/workspace/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func LoadOrDefaultConfig(path string) (Config, error) {

type Workspace struct {
Path string
Metadata WorkspaceMetadata
}

func (ws Workspace) Name() string {
Expand Down
87 changes: 75 additions & 12 deletions internal/workspace/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"os"
"path/filepath"

"github.com/pelletier/go-toml/v2"
)

//go:embed templates/CLAUDE.md
Expand All @@ -13,6 +15,18 @@ var claudeMdTemplate string
//go:embed templates/WORKSPACE.md
var workspaceMdTemplate string

type RepoConfig struct {
URL string `toml:"url"`
}

type WorkspaceMetadata struct {

Config struct {
WorktreeSubdir string `toml:"worktree_subdir"`
} `toml:"config"`
Repos map[string]RepoConfig `toml:"repos"`
}

func FindWorkspaceDir(startDir string) (string, error) {
dir := startDir

Expand All @@ -31,6 +45,42 @@ func FindWorkspaceDir(startDir string) (string, error) {
}
}

func (w *Workspace) writeMetadata() error {
bytes, err := toml.Marshal(w.Metadata)
if err != nil {
return err
}

err = os.WriteFile(filepath.Join(w.Path, "workspace.toml"), bytes, 0666)
if err != nil {
return err
}
return nil
}

func (w *Workspace) loadMetadata() error {

bytes, err := os.ReadFile(filepath.Join(w.Path, "workspace.toml"))
if err != nil {
return err
}

err = toml.Unmarshal(bytes, &w.Metadata)
if err != nil {
return err
}
return nil
}

func NewWorkspace(path string) Workspace {
return Workspace{
Path: path,
Metadata: WorkspaceMetadata{
Repos: make(map[string]RepoConfig),
},
}
}

func CreateWorkspace(name string, config Config) (Workspace, error) {

var err error
Expand All @@ -56,23 +106,25 @@ func CreateWorkspace(name string, config Config) (Workspace, error) {
return Workspace{}, err
}

err = os.WriteFile(filepath.Join(wsPath, "workspace.toml"), []byte{}, 0666)
err = os.WriteFile(filepath.Join(wsPath, "CLAUDE.md"), []byte(claudeMdTemplate), 0666)
if err != nil {
return Workspace{}, err
}

err = os.WriteFile(filepath.Join(wsPath, "CLAUDE.md"), []byte(claudeMdTemplate), 0666)
err = os.WriteFile(filepath.Join(wsPath, "WORKSPACE.md"), []byte(workspaceMdTemplate), 0666)
if err != nil {
return Workspace{}, err
}

err = os.WriteFile(filepath.Join(wsPath, "WORKSPACE.md"), []byte(workspaceMdTemplate), 0666)
ws := NewWorkspace(wsPath)
ws.Metadata.Config.WorktreeSubdir = config.WorktreeSubdir
err = ws.writeMetadata()
if err != nil {
return Workspace{}, err
}

success = true
return Workspace{Path: wsPath}, nil
return ws, nil
}

func LoadWorkspace(wsPath string) (Workspace, error) {
Expand All @@ -82,13 +134,13 @@ func LoadWorkspace(wsPath string) (Workspace, error) {
return Workspace{}, fmt.Errorf("workspace path %q not found: %w", wsPath, err)
}

wsTomlPath := filepath.Join(wsPath, "workspace.toml")
_, err = os.Stat(wsTomlPath)
ws := NewWorkspace(wsPath)
err = ws.loadMetadata()
if err != nil {
return Workspace{}, fmt.Errorf("%q is not a workspace (missing workspace.toml): %w", wsPath, err)
return Workspace{}, err
}

return Workspace{Path: wsPath}, nil
return ws, nil
}

func ListWorkspaces(config Config) ([]Workspace, error) {
Expand Down Expand Up @@ -117,11 +169,11 @@ func ListWorkspaces(config Config) ([]Workspace, error) {
return workspaces, nil
}

func DefaultBranchName(workspace Workspace, config Config) string {
func defaultBranchName(workspace Workspace, config Config) string {
return config.BranchPrefix + workspace.Name()
}

func AddRepo(ws Workspace, repoURL string, branch string, config Config) error {
func (ws *Workspace) AddRepo(repoURL string, branch string, config Config) error {

repo, err := parseRepoURL(repoURL)
if err != nil {
Expand All @@ -134,18 +186,29 @@ func AddRepo(ws Workspace, repoURL string, branch string, config Config) error {
}

if branch == "" {
branch = DefaultBranchName(ws, config)
branch = defaultBranchName(*ws, config)
}

// TODO: handle conflicting worktree paths
workTreeDest := filepath.Join(ws.Path, config.WorktreeSubdir, repo.Name)
success := false
defer func() {
if !success {
_ = os.RemoveAll(workTreeDest)
}
}()

err = addWorktree(bareRepoPath, workTreeDest, branch)
if err != nil {
return err
}

// TODO: add to workspace.toml
ws.Metadata.Repos[repo.Name] = RepoConfig{URL: repo.Url}
err = ws.writeMetadata()
if err != nil {
return err
}

success = true
return nil
}
11 changes: 9 additions & 2 deletions internal/workspace/workspace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func TestCreateWorkspace(t *testing.T) {
t.Errorf("expected os.ErrExists, got: %v:", err)
}

if ws != (Workspace{}) {
if ws.Path != "" {
t.Errorf("expected empty Workspace struct got: %v:", ws)
}
})
Expand Down Expand Up @@ -161,12 +161,16 @@ func TestLoadWorkspace(t *testing.T) {

tmpDir := t.TempDir()
parentDir := filepath.Join(tmpDir, "Workspaces")
config := Config{WorkspacesDir: parentDir}
config := DefaultConfig()
config.WorkspacesDir = parentDir

ws, err := CreateWorkspace("ws1", config)
if err != nil {
t.Fatalf("CreateWorkspace expected no error, got %v", err)
}
if ws.Metadata.Config.WorktreeSubdir != config.WorktreeSubdir {
t.Errorf("WorktreeSubdir expected %v, got %v", config.WorktreeSubdir, ws.Metadata.Config.WorktreeSubdir)
}

ws2, err := LoadWorkspace(ws.Path)
if err != nil {
Expand All @@ -175,6 +179,9 @@ func TestLoadWorkspace(t *testing.T) {
if ws.Path != ws2.Path {
t.Errorf("expected workspace paths to be equal, ws.path: %v ws2.path: %v", ws.Path, ws2.Path)
}
if ws.Metadata.Config.WorktreeSubdir != ws2.Metadata.Config.WorktreeSubdir {
t.Errorf("expected worktreeSubdir config to be equal, ws..WorktreeSubdir: %v ws2..WorktreeSubdir: %v", ws.Metadata.Config.WorktreeSubdir, ws2.Metadata.Config.WorktreeSubdir)
}
})
}

Expand Down
Loading