Files
onyx/internal/commands/init.go

232 lines
6.1 KiB
Go

package commands
import (
"fmt"
"os"
"path/filepath"
"git.dws.rip/DWS/onyx/internal/core"
"git.dws.rip/DWS/onyx/internal/storage"
"github.com/go-git/go-git/v5/config"
"github.com/spf13/cobra"
)
// NewInitCmd creates the init command
func NewInitCmd() *cobra.Command {
var remoteURL string
cmd := &cobra.Command{
Use: "init [path]",
Short: "Initialize a new Onyx repository",
Long: `Initialize a new Onyx repository in the specified directory.
If no path is provided, initializes in the current directory.
This command will:
- Create a Git repository (if one doesn't exist)
- Create the .onx directory structure
- Initialize the oplog file
- Create default workstreams.json
- Add .onx to .gitignore
- Optionally configure a remote repository
Example:
onx init
onx init --remote https://git.dws.rip/DWS/onyx.git`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runInit(cmd, args, remoteURL)
},
}
cmd.Flags().StringVarP(&remoteURL, "remote", "r", "", "Remote repository URL to configure as 'origin'")
return cmd
}
func runInit(cmd *cobra.Command, args []string, remoteURL string) error {
// Determine the path
path := "."
if len(args) > 0 {
path = args[0]
}
// Resolve to absolute path
absPath, err := filepath.Abs(path)
if err != nil {
return fmt.Errorf("failed to resolve path: %w", err)
}
// Check if already an Onyx repository
if core.IsOnyxRepo(absPath) {
return fmt.Errorf("already an onyx repository: %s", absPath)
}
// Create and initialize repository
repo := &core.OnyxRepository{}
err = repo.Init(absPath)
if err != nil {
return fmt.Errorf("failed to initialize repository: %w", err)
}
// Add remote if specified
if remoteURL != "" {
gitRepo := repo.GetGitRepo()
_, err = gitRepo.CreateRemote(&config.RemoteConfig{
Name: "origin",
URLs: []string{remoteURL},
})
if err != nil {
// Don't fail the init, but warn the user
fmt.Fprintf(os.Stderr, "Warning: failed to add remote: %v\n", err)
} else {
fmt.Printf("Added remote 'origin': %s\n", remoteURL)
}
}
// Create default workstream matching the current Git branch
wsManager := core.NewWorkstreamManager(repo)
if err := wsManager.CreateDefaultWorkstream(); err != nil {
// Don't fail init if workstream creation fails, just warn
fmt.Fprintf(os.Stderr, "Warning: failed to create default workstream: %v\n", err)
}
// Add .onx to .gitignore
gitignorePath := filepath.Join(absPath, ".gitignore")
err = addToGitignore(gitignorePath, ".onx/")
if err != nil {
// Don't fail if we can't update .gitignore, just warn
fmt.Fprintf(os.Stderr, "Warning: failed to update .gitignore: %v\n", err)
}
// Log the init operation to oplog
txn, err := core.NewTransaction(repo)
if err != nil {
// Don't fail if we can't create transaction, repo is already initialized
fmt.Fprintf(os.Stderr, "Warning: failed to log init to oplog: %v\n", err)
} else {
defer txn.Close()
// Execute a no-op function just to log the init
err = txn.ExecuteWithTransaction("init", "Initialized Onyx repository", func() error {
return nil
})
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: failed to log init: %v\n", err)
}
}
// Get the workstream name to display to the user
currentWs, _ := wsManager.GetCurrentWorkstreamName()
if currentWs == "" {
currentWs = "master" // fallback
}
fmt.Printf("Initialized empty Onyx repository in %s\n", filepath.Join(absPath, ".onx"))
fmt.Printf("\n✓ Created workstream '%s' tracking branch '%s'\n", currentWs, currentWs)
if remoteURL != "" {
fmt.Printf("\nYou can now:\n")
fmt.Printf(" onx save -m \"message\" # Save your work\n")
fmt.Printf(" onx push # Push to remote\n")
} else {
fmt.Printf("\nYou can now:\n")
fmt.Printf(" onx save -m \"message\" # Save your work\n")
fmt.Printf(" onx new <name> # Create a new workstream\n")
}
return nil
}
// addToGitignore adds an entry to .gitignore if it doesn't already exist
func addToGitignore(gitignorePath, entry string) error {
// Read existing .gitignore if it exists
var content []byte
if _, err := os.Stat(gitignorePath); err == nil {
content, err = os.ReadFile(gitignorePath)
if err != nil {
return fmt.Errorf("failed to read .gitignore: %w", err)
}
}
// Check if entry already exists
contentStr := string(content)
if len(contentStr) > 0 && contentStr[len(contentStr)-1] != '\n' {
contentStr += "\n"
}
// Add entry if it doesn't exist
needle := entry
if len(needle) > 0 && needle[len(needle)-1] != '\n' {
needle += "\n"
}
// Simple check - not perfect but good enough
if !containsLine(contentStr, entry) {
contentStr += needle
}
// Write back to .gitignore
err := os.WriteFile(gitignorePath, []byte(contentStr), 0644)
if err != nil {
return fmt.Errorf("failed to write .gitignore: %w", err)
}
return nil
}
// containsLine checks if a multi-line string contains a specific line
func containsLine(content, line string) bool {
// Simple implementation - just check if the line exists as a substring
// In the future, we might want to do line-by-line checking
target := line
if len(target) > 0 && target[len(target)-1] == '\n' {
target = target[:len(target)-1]
}
lines := splitLines(content)
for _, l := range lines {
if l == target {
return true
}
}
return false
}
// splitLines splits a string into lines
func splitLines(s string) []string {
if s == "" {
return []string{}
}
var lines []string
start := 0
for i := 0; i < len(s); i++ {
if s[i] == '\n' {
lines = append(lines, s[start:i])
start = i + 1
}
}
// Add the last line if it doesn't end with newline
if start < len(s) {
lines = append(lines, s[start:])
}
return lines
}
// GetOplogWriter creates an oplog writer for the repository at the given path
func GetOplogWriter(path string) (*storage.OplogWriter, error) {
absPath, err := filepath.Abs(path)
if err != nil {
return nil, fmt.Errorf("failed to resolve path: %w", err)
}
if !core.IsOnyxRepo(absPath) {
return nil, fmt.Errorf("not an onyx repository: %s", absPath)
}
oplogPath := filepath.Join(absPath, ".onx", "oplog")
return storage.OpenOplog(oplogPath)
}