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 # 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) }