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  }