Show popup message with breaking changes on startup

This commit is contained in:
Stefan Haller 2024-03-10 20:59:11 +01:00
parent d12ceeb1ec
commit 2f4437591e
6 changed files with 226 additions and 0 deletions

View file

@ -343,6 +343,7 @@ type AppState struct {
LastUpdateCheck int64
RecentRepos []string
StartupPopupVersion int
LastVersion string // this is the last version the user was using, for the purpose of showing release notes
// these are for custom commands typed in directly, not for custom commands in the lazygit config
CustomCommandsHistory []string
@ -367,6 +368,7 @@ func getDefaultAppState() *AppState {
LastUpdateCheck: 0,
RecentRepos: []string{},
StartupPopupVersion: 0,
LastVersion: "",
DiffContextSize: 3,
LocalBranchSortOrder: "recency",
RemoteBranchSortOrder: "alphabetical",

View file

@ -5,6 +5,7 @@ import (
"fmt"
"io"
"os"
"sort"
"strings"
"sync"
@ -40,6 +41,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/updates"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"github.com/sasha-s/go-deadlock"
"gopkg.in/ozeidan/fuzzy-patricia.v3/patricia"
)
@ -869,6 +871,72 @@ func (gui *Gui) showIntroPopupMessage() {
})
}
func (gui *Gui) showBreakingChangesMessage() {
_, err := types.ParseVersionNumber(gui.Config.GetVersion())
if err != nil {
// We don't have a parseable version, so we'll assume it's a developer
// build, or a build from HEAD with a version such as 0.40.0-g1234567;
// in these cases we don't show release notes.
return
}
last := &types.VersionNumber{}
lastVersionStr := gui.c.GetAppState().LastVersion
// If there's no saved last version, we show all release notes. This is for
// people upgrading from a version before we started to save lastVersion.
// First time new users won't see the release notes because we show them the
// intro popup instead.
if lastVersionStr != "" {
last, err = types.ParseVersionNumber(lastVersionStr)
if err != nil {
// The last version was a developer build, so don't show release
// notes in this case either.
return
}
}
// Now collect all release notes texts for versions newer than lastVersion.
// We don't need to bother checking the current version here, because we
// can't possibly have texts for versions newer than current.
type versionAndText struct {
version *types.VersionNumber
text string
}
texts := []versionAndText{}
for versionStr, text := range gui.Tr.BreakingChangesByVersion {
v, err := types.ParseVersionNumber(versionStr)
if err != nil {
// Ignore bogus entries in the BreakingChanges map
continue
}
if last.IsOlderThan(v) {
texts = append(texts, versionAndText{version: v, text: text})
}
}
if len(texts) > 0 {
sort.Slice(texts, func(i, j int) bool {
return texts[i].version.IsOlderThan(texts[j].version)
})
message := strings.Join(lo.Map(texts, func(t versionAndText, _ int) string { return t.text }), "\n")
gui.waitForIntro.Add(1)
gui.c.OnUIThread(func() error {
onConfirm := func() error {
gui.waitForIntro.Done()
return nil
}
return gui.c.Confirm(types.ConfirmOpts{
Title: gui.Tr.BreakingChangesTitle,
Prompt: gui.Tr.BreakingChangesMessage + "\n\n" + message,
HandleConfirm: onConfirm,
HandleClose: onConfirm,
})
})
}
}
// setColorScheme sets the color scheme for the app based on the user config
func (gui *Gui) setColorScheme() error {
userConfig := gui.UserConfig

View file

@ -254,9 +254,14 @@ func (gui *Gui) onInitialViewsCreation() error {
storedPopupVersion := gui.c.GetAppState().StartupPopupVersion
if storedPopupVersion < StartupPopupVersion {
gui.showIntroPopupMessage()
} else {
gui.showBreakingChangesMessage()
}
}
gui.c.GetAppState().LastVersion = gui.Config.GetVersion()
gui.c.SaveAppStateAndLogError()
if gui.showRecentRepos {
if err := gui.helpers.Repos.CreateRecentReposMenu(); err != nil {
return err

View file

@ -0,0 +1,41 @@
package types
import (
"errors"
"regexp"
"strconv"
)
type VersionNumber struct {
Major, Minor, Patch int
}
func (v *VersionNumber) IsOlderThan(otherVersion *VersionNumber) bool {
this := v.Major*1000*1000 + v.Minor*1000 + v.Patch
other := otherVersion.Major*1000*1000 + otherVersion.Minor*1000 + otherVersion.Patch
return this < other
}
func ParseVersionNumber(versionStr string) (*VersionNumber, error) {
re := regexp.MustCompile(`^v?(\d+)\.(\d+)(?:\.(\d+))?$`)
matches := re.FindStringSubmatch(versionStr)
if matches == nil {
return nil, errors.New("unexpected version format: " + versionStr)
}
v := &VersionNumber{}
var err error
if v.Major, err = strconv.Atoi(matches[1]); err != nil {
return nil, err
}
if v.Minor, err = strconv.Atoi(matches[2]); err != nil {
return nil, err
}
if len(matches[3]) > 0 {
if v.Patch, err = strconv.Atoi(matches[3]); err != nil {
return nil, err
}
}
return v, nil
}

View file

@ -0,0 +1,81 @@
package types
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseVersionNumber(t *testing.T) {
tests := []struct {
versionStr string
expected *VersionNumber
err error
}{
{
versionStr: "1.2.3",
expected: &VersionNumber{
Major: 1,
Minor: 2,
Patch: 3,
},
err: nil,
},
{
versionStr: "v1.2.3",
expected: &VersionNumber{
Major: 1,
Minor: 2,
Patch: 3,
},
err: nil,
},
{
versionStr: "12.34.56",
expected: &VersionNumber{
Major: 12,
Minor: 34,
Patch: 56,
},
err: nil,
},
{
versionStr: "1.2",
expected: &VersionNumber{
Major: 1,
Minor: 2,
Patch: 0,
},
err: nil,
},
{
versionStr: "1",
expected: nil,
err: errors.New("unexpected version format: 1"),
},
{
versionStr: "invalid",
expected: nil,
err: errors.New("unexpected version format: invalid"),
},
{
versionStr: "junk_before 1.2.3",
expected: nil,
err: errors.New("unexpected version format: junk_before 1.2.3"),
},
{
versionStr: "1.2.3 junk_after",
expected: nil,
err: errors.New("unexpected version format: 1.2.3 junk_after"),
},
}
for _, test := range tests {
t.Run(test.versionStr, func(t *testing.T) {
actual, err := ParseVersionNumber(test.versionStr)
assert.Equal(t, test.expected, actual)
assert.Equal(t, test.err, err)
})
}
}

View file

@ -773,6 +773,9 @@ type TranslationSet struct {
Actions Actions
Bisect Bisect
Log Log
BreakingChangesTitle string
BreakingChangesMessage string
BreakingChangesByVersion map[string]string
}
type Bisect struct {
@ -1866,5 +1869,31 @@ func EnglishTranslationSet() TranslationSet {
AppendingLineToFile: "Appending '{{.line}}' to file '{{.filename}}'",
EditRebaseFromBaseCommit: "Beginning interactive rebase from '{{.baseCommit}}' onto '{{.targetBranchName}}",
},
BreakingChangesTitle: "Breaking Changes",
BreakingChangesMessage: `You are updating to a new version of lazygit which contains breaking changes. Please review the notes below and update your configuration if necessary.
For more information, see the full release notes at <https://github.com/jesseduffield/lazygit/releases>.`,
BreakingChangesByVersion: map[string]string{
"0.41.0": `- When you press 'g' to bring up the git reset menu, the 'mixed' option is now the first and default, rather than 'soft'. This is because 'mixed' is the most commonly used option.
- The commit message panel now automatically hard-wraps by default (i.e. it adds newline characters when you reach the margin). You can adjust the config like so:
git:
commit:
autoWrapCommitMessage: true
autoWrapWidth: 72
- The 'v' key was already being used in the staging view to start a range select, but now you can use it to start a range select in any view. Unfortunately this clashes with the 'v' keybinding for pasting commits (cherry-pick), so now pasting commits is done via 'shift+V' and for the sake of consistency, copying commits is now done via 'shift+C' instead of just 'c'. Note that the 'v' keybinding is only one way to start a range-select: you can use shift+up/down arrow instead. So, if you want to configure the cherry-pick keybindings to get the old behaviour, set the following in your config:
keybinding:
universal:
toggleRangeSelect: <something other than v>
commits:
cherryPickCopy: 'c'
pasteCommits: 'v'
- Squashing fixups using 'shift-S' now brings up a menu, with the default option being to squash all fixup commits in the branch. The original behaviour of only squashing fixup commits above the selected commit is still available as the second option in that menu.
- Push/pull/fetch loading statuses are now shown against the branch rather than in a popup. This allows you to e.g. fetch multiple branches in parallel and see the status for each branch.
- The git log graph in the commits view is now always shown by default (previously it was only shown when the view was maximised). If you find this too noisy, you can change it back via ctrl+L -> 'Show git graph' -> 'when maximised'
`,
},
}
}