github.com/andrewrech/lazygit@v0.8.1/pkg/gui/branches_panel.go (about) 1 package gui 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/jesseduffield/gocui" 8 "github.com/jesseduffield/lazygit/pkg/commands" 9 "github.com/jesseduffield/lazygit/pkg/git" 10 ) 11 12 // list panel functions 13 14 func (gui *Gui) getSelectedBranch() *commands.Branch { 15 selectedLine := gui.State.Panels.Branches.SelectedLine 16 if selectedLine == -1 { 17 return nil 18 } 19 20 return gui.State.Branches[selectedLine] 21 } 22 23 // may want to standardise how these select methods work 24 func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error { 25 if gui.popupPanelFocused() { 26 return nil 27 } 28 29 if _, err := gui.g.SetCurrentView(v.Name()); err != nil { 30 return err 31 } 32 // This really shouldn't happen: there should always be a master branch 33 if len(gui.State.Branches) == 0 { 34 return gui.renderString(g, "main", gui.Tr.SLocalize("NoBranchesThisRepo")) 35 } 36 branch := gui.getSelectedBranch() 37 if err := gui.focusPoint(0, gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches), v); err != nil { 38 return err 39 } 40 go func() { 41 _ = gui.RenderSelectedBranchUpstreamDifferences() 42 }() 43 go func() { 44 graph, err := gui.GitCommand.GetBranchGraph(branch.Name) 45 if err != nil && strings.HasPrefix(graph, "fatal: ambiguous argument") { 46 graph = gui.Tr.SLocalize("NoTrackingThisBranch") 47 } 48 _ = gui.renderString(g, "main", graph) 49 }() 50 return nil 51 } 52 53 func (gui *Gui) RenderSelectedBranchUpstreamDifferences() error { 54 // here we tell the selected branch that it is selected. 55 // this is necessary for showing stats on a branch that is selected, because 56 // the displaystring function doesn't have access to gui state to tell if it's selected 57 for i, branch := range gui.State.Branches { 58 branch.Selected = i == gui.State.Panels.Branches.SelectedLine 59 } 60 61 branch := gui.getSelectedBranch() 62 branch.Pushables, branch.Pullables = gui.GitCommand.GetBranchUpstreamDifferenceCount(branch.Name) 63 return gui.renderListPanel(gui.getBranchesView(), gui.State.Branches) 64 } 65 66 // gui.refreshStatus is called at the end of this because that's when we can 67 // be sure there is a state.Branches array to pick the current branch from 68 func (gui *Gui) refreshBranches(g *gocui.Gui) error { 69 g.Update(func(g *gocui.Gui) error { 70 builder, err := git.NewBranchListBuilder(gui.Log, gui.GitCommand) 71 if err != nil { 72 return err 73 } 74 gui.State.Branches = builder.Build() 75 76 gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches)) 77 if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil { 78 return err 79 } 80 81 return gui.refreshStatus(g) 82 }) 83 return nil 84 } 85 86 func (gui *Gui) handleBranchesNextLine(g *gocui.Gui, v *gocui.View) error { 87 if gui.popupPanelFocused() { 88 return nil 89 } 90 91 panelState := gui.State.Panels.Branches 92 gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Branches), false) 93 94 if err := gui.resetOrigin(gui.getMainView()); err != nil { 95 return err 96 } 97 return gui.handleBranchSelect(gui.g, v) 98 } 99 100 func (gui *Gui) handleBranchesPrevLine(g *gocui.Gui, v *gocui.View) error { 101 if gui.popupPanelFocused() { 102 return nil 103 } 104 105 panelState := gui.State.Panels.Branches 106 gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Branches), true) 107 108 if err := gui.resetOrigin(gui.getMainView()); err != nil { 109 return err 110 } 111 return gui.handleBranchSelect(gui.g, v) 112 } 113 114 // specific functions 115 116 func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error { 117 if gui.State.Panels.Branches.SelectedLine == -1 { 118 return nil 119 } 120 if gui.State.Panels.Branches.SelectedLine == 0 { 121 return gui.createErrorPanel(g, gui.Tr.SLocalize("AlreadyCheckedOutBranch")) 122 } 123 branch := gui.getSelectedBranch() 124 return gui.handleCheckoutBranch(branch.Name) 125 } 126 127 func (gui *Gui) handleCreatePullRequestPress(g *gocui.Gui, v *gocui.View) error { 128 pullRequest := commands.NewPullRequest(gui.GitCommand) 129 130 branch := gui.getSelectedBranch() 131 if err := pullRequest.Create(branch); err != nil { 132 return gui.createErrorPanel(g, err.Error()) 133 } 134 135 return nil 136 } 137 138 func (gui *Gui) handleGitFetch(g *gocui.Gui, v *gocui.View) error { 139 if err := gui.createLoaderPanel(gui.g, v, gui.Tr.SLocalize("FetchWait")); err != nil { 140 return err 141 } 142 go func() { 143 unamePassOpend, err := gui.fetch(g, v, true) 144 gui.HandleCredentialsPopup(g, unamePassOpend, err) 145 }() 146 return nil 147 } 148 149 func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error { 150 branch := gui.getSelectedBranch() 151 message := gui.Tr.SLocalize("SureForceCheckout") 152 title := gui.Tr.SLocalize("ForceCheckoutBranch") 153 return gui.createConfirmationPanel(g, v, title, message, func(g *gocui.Gui, v *gocui.View) error { 154 if err := gui.GitCommand.Checkout(branch.Name, true); err != nil { 155 gui.createErrorPanel(g, err.Error()) 156 } 157 return gui.refreshSidePanels(g) 158 }, nil) 159 } 160 161 func (gui *Gui) handleCheckoutBranch(branchName string) error { 162 if err := gui.GitCommand.Checkout(branchName, false); err != nil { 163 // note, this will only work for english-language git commands. If we force git to use english, and the error isn't this one, then the user will receive an english command they may not understand. I'm not sure what the best solution to this is. Running the command once in english and a second time in the native language is one option 164 if !strings.Contains(err.Error(), "Please commit your changes or stash them before you switch branch") { 165 if err := gui.createErrorPanel(gui.g, err.Error()); err != nil { 166 return err 167 } 168 } 169 170 // offer to autostash changes 171 return gui.createConfirmationPanel(gui.g, gui.getBranchesView(), gui.Tr.SLocalize("AutoStashTitle"), gui.Tr.SLocalize("AutoStashPrompt"), func(g *gocui.Gui, v *gocui.View) error { 172 if err := gui.GitCommand.StashSave(gui.Tr.SLocalize("StashPrefix") + branchName); err != nil { 173 return gui.createErrorPanel(g, err.Error()) 174 } 175 if err := gui.GitCommand.Checkout(branchName, false); err != nil { 176 return gui.createErrorPanel(g, err.Error()) 177 } 178 179 // checkout successful so we select the new branch 180 gui.State.Panels.Branches.SelectedLine = 0 181 182 if err := gui.GitCommand.StashDo(0, "pop"); err != nil { 183 if err := gui.refreshSidePanels(g); err != nil { 184 return err 185 } 186 return gui.createErrorPanel(g, err.Error()) 187 } 188 return gui.refreshSidePanels(g) 189 }, nil) 190 } 191 192 gui.State.Panels.Branches.SelectedLine = 0 193 return gui.refreshSidePanels(gui.g) 194 } 195 196 func (gui *Gui) handleCheckoutByName(g *gocui.Gui, v *gocui.View) error { 197 gui.createPromptPanel(g, v, gui.Tr.SLocalize("BranchName")+":", func(g *gocui.Gui, v *gocui.View) error { 198 return gui.handleCheckoutBranch(gui.trimmedContent(v)) 199 }) 200 return nil 201 } 202 203 func (gui *Gui) handleNewBranch(g *gocui.Gui, v *gocui.View) error { 204 branch := gui.State.Branches[0] 205 message := gui.Tr.TemplateLocalize( 206 "NewBranchNameBranchOff", 207 Teml{ 208 "branchName": branch.Name, 209 }, 210 ) 211 gui.createPromptPanel(g, v, message, func(g *gocui.Gui, v *gocui.View) error { 212 if err := gui.GitCommand.NewBranch(gui.trimmedContent(v)); err != nil { 213 return gui.createErrorPanel(g, err.Error()) 214 } 215 gui.refreshSidePanels(g) 216 return gui.handleBranchSelect(g, v) 217 }) 218 return nil 219 } 220 221 func (gui *Gui) handleDeleteBranch(g *gocui.Gui, v *gocui.View) error { 222 return gui.deleteBranch(g, v, false) 223 } 224 225 func (gui *Gui) handleForceDeleteBranch(g *gocui.Gui, v *gocui.View) error { 226 return gui.deleteBranch(g, v, true) 227 } 228 229 func (gui *Gui) deleteBranch(g *gocui.Gui, v *gocui.View, force bool) error { 230 selectedBranch := gui.getSelectedBranch() 231 if selectedBranch == nil { 232 return nil 233 } 234 checkedOutBranch := gui.State.Branches[0] 235 if checkedOutBranch.Name == selectedBranch.Name { 236 return gui.createErrorPanel(g, gui.Tr.SLocalize("CantDeleteCheckOutBranch")) 237 } 238 return gui.deleteNamedBranch(g, v, selectedBranch, force) 239 } 240 241 func (gui *Gui) deleteNamedBranch(g *gocui.Gui, v *gocui.View, selectedBranch *commands.Branch, force bool) error { 242 title := gui.Tr.SLocalize("DeleteBranch") 243 var messageID string 244 if force { 245 messageID = "ForceDeleteBranchMessage" 246 } else { 247 messageID = "DeleteBranchMessage" 248 } 249 message := gui.Tr.TemplateLocalize( 250 messageID, 251 Teml{ 252 "selectedBranchName": selectedBranch.Name, 253 }, 254 ) 255 return gui.createConfirmationPanel(g, v, title, message, func(g *gocui.Gui, v *gocui.View) error { 256 if err := gui.GitCommand.DeleteBranch(selectedBranch.Name, force); err != nil { 257 errMessage := err.Error() 258 if !force && strings.Contains(errMessage, "is not fully merged") { 259 return gui.deleteNamedBranch(g, v, selectedBranch, true) 260 } 261 return gui.createErrorPanel(g, errMessage) 262 } 263 return gui.refreshSidePanels(g) 264 }, nil) 265 } 266 267 func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error { 268 checkedOutBranch := gui.State.Branches[0].Name 269 selectedBranch := gui.getSelectedBranch().Name 270 if checkedOutBranch == selectedBranch { 271 return gui.createErrorPanel(g, gui.Tr.SLocalize("CantMergeBranchIntoItself")) 272 } 273 prompt := gui.Tr.TemplateLocalize( 274 "ConfirmMerge", 275 Teml{ 276 "checkedOutBranch": checkedOutBranch, 277 "selectedBranch": selectedBranch, 278 }, 279 ) 280 return gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("MergingTitle"), prompt, 281 func(g *gocui.Gui, v *gocui.View) error { 282 283 err := gui.GitCommand.Merge(selectedBranch) 284 return gui.handleGenericMergeCommandResult(err) 285 }, nil) 286 } 287 288 func (gui *Gui) handleRebase(g *gocui.Gui, v *gocui.View) error { 289 checkedOutBranch := gui.State.Branches[0].Name 290 selectedBranch := gui.getSelectedBranch().Name 291 if selectedBranch == checkedOutBranch { 292 return gui.createErrorPanel(g, gui.Tr.SLocalize("CantRebaseOntoSelf")) 293 } 294 prompt := gui.Tr.TemplateLocalize( 295 "ConfirmRebase", 296 Teml{ 297 "checkedOutBranch": checkedOutBranch, 298 "selectedBranch": selectedBranch, 299 }, 300 ) 301 return gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("RebasingTitle"), prompt, 302 func(g *gocui.Gui, v *gocui.View) error { 303 304 err := gui.GitCommand.RebaseBranch(selectedBranch) 305 return gui.handleGenericMergeCommandResult(err) 306 }, nil) 307 } 308 309 func (gui *Gui) handleFastForward(g *gocui.Gui, v *gocui.View) error { 310 branch := gui.getSelectedBranch() 311 if branch == nil { 312 return nil 313 } 314 if branch.Pushables == "" { 315 return nil 316 } 317 if branch.Pushables == "?" { 318 return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("FwdNoUpstream")) 319 } 320 if branch.Pushables != "0" { 321 return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("FwdCommitsToPush")) 322 } 323 upstream := "origin" // hardcoding for now 324 message := gui.Tr.TemplateLocalize( 325 "Fetching", 326 Teml{ 327 "from": fmt.Sprintf("%s/%s", upstream, branch.Name), 328 "to": branch.Name, 329 }, 330 ) 331 go func() { 332 _ = gui.createLoaderPanel(gui.g, v, message) 333 if err := gui.GitCommand.FastForward(branch.Name); err != nil { 334 _ = gui.createErrorPanel(gui.g, err.Error()) 335 } else { 336 _ = gui.closeConfirmationPrompt(gui.g) 337 _ = gui.RenderSelectedBranchUpstreamDifferences() 338 } 339 }() 340 return nil 341 }