1+ package cmd
2+
3+ import (
4+ "fmt"
5+ "os"
6+ "os/exec"
7+ "strings"
8+
9+ "github.com/ksred/ccswitch/internal/git"
10+ "github.com/ksred/ccswitch/internal/session"
11+ "github.com/ksred/ccswitch/internal/ui"
12+ "github.com/spf13/cobra"
13+ )
14+
15+ func newPRCmd () * cobra.Command {
16+ return & cobra.Command {
17+ Use : "pr" ,
18+ Short : "Create a pull request for the current session" ,
19+ Run : createPullRequest ,
20+ }
21+ }
22+
23+ func createPullRequest (cmd * cobra.Command , args []string ) {
24+ // Get current directory
25+ currentDir , err := os .Getwd ()
26+ if err != nil {
27+ fmt .Println (ui .ErrorStyle .Render ("✗ Failed to get current directory" ))
28+ return
29+ }
30+
31+ // Check if gh CLI is available
32+ if ! isGitHubCLIAvailable () {
33+ fmt .Println (ui .ErrorStyle .Render ("✗ GitHub CLI (gh) is not installed or not in PATH" ))
34+ fmt .Println (ui .InfoStyle .Render (" Install GitHub CLI: https://cli.github.com/" ))
35+ return
36+ }
37+
38+ // Check if we're in a git repository
39+ if ! git .IsGitRepository (currentDir ) {
40+ fmt .Println (ui .ErrorStyle .Render ("✗ Not in a git repository" ))
41+ return
42+ }
43+
44+ // Get current branch
45+ currentBranch , err := git .GetCurrentBranch (currentDir )
46+ if err != nil {
47+ fmt .Printf (ui .ErrorStyle .Render ("✗ Failed to get current branch: %v\n " ), err )
48+ return
49+ }
50+
51+ // Check if we're on main/master branch
52+ if currentBranch == "main" || currentBranch == "master" {
53+ fmt .Println (ui .ErrorStyle .Render ("✗ Cannot create PR from main/master branch" ))
54+ fmt .Println (ui .InfoStyle .Render (" Switch to a feature branch first using 'ccswitch list'" ))
55+ return
56+ }
57+
58+ // Check if we're in a ccswitch session
59+ manager := session .NewManager (currentDir )
60+ sessions , err := manager .ListSessions ()
61+ if err != nil {
62+ fmt .Printf (ui .ErrorStyle .Render ("✗ Failed to list sessions: %v\n " ), err )
63+ return
64+ }
65+
66+ var currentSession * git.SessionInfo
67+ for _ , s := range sessions {
68+ if s .Path == currentDir {
69+ s := s // Create a copy to take address of
70+ currentSession = & s
71+ break
72+ }
73+ }
74+
75+ if currentSession == nil {
76+ fmt .Println (ui .ErrorStyle .Render ("✗ Not in a ccswitch session directory" ))
77+ fmt .Println (ui .InfoStyle .Render (" Use 'ccswitch list' to enter a session first" ))
78+ return
79+ }
80+
81+ fmt .Printf (ui .TitleStyle .Render ("🚀 Creating pull request for session: %s\n " ), currentSession .Name )
82+ fmt .Printf (ui .InfoStyle .Render (" Branch: %s\n " ), currentBranch )
83+
84+ // Check if branch has commits ahead of main
85+ hasCommits , err := checkBranchHasCommits (currentDir , currentBranch )
86+ if err != nil {
87+ fmt .Printf (ui .ErrorStyle .Render ("✗ Failed to check branch commits: %v\n " ), err )
88+ return
89+ }
90+
91+ if ! hasCommits {
92+ fmt .Println (ui .ErrorStyle .Render ("✗ No commits found on this branch" ))
93+ fmt .Println (ui .InfoStyle .Render (" Make some commits before creating a PR" ))
94+ return
95+ }
96+
97+ // Push the branch if needed
98+ fmt .Println (ui .InfoStyle .Render ("📤 Pushing branch to remote..." ))
99+ if err := pushBranch (currentDir , currentBranch ); err != nil {
100+ fmt .Printf (ui .ErrorStyle .Render ("✗ Failed to push branch: %v\n " ), err )
101+ return
102+ }
103+
104+ // Create PR using gh CLI
105+ fmt .Println (ui .InfoStyle .Render ("📝 Creating pull request..." ))
106+ prURL , err := createPRWithGH (currentDir , currentSession .Name )
107+ if err != nil {
108+ fmt .Printf (ui .ErrorStyle .Render ("✗ Failed to create PR: %v\n " ), err )
109+ return
110+ }
111+
112+ fmt .Printf (ui .SuccessStyle .Render ("✓ Pull request created successfully!\n " ))
113+ fmt .Printf (ui .InfoStyle .Render (" URL: %s\n " ), prURL )
114+
115+ // Open in browser
116+ fmt .Println (ui .InfoStyle .Render ("🌐 Opening PR in browser..." ))
117+ if err := openInBrowser (prURL ); err != nil {
118+ fmt .Printf (ui .ErrorStyle .Render ("✗ Failed to open browser: %v\n " ), err )
119+ fmt .Println (ui .InfoStyle .Render (" You can manually open the URL above" ))
120+ }
121+ }
122+
123+ func isGitHubCLIAvailable () bool {
124+ _ , err := exec .LookPath ("gh" )
125+ return err == nil
126+ }
127+
128+ func checkBranchHasCommits (dir , branch string ) (bool , error ) {
129+ cmd := exec .Command ("git" , "rev-list" , "--count" , "main.." + branch )
130+ cmd .Dir = dir
131+
132+ output , err := cmd .Output ()
133+ if err != nil {
134+ return false , err
135+ }
136+
137+ count := strings .TrimSpace (string (output ))
138+ return count != "0" , nil
139+ }
140+
141+ func pushBranch (dir , branch string ) error {
142+ cmd := exec .Command ("git" , "push" , "-u" , "origin" , branch )
143+ cmd .Dir = dir
144+ cmd .Stdout = os .Stdout
145+ cmd .Stderr = os .Stderr
146+
147+ return cmd .Run ()
148+ }
149+
150+ func createPRWithGH (dir , sessionName string ) (string , error ) {
151+ // Generate PR title from session name
152+ title := strings .ReplaceAll (sessionName , "-" , " " )
153+ title = strings .Title (title )
154+
155+ cmd := exec .Command ("gh" , "pr" , "create" , "--title" , title , "--body" , "Created from ccswitch session: " + sessionName , "--web" )
156+ cmd .Dir = dir
157+
158+ output , err := cmd .Output ()
159+ if err != nil {
160+ return "" , err
161+ }
162+
163+ // Extract URL from output
164+ lines := strings .Split (string (output ), "\n " )
165+ for _ , line := range lines {
166+ line = strings .TrimSpace (line )
167+ if strings .HasPrefix (line , "https://github.com/" ) {
168+ return line , nil
169+ }
170+ }
171+
172+ return string (output ), nil
173+ }
174+
175+ func openInBrowser (url string ) error {
176+ var cmd * exec.Cmd
177+
178+ switch {
179+ case exec .Command ("which" , "open" ).Run () == nil : // macOS
180+ cmd = exec .Command ("open" , url )
181+ case exec .Command ("which" , "xdg-open" ).Run () == nil : // Linux
182+ cmd = exec .Command ("xdg-open" , url )
183+ case exec .Command ("which" , "cmd" ).Run () == nil : // Windows
184+ cmd = exec .Command ("cmd" , "/c" , "start" , url )
185+ default :
186+ return fmt .Errorf ("unable to detect platform to open browser" )
187+ }
188+
189+ return cmd .Run ()
190+ }
0 commit comments