From 1be0ff8da738c706e1aff8111428b36a5efced44 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 17 Mar 2020 21:22:07 +1100 Subject: [PATCH] better upstream tracking and allow renaming a branch --- pkg/commands/branch.go | 9 +- pkg/commands/branch_list_builder.go | 179 +++++++++++++++------------- pkg/commands/git.go | 6 +- pkg/config/app_config.go | 1 + pkg/gui/branches_panel.go | 74 ++++++++---- pkg/gui/files_panel.go | 26 ++-- pkg/gui/gui.go | 15 +-- pkg/gui/keybindings.go | 8 ++ pkg/gui/presentation/branches.go | 27 +++-- pkg/gui/reset_menu_panel.go | 3 + pkg/gui/status_panel.go | 11 +- pkg/i18n/english.go | 9 ++ 12 files changed, 222 insertions(+), 146 deletions(-) diff --git a/pkg/commands/branch.go b/pkg/commands/branch.go index 8da8918d1..f0f97a331 100644 --- a/pkg/commands/branch.go +++ b/pkg/commands/branch.go @@ -3,8 +3,9 @@ package commands // Branch : A git branch // duplicating this for now type Branch struct { - Name string - Recency string - Pushables string - Pullables string + Name string + Recency string + Pushables string + Pullables string + UpstreamName string } diff --git a/pkg/commands/branch_list_builder.go b/pkg/commands/branch_list_builder.go index 26864d944..553d2f1f9 100644 --- a/pkg/commands/branch_list_builder.go +++ b/pkg/commands/branch_list_builder.go @@ -7,8 +7,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/utils" "github.com/sirupsen/logrus" - - "gopkg.in/src-d/go-git.v4/plumbing" ) // context: @@ -36,86 +34,94 @@ func NewBranchListBuilder(log *logrus.Entry, gitCommand *GitCommand) (*BranchLis }, nil } -func (b *BranchListBuilder) obtainCurrentBranch() *Branch { +func (b *BranchListBuilder) obtainCurrentBranchName() string { branchName, err := b.GitCommand.CurrentBranchName() if err != nil { panic(err.Error()) } - - return &Branch{Name: strings.TrimSpace(branchName)} + return branchName } -func (b *BranchListBuilder) obtainReflogBranches() []*Branch { - branches := make([]*Branch, 0) - // if we directly put this string in RunCommandWithOutput the compiler complains because it thinks it's a format string - unescaped := "git reflog --date=relative --pretty='%gd|%gs' --grep-reflog='checkout: moving' HEAD" - rawString, err := b.GitCommand.OSCommand.RunCommandWithOutput(unescaped) - if err != nil { - return branches - } - - branchLines := utils.SplitLines(rawString) - for _, line := range branchLines { - recency, branchName := branchInfoFromLine(line) - if branchName == "" { - continue - } - branch := &Branch{Name: branchName, Recency: recency} - branches = append(branches, branch) - } - return uniqueByName(branches) -} - -func (b *BranchListBuilder) obtainSafeBranches() []*Branch { - branches := make([]*Branch, 0) - - bIter, err := b.GitCommand.Repo.Branches() +func (b *BranchListBuilder) obtainBranches() []*Branch { + cmdStr := `git branch --format="%(refname:short)|%(upstream:short)|%(upstream:track)"` + output, err := b.GitCommand.OSCommand.RunCommandWithOutput(cmdStr) if err != nil { panic(err) } - _ = bIter.ForEach(func(b *plumbing.Reference) error { - name := b.Name().Short() - branches = append(branches, &Branch{Name: name}) - return nil - }) + + trimmedOutput := strings.TrimSpace(output) + outputLines := strings.Split(trimmedOutput, "\n") + branches := make([]*Branch, len(outputLines)) + for i, line := range outputLines { + split := strings.Split(line, SEPARATION_CHAR) + + name := split[0] + branches[i] = &Branch{ + Name: name, + Pullables: "?", + Pushables: "?", + } + upstreamName := split[1] + if upstreamName == "" { + continue + } + + branches[i].UpstreamName = upstreamName + + track := split[2] + re := regexp.MustCompile(`ahead (\d+)`) + match := re.FindStringSubmatch(track) + if len(match) > 1 { + branches[i].Pushables = match[1] + } else { + branches[i].Pushables = "0" + } + + re = regexp.MustCompile(`behind (\d+)`) + match = re.FindStringSubmatch(track) + if len(match) > 1 { + branches[i].Pullables = match[1] + } else { + branches[i].Pullables = "0" + } + } return branches } -func (b *BranchListBuilder) appendNewBranches(finalBranches, newBranches, existingBranches []*Branch, included bool) []*Branch { - for _, newBranch := range newBranches { - if included == branchIncluded(newBranch.Name, existingBranches) { - finalBranches = append(finalBranches, newBranch) - } - } - return finalBranches -} - -func sanitisedReflogName(reflogBranch *Branch, safeBranches []*Branch) string { - for _, safeBranch := range safeBranches { - if strings.EqualFold(safeBranch.Name, reflogBranch.Name) { - return safeBranch.Name - } - } - return reflogBranch.Name -} - // Build the list of branches for the current repo func (b *BranchListBuilder) Build() []*Branch { - branches := make([]*Branch, 0) - head := b.obtainCurrentBranch() - safeBranches := b.obtainSafeBranches() - + currentBranchName := b.obtainCurrentBranchName() + branches := b.obtainBranches() reflogBranches := b.obtainReflogBranches() - for i, reflogBranch := range reflogBranches { - reflogBranches[i].Name = sanitisedReflogName(reflogBranch, safeBranches) + + // loop through reflog branches. If there is a match, merge them, then remove it from the branches and keep it in the reflog branches + branchesWithRecency := make([]*Branch, 0) +outer: + for _, reflogBranch := range reflogBranches { + for j, branch := range branches { + if strings.EqualFold(reflogBranch.Name, branch.Name) { + branch.Recency = reflogBranch.Recency + branchesWithRecency = append(branchesWithRecency, branch) + branches = append(branches[0:j], branches[j+1:]...) + continue outer + } + } } - branches = b.appendNewBranches(branches, reflogBranches, safeBranches, true) - branches = b.appendNewBranches(branches, safeBranches, branches, false) + branches = append(branchesWithRecency, branches...) - if len(branches) == 0 || branches[0].Name != head.Name { - branches = append([]*Branch{head}, branches...) + // if it's the current branch we need to pull it up to the top + for i, branch := range branches { + if branch.Name == currentBranchName { + branches = append(branches[0:i], branches[i+1:]...) + branches = append([]*Branch{branch}, branches...) + break + } + } + + if len(branches) == 0 || branches[0].Name != currentBranchName { + branches = append([]*Branch{{Name: currentBranchName}}, branches...) } branches[0].Recency = " *" @@ -123,26 +129,6 @@ func (b *BranchListBuilder) Build() []*Branch { return branches } -func branchIncluded(branchName string, branches []*Branch) bool { - for _, existingBranch := range branches { - if strings.EqualFold(existingBranch.Name, branchName) { - return true - } - } - return false -} - -func uniqueByName(branches []*Branch) []*Branch { - finalBranches := make([]*Branch, 0) - for _, branch := range branches { - if branchIncluded(branch.Name, finalBranches) { - continue - } - finalBranches = append(finalBranches, branch) - } - return finalBranches -} - // A line will have the form '10 days ago master' so we need to strip out the // useful information from that into timeNumber, timeUnit, and branchName func branchInfoFromLine(line string) (string, string) { @@ -172,3 +158,30 @@ func abbreviatedTimeUnit(timeUnit string) string { } return timeUnitMap[timeUnit] } + +func (b *BranchListBuilder) obtainReflogBranches() []*Branch { + branches := make([]*Branch, 0) + // if we directly put this string in RunCommandWithOutput the compiler complains because it thinks it's a format string + unescaped := "git reflog --date=relative --pretty='%gd|%gs' --grep-reflog='checkout: moving' HEAD" + rawString, err := b.GitCommand.OSCommand.RunCommandWithOutput(unescaped) + if err != nil { + return branches + } + + branchNameMap := map[string]bool{} + + branchLines := utils.SplitLines(rawString) + for _, line := range branchLines { + recency, branchName := branchInfoFromLine(line) + if branchName == "" { + continue + } + if _, ok := branchNameMap[branchName]; ok { + continue + } + branchNameMap[branchName] = true + branch := &Branch{Name: branchName, Recency: recency} + branches = append(branches, branch) + } + return branches +} diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 00ec296de..2bd852de8 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -343,7 +343,7 @@ func (c *GitCommand) CurrentBranchName() (string, error) { match := re.FindStringSubmatch(output) branchName = match[1] } - return utils.TrimTrailingNewline(branchName), nil + return strings.TrimSpace(branchName), nil } // DeleteBranch delete branch @@ -1163,3 +1163,7 @@ func (c *GitCommand) GetPager(width int) string { func (c *GitCommand) colorArg() string { return c.Config.GetUserConfig().GetString("git.paging.colorArg") } + +func (c *GitCommand) RenameBranch(oldName string, newName string) error { + return c.OSCommand.RunCommand("git branch --move %s %s", oldName, newName) +} diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index bc79e12ba..deb5a9bd3 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -334,6 +334,7 @@ keybinding: checkoutBranchByName: 'c' forceCheckoutBranch: 'F' rebaseBranch: 'r' + renameBranch: 'R' mergeIntoCurrentBranch: 'M' viewGitFlowOptions: 'i' fastForward: 'f' diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index caac8e218..b2e9b1525 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -41,9 +41,6 @@ func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error { } branch := gui.getSelectedBranch() v.FocusPoint(0, gui.State.Panels.Branches.SelectedLine) - if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil { - return err - } cmd := gui.OSCommand.ExecutableFromString( gui.GitCommand.GetBranchGraphCmdStr(branch.Name), @@ -54,24 +51,6 @@ func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error { return nil } -func (gui *Gui) RenderSelectedBranchUpstreamDifferences() error { - return gui.newTask("branches", func(stop chan struct{}) error { - branch := gui.getSelectedBranch() - branch.Pushables, branch.Pullables = gui.GitCommand.GetBranchUpstreamDifferenceCount(branch.Name) - - select { - case <-stop: - return nil - default: - } - - branchesView := gui.getBranchesView() - displayStrings := presentation.GetBranchListDisplayStrings(gui.State.Branches, gui.currentViewName() == "branches", gui.State.Panels.Branches.SelectedLine) - gui.renderDisplayStrings(branchesView, displayStrings) - return nil - }) -} - // gui.refreshStatus is called at the end of this because that's when we can // be sure there is a state.Branches array to pick the current branch from func (gui *Gui) refreshBranches(g *gocui.Gui) error { @@ -106,9 +85,8 @@ func (gui *Gui) renderLocalBranchesWithSelection() error { branchesView := gui.getBranchesView() gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches)) - if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil { - return err - } + displayStrings := presentation.GetBranchListDisplayStrings(gui.State.Branches, gui.State.ScreenMode != SCREEN_NORMAL) + gui.renderDisplayStrings(branchesView, displayStrings) if gui.g.CurrentView() == branchesView { if err := gui.handleBranchSelect(gui.g, branchesView); err != nil { return err @@ -375,7 +353,7 @@ func (gui *Gui) handleFastForward(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.FastForward(branch.Name, remoteName, remoteBranchName); err != nil { _ = gui.createErrorPanel(gui.g, err.Error()) } - _ = gui.RenderSelectedBranchUpstreamDifferences() + _ = gui.refreshBranches(gui.g) } _ = gui.closeConfirmationPrompt(gui.g, true) @@ -407,7 +385,13 @@ func (gui *Gui) switchBranchesPanelContext(context string) error { branchesView.TabIndex = contextTabIndexMap[context] - switch context { + return gui.refreshBranchesViewWithSelection() +} + +func (gui *Gui) refreshBranchesViewWithSelection() error { + branchesView := gui.getBranchesView() + + switch branchesView.Context { case "local-branches": return gui.renderLocalBranchesWithSelection() case "remotes": @@ -457,3 +441,41 @@ func (gui *Gui) onBranchesPanelSearchSelect(selectedLine int) error { } return nil } + +func (gui *Gui) handleRenameBranch(g *gocui.Gui, v *gocui.View) error { + branch := gui.getSelectedBranch() + if branch == nil { + return nil + } + + promptForNewName := func() error { + return gui.createPromptPanel(g, v, gui.Tr.SLocalize("NewBranchNamePrompt")+" "+branch.Name+":", "", func(g *gocui.Gui, v *gocui.View) error { + newName := gui.trimmedContent(v) + if err := gui.GitCommand.RenameBranch(branch.Name, newName); err != nil { + return gui.createErrorPanel(gui.g, err.Error()) + } + // need to checkout so that the branch shows up in our reflog and therefore + // doesn't get lost among all the other branches when we switch to something else + if err := gui.GitCommand.Checkout(newName, false); err != nil { + return gui.createErrorPanel(gui.g, err.Error()) + } + + return gui.refreshBranches(gui.g) + }) + } + + // I could do an explicit check here for whether the branch is tracking a remote branch + // but if we've selected it we'll already know that via Pullables and Pullables. + // Bit of a hack but I'm lazy. + notTrackingRemote := branch.Pullables == "?" + if notTrackingRemote { + return promptForNewName() + } + return gui.createConfirmationPanel(gui.g, v, true, gui.Tr.SLocalize("renameBranch"), gui.Tr.SLocalize("RenameBranchWarning"), func(_g *gocui.Gui, _v *gocui.View) error { + return promptForNewName() + }, nil) +} + +func (gui *Gui) currentBranch() *commands.Branch { + return gui.State.Branches[0] +} diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 0b761bff7..bcfeccaa1 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -399,24 +399,20 @@ func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) { func (gui *Gui) handlePullFiles(g *gocui.Gui, v *gocui.View) error { // if we have no upstream branch we need to set that first - _, pullables := gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount() - currentBranchName, err := gui.GitCommand.CurrentBranchName() - if err != nil { - return err - } - if pullables == "?" { + currentBranch := gui.currentBranch() + if currentBranch.Pullables == "?" { // see if we have this branch in our config with an upstream conf, err := gui.GitCommand.Repo.Config() if err != nil { return gui.createErrorPanel(gui.g, err.Error()) } for branchName, branch := range conf.Branches { - if branchName == currentBranchName { + if branchName == currentBranch.Name { return gui.pullFiles(v, fmt.Sprintf("%s %s", branch.Remote, branchName)) } } - return gui.createPromptPanel(g, v, gui.Tr.SLocalize("EnterUpstream"), "origin/"+currentBranchName, func(g *gocui.Gui, v *gocui.View) error { + return gui.createPromptPanel(g, v, gui.Tr.SLocalize("EnterUpstream"), "origin/"+currentBranch.Name, func(g *gocui.Gui, v *gocui.View) error { upstream := gui.trimmedContent(v) if err := gui.GitCommand.SetUpstreamBranch(upstream); err != nil { errorMessage := err.Error() @@ -467,28 +463,24 @@ func (gui *Gui) pushWithForceFlag(g *gocui.Gui, v *gocui.View, force bool, upstr func (gui *Gui) pushFiles(g *gocui.Gui, v *gocui.View) error { // if we have pullables we'll ask if the user wants to force push - _, pullables := gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount() - currentBranchName, err := gui.GitCommand.CurrentBranchName() - if err != nil { - return err - } + currentBranch := gui.currentBranch() - if pullables == "?" { + if currentBranch.Pullables == "?" { // see if we have this branch in our config with an upstream conf, err := gui.GitCommand.Repo.Config() if err != nil { return gui.createErrorPanel(gui.g, err.Error()) } for branchName, branch := range conf.Branches { - if branchName == currentBranchName { + if branchName == currentBranch.Name { return gui.pushWithForceFlag(g, v, false, "", fmt.Sprintf("%s %s", branch.Remote, branchName)) } } - return gui.createPromptPanel(g, v, gui.Tr.SLocalize("EnterUpstream"), "origin "+currentBranchName, func(g *gocui.Gui, v *gocui.View) error { + return gui.createPromptPanel(g, v, gui.Tr.SLocalize("EnterUpstream"), "origin "+currentBranch.Name, func(g *gocui.Gui, v *gocui.View) error { return gui.pushWithForceFlag(g, v, false, gui.trimmedContent(v), "") }) - } else if pullables == "0" { + } else if currentBranch.Pullables == "0" { return gui.pushWithForceFlag(g, v, false, "", "") } return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("ForcePush"), gui.Tr.SLocalize("ForcePushPrompt"), func(g *gocui.Gui, v *gocui.View) error { diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 2204e3ac4..42f4bfc75 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -24,7 +24,6 @@ import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/presentation" "github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/tasks" "github.com/jesseduffield/lazygit/pkg/theme" @@ -278,6 +277,10 @@ func (gui *Gui) nextScreenMode(g *gocui.Gui, v *gocui.View) error { if err := gui.refreshCommitsViewWithSelection(); err != nil { return err } + // same with branches + if err := gui.refreshBranchesViewWithSelection(); err != nil { + return err + } return nil } @@ -288,6 +291,10 @@ func (gui *Gui) prevScreenMode(g *gocui.Gui, v *gocui.View) error { if err := gui.refreshCommitsViewWithSelection(); err != nil { return err } + // same with branches + if err := gui.refreshBranchesViewWithSelection(); err != nil { + return err + } return nil } @@ -386,12 +393,6 @@ func (gui *Gui) onFocusLost(v *gocui.View, newView *gocui.View) error { } } switch v.Name() { - case "branches": - if v.Context == "local-branches" { - // This stops the branches panel from showing the upstream/downstream changes to the selected branch, when it loses focus - displayStrings := presentation.GetBranchListDisplayStrings(gui.State.Branches, false, -1) - gui.renderDisplayStrings(gui.getBranchesView(), displayStrings) - } case "main": // if we have lost focus to a first-class panel, we need to do some cleanup gui.changeMainViewsContext("normal") diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 0fb196aa3..258409e09 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -575,6 +575,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { Handler: gui.handleCreateResetToBranchMenu, Description: gui.Tr.SLocalize("viewResetOptions"), }, + { + ViewName: "branches", + Contexts: []string{"local-branches"}, + Key: gui.getKey("branches.renameBranch"), + Modifier: gocui.ModNone, + Handler: gui.handleRenameBranch, + Description: gui.Tr.SLocalize("viewResetOptions"), + }, { ViewName: "branches", Contexts: []string{"tags"}, diff --git a/pkg/gui/presentation/branches.go b/pkg/gui/presentation/branches.go index 3b918116b..4283ee1dd 100644 --- a/pkg/gui/presentation/branches.go +++ b/pkg/gui/presentation/branches.go @@ -10,25 +10,38 @@ import ( "github.com/jesseduffield/lazygit/pkg/utils" ) -func GetBranchListDisplayStrings(branches []*commands.Branch, isFocused bool, selectedLine int) [][]string { +func GetBranchListDisplayStrings(branches []*commands.Branch, fullDescription bool) [][]string { lines := make([][]string, len(branches)) for i := range branches { - showUpstreamDifferences := isFocused && i == selectedLine - lines[i] = getBranchDisplayStrings(branches[i], showUpstreamDifferences) + lines[i] = getBranchDisplayStrings(branches[i], fullDescription) } return lines } // getBranchDisplayStrings returns the display string of branch -func getBranchDisplayStrings(b *commands.Branch, showUpstreamDifferences bool) []string { +func getBranchDisplayStrings(b *commands.Branch, fullDescription bool) []string { displayName := utils.ColoredString(b.Name, GetBranchColor(b.Name)) - if showUpstreamDifferences && b.Pushables != "" && b.Pullables != "" { - displayName = fmt.Sprintf("%s ↑%s↓%s", displayName, b.Pushables, b.Pullables) + if b.Pushables != "" && b.Pullables != "" && b.Pushables != "?" && b.Pullables != "?" { + trackColor := color.FgYellow + if b.Pushables == "0" && b.Pullables == "0" { + trackColor = color.FgGreen + } + track := utils.ColoredString(fmt.Sprintf("↑%s↓%s", b.Pushables, b.Pullables), trackColor) + displayName = fmt.Sprintf("%s %s", displayName, track) } - return []string{b.Recency, displayName} + recencyColor := color.FgCyan + if b.Recency == " *" { + recencyColor = color.FgGreen + } + + if fullDescription { + return []string{utils.ColoredString(b.Recency, recencyColor), displayName, utils.ColoredString(b.UpstreamName, color.FgYellow)} + } + + return []string{utils.ColoredString(b.Recency, recencyColor), displayName} } // GetBranchColor branch color diff --git a/pkg/gui/reset_menu_panel.go b/pkg/gui/reset_menu_panel.go index 5e9cc0068..1920aeb1e 100644 --- a/pkg/gui/reset_menu_panel.go +++ b/pkg/gui/reset_menu_panel.go @@ -36,6 +36,9 @@ func (gui *Gui) createResetMenu(ref string) error { if err := gui.refreshFiles(); err != nil { return err } + if err := gui.refreshBranches(gui.g); err != nil { + return err + } if err := gui.resetOrigin(gui.getCommitsView()); err != nil { return err } diff --git a/pkg/gui/status_panel.go b/pkg/gui/status_panel.go index 4c69e9b30..d7561d9d7 100644 --- a/pkg/gui/status_panel.go +++ b/pkg/gui/status_panel.go @@ -22,11 +22,20 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error { // contents end up cleared g.Update(func(*gocui.Gui) error { v.Clear() + // TODO: base this off of the current branch state.pushables, state.pullables = gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount() if err := gui.updateWorkTreeState(); err != nil { return err } - status := fmt.Sprintf("↑%s↓%s", state.pushables, state.pullables) + + trackColor := color.FgYellow + if state.pushables == "0" && state.pullables == "0" { + trackColor = color.FgGreen + } else if state.pushables == "?" && state.pullables == "?" { + trackColor = color.FgRed + } + + status := utils.ColoredString(fmt.Sprintf("↑%s↓%s", state.pushables, state.pullables), trackColor) branches := gui.State.Branches if gui.State.WorkingTreeState != "normal" { diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 9df162483..121fadda5 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -1017,6 +1017,15 @@ func addEnglish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "Keybindings", Other: "Keybindings", + }, &i18n.Message{ + ID: "renameBranch", + Other: "rename branch", + }, &i18n.Message{ + ID: "NewBranchNamePrompt", + Other: "Enter new branch name for branch", + }, &i18n.Message{ + ID: "RenameBranchWarning", + Other: "This branch is tracking a remote. This action will only rename the local branch name, not the name of the remote branch. Continue?", }, ) }