@@ -41,14 +41,129 @@ var removeCmd = &cobra.Command{
4141 return fmt .Errorf ("cannot remove worktree while inside it. Please navigate out first" )
4242 }
4343
44+ // Load session early to get baseBranch for unpushed commit check
45+ session , _ := loadSession (wt .Path )
46+ baseBranch := ""
47+ if session != nil {
48+ baseBranch = session .BaseBranch
49+ }
50+
4451 // Check for uncommitted changes
4552 hasChanges , err := hasUncommittedChanges (wt .Path )
4653 if err != nil {
4754 return fmt .Errorf ("failed to check for uncommitted changes: %w" , err )
4855 }
4956
50- if hasChanges && ! forceRemove {
51- return fmt .Errorf ("worktree has uncommitted changes. Commit or stash them first, or use --force" )
57+ // Check for unpushed commits (works for both pushed and unpushed branches)
58+ unpushedCount , _ := getUnpushedCommitCount (wt .Path , branch , baseBranch )
59+
60+ changesStashed := false
61+ if (hasChanges || unpushedCount > 0 ) && ! forceRemove {
62+ // Show the changes if any
63+ if hasChanges {
64+ status , err := getGitStatus (wt .Path )
65+ if err != nil {
66+ color .Yellow ("⚠ Failed to get status: %v" , err )
67+ } else {
68+ color .Yellow ("Uncommitted changes in worktree:" )
69+ fmt .Println (status )
70+ }
71+ }
72+
73+ // Show unpushed commits if any
74+ if unpushedCount > 0 {
75+ color .Yellow ("Branch has %d unpushed commit(s)." , unpushedCount )
76+ fmt .Println ()
77+ }
78+
79+ // Build prompt message
80+ var promptMsg string
81+ if hasChanges && unpushedCount > 0 {
82+ promptMsg = "Worktree has uncommitted changes and unpushed commits."
83+ } else if hasChanges {
84+ promptMsg = "Worktree has uncommitted changes."
85+ } else {
86+ promptMsg = "Worktree has unpushed commits."
87+ }
88+
89+ // Build options based on what issues exist
90+ var options []Option
91+ if hasChanges && unpushedCount > 0 {
92+ // Both issues: offer combined options
93+ options = []Option {
94+ {Option : "f" , Description : "Force remove (discard all)" },
95+ {Option : "b" , Description : "Stash and push before removing" },
96+ {Option : "a" , Description : "Stash all (reset branch, stash everything)" },
97+ {Option : "n" , Description : "Cancel" , Default : true },
98+ }
99+ } else if hasChanges {
100+ options = []Option {
101+ {Option : "f" , Description : "Force remove (discard changes)" },
102+ {Option : "s" , Description : "Stash changes before removing" },
103+ {Option : "n" , Description : "Cancel" , Default : true },
104+ }
105+ } else {
106+ // Only unpushed commits
107+ options = []Option {
108+ {Option : "f" , Description : "Force remove" },
109+ {Option : "p" , Description : "Push to remote first" },
110+ {Option : "a" , Description : "Stash all (reset branch, stash commits)" },
111+ {Option : "n" , Description : "Cancel" , Default : true },
112+ }
113+ }
114+
115+ choice , _ , err := promptOption (promptMsg , options )
116+ if err != nil {
117+ return fmt .Errorf ("failed to get user input: %w" , err )
118+ }
119+
120+ switch choice {
121+ case "n" :
122+ fmt .Println ("Cancelled." )
123+ return nil
124+ case "s" :
125+ // Stash only (no unpushed commits)
126+ color .Blue ("Stashing changes..." )
127+ if err := stashChanges (wt .Path ); err != nil {
128+ return fmt .Errorf ("failed to stash changes: %w" , err )
129+ }
130+ color .Green ("✓ Changes stashed" )
131+ changesStashed = true
132+ case "p" :
133+ // Push only (no uncommitted changes)
134+ if err := doPush (wt .Path , branch ); err != nil {
135+ return err
136+ }
137+ case "b" :
138+ // Stash and push (both uncommitted changes and unpushed commits)
139+ color .Blue ("Stashing changes..." )
140+ if err := stashChanges (wt .Path ); err != nil {
141+ return fmt .Errorf ("failed to stash changes: %w" , err )
142+ }
143+ color .Green ("✓ Changes stashed" )
144+ changesStashed = true
145+
146+ if err := doPush (wt .Path , branch ); err != nil {
147+ return err
148+ }
149+ case "a" :
150+ // Stash all: mixed reset to base, then stage and stash everything
151+ color .Blue ("Resetting branch to base..." )
152+ if err := mixedResetToBase (wt .Path , branch , baseBranch ); err != nil {
153+ return fmt .Errorf ("failed to reset branch: %w" , err )
154+ }
155+ color .Green ("✓ Branch reset" )
156+
157+ color .Blue ("Staging and stashing all changes..." )
158+ if err := stashAll (wt .Path ); err != nil {
159+ return fmt .Errorf ("failed to stash changes: %w" , err )
160+ }
161+ color .Green ("✓ All changes stashed" )
162+ changesStashed = true
163+ forceRemove = true // Need force delete since branch appears unmerged after reset
164+ case "f" :
165+ forceRemove = true
166+ }
52167 }
53168
54169 // Load config for remove actions
@@ -57,12 +172,6 @@ var removeCmd = &cobra.Command{
57172 color .Yellow ("⚠ Failed to load config: %v" , err )
58173 } else if len (config .Remove ) > 0 {
59174 fmt .Println ("Running remove actions..." )
60- // Load session for environment variables
61- session , _ := loadSession (wt .Path )
62- baseBranch := ""
63- if session != nil {
64- baseBranch = session .BaseBranch
65- }
66175 if err := runActions (config .Remove , wt .Path , branch , baseBranch ); err != nil {
67176 color .Yellow ("⚠ Remove actions completed with errors" )
68177 }
@@ -81,8 +190,8 @@ var removeCmd = &cobra.Command{
81190
82191 color .Green ("✓ Worktree removed: %s" , branch )
83192
84- // Delete branch if worktree is clean or force is used
85- if ! hasChanges || forceRemove {
193+ // Delete branch if worktree is clean, force is used, or changes were stashed
194+ if ! hasChanges || forceRemove || changesStashed {
86195 color .Blue ("Deleting branch '%s'..." , branch )
87196 if err := deleteBranch (branch , forceRemove ); err != nil {
88197 color .Yellow ("⚠ Failed to delete branch: %v" , err )
@@ -95,6 +204,29 @@ var removeCmd = &cobra.Command{
95204 },
96205}
97206
207+ // doPush handles pushing to remote, prompting for branch name if needed
208+ func doPush (path , branch string ) error {
209+ if remoteBranchExists (branch ) {
210+ color .Blue ("Pushing to origin/%s..." , branch )
211+ if err := pushToRemote (path , branch ); err != nil {
212+ return fmt .Errorf ("failed to push: %w" , err )
213+ }
214+ color .Green ("✓ Pushed to remote" )
215+ } else {
216+ // Prompt for remote branch name with current branch as default
217+ remoteBranch , err := promptTextWithDefault ("Remote branch name" , branch )
218+ if err != nil {
219+ return fmt .Errorf ("failed to get branch name: %w" , err )
220+ }
221+ color .Blue ("Pushing to origin/%s..." , remoteBranch )
222+ if err := pushToNewRemote (path , branch , remoteBranch ); err != nil {
223+ return fmt .Errorf ("failed to push: %w" , err )
224+ }
225+ color .Green ("✓ Pushed to new remote branch: %s" , remoteBranch )
226+ }
227+ return nil
228+ }
229+
98230func init () {
99231 rootCmd .AddCommand (removeCmd )
100232 removeCmd .Flags ().BoolVarP (& forceRemove , "force" , "f" , false , "Force removal even with uncommitted changes" )
0 commit comments