package commands import ( "fmt" "os" "path/filepath" "git.dws.rip/DWS/onyx/internal/core" "git.dws.rip/DWS/onyx/internal/storage" "github.com/spf13/cobra" ) // NewInitCmd creates the init command func NewInitCmd() *cobra.Command { 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`, Args: cobra.MaximumNArgs(1), RunE: runInit, } return cmd } func runInit(cmd *cobra.Command, args []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 .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) } } fmt.Printf("Initialized empty Onyx repository in %s\n", filepath.Join(absPath, ".onx")) 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) }