1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-02-01 13:17:53 +02:00

Add rebase from marked base commit test

This also fixes a bug where after the rebase each commit in the commits view had a tick against it because we hadn't
refreshed the view since the base commit was no longer marked
This commit is contained in:
Jesse Duffield 2023-08-12 13:17:01 +10:00
parent 3ea81d4a6f
commit f1753f36c8
7 changed files with 385 additions and 14 deletions

View File

@ -129,6 +129,12 @@ Learn more in the [Rebase magic Youtube tutorial](https://youtu.be/4XaToVut_hs).
![custom_patch](../assets/demo/custom_patch-compressed.gif)
### Rebase from marked base commit
Say you're on a feature branch that was itself branched off of the develop branch, and you've decided you'd rather be branching off the master branch. You need a way to rebase only the commits from your feature branch. In this demo we check to see which was the last commit on the develop branch, then press `shift+b` to mark that commit as our base commit, then press `r` on the master branch to rebase onto it, only bringing across the commits from our feature branch. Then we push our changes with `shift+p`.
![rebase_onto](../assets/demo/rebase_onto-compressed.gif)
### Undo
You can undo the last action by pressing 'z' and redo with `ctrl+z`. Here we drop a couple of commits and then undo the actions.

View File

@ -235,7 +235,7 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
}
err = self.CheckMergeOrRebase(err)
if err == nil {
self.c.Modes().MarkedBaseCommit.Reset()
return self.ResetMarkedBaseCommit()
}
return err
})
@ -257,7 +257,9 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
if err = self.CheckMergeOrRebase(err); err != nil {
return err
}
self.c.Modes().MarkedBaseCommit.Reset()
if err = self.ResetMarkedBaseCommit(); err != nil {
return err
}
return self.c.PushContext(self.c.Contexts().LocalCommits)
},
},

View File

@ -210,3 +210,265 @@ var RandomFiles = []RandomFile{
{Name: `seo/alt_text2.go`, Content: `package seo`},
{Name: `moderation/comment_moderation2.go`, Content: `package moderation`},
}
var RandomFileContents = []string{
`package main
import (
"bytes"
"fmt"
"go/format"
"io/fs"
"os"
"strings"
"github.com/samber/lo"
)
func main() {
code := generateCode()
formattedCode, err := format.Source(code)
if err != nil {
panic(err)
}
if err := os.WriteFile("test_list.go", formattedCode, 0o644); err != nil {
panic(err)
}
}
`,
`
package tests
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/lazycore/pkg/utils"
"github.com/jesseduffield/lazygit/pkg/integration/components"
"github.com/samber/lo"
)
func GetTests() []*components.IntegrationTest {
// first we ensure that each test in this directory has actually been added to the above list.
testCount := 0
testNamesSet := set.NewFromSlice(lo.Map(
tests,
func(test *components.IntegrationTest, _ int) string {
return test.Name()
},
))
}
`,
`
package components
import (
"os"
"strconv"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/config"
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
// IntegrationTest describes an integration test that will be run against the lazygit gui.
// our unit tests will use this description to avoid a panic caused by attempting
// to get the test's name via it's file's path.
const unitTestDescription = "test test"
const (
defaultWidth = 100
defaultHeight = 100
)
`,
`package components
import (
"fmt"
"time"
"github.com/atotto/clipboard"
"github.com/jesseduffield/lazygit/pkg/config"
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
)
type TestDriver struct {
gui integrationTypes.GuiDriver
keys config.KeybindingConfig
inputDelay int
*assertionHelper
shell *Shell
}
func NewTestDriver(gui integrationTypes.GuiDriver, shell *Shell, keys config.KeybindingConfig, inputDelay int) *TestDriver {
return &TestDriver{
gui: gui,
keys: keys,
inputDelay: inputDelay,
assertionHelper: &assertionHelper{gui: gui},
shell: shell,
}
}
// key is something like 'w' or '<space>'. It's best not to pass a direct value,
// but instead to go through the default user config to get a more meaningful key name
func (self *TestDriver) press(keyStr string) {
self.SetCaption(fmt.Sprintf("Pressing %s", keyStr))
self.gui.PressKey(keyStr)
self.Wait(self.inputDelay)
}
`,
`package updates
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/go-errors/errors"
"github.com/kardianos/osext"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/constants"
"github.com/jesseduffield/lazygit/pkg/utils"
)
// Updater checks for updates and does updates
type Updater struct {
*common.Common
Config config.AppConfigurer
OSCommand *oscommands.OSCommand
}
// Updaterer implements the check and update methods
type Updaterer interface {
CheckForNewUpdate()
Update()
}
`,
`
package utils
import (
"fmt"
"regexp"
"strings"
)
// IsValidEmail checks if an email address is valid
func IsValidEmail(email string) bool {
// Using a regex pattern to validate email addresses
// This is a simple example and might not cover all edge cases
emailPattern := ` + "`" + `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` + "`" + `
match, _ := regexp.MatchString(emailPattern, email)
return match
}
`,
`
package main
import (
"fmt"
"net/http"
"time"
"github.com/jesseduffield/lazygit/pkg/utils"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, the current time is: %s", time.Now().Format(time.RFC3339))
})
port := 8080
utils.PrintMessage(fmt.Sprintf("Server is listening on port %d", port))
http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
}
`,
`
package logging
import (
"fmt"
"os"
"time"
)
// LogMessage represents a log message with its timestamp
type LogMessage struct {
Timestamp time.Time
Message string
}
// Log writes a message to the log file along with a timestamp
func Log(message string) {
logFile, err := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Println("Error opening log file:", err)
return
}
defer logFile.Close()
logEntry := LogMessage{
Timestamp: time.Now(),
Message: message,
}
logLine := fmt.Sprintf("[%s] %s\n", logEntry.Timestamp.Format("2006-01-02 15:04:05"), logEntry.Message)
_, err = logFile.WriteString(logLine)
if err != nil {
fmt.Println("Error writing to log file:", err)
}
}
`,
`
package encryption
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"io"
)
// Encrypt encrypts a plaintext using AES-GCM encryption
func Encrypt(key []byte, plaintext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, aesGCM.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
ciphertext := aesGCM.Seal(nil, nonce, plaintext, nil)
return append(nonce, ciphertext...), nil
}
`,
}

View File

@ -18,6 +18,8 @@ type Shell struct {
// when running the shell outside the gui we can directly panic on failure,
// but inside the gui we need to close the gui before panicking
fail func(string)
randomFileContentIndex int
}
func NewShell(dir string, fail func(string)) *Shell {
@ -221,6 +223,16 @@ func (self *Shell) CreateNCommitsWithRandomMessages(n int) *Shell {
return self
}
// Creates a commit with a random file
// Only to be used in demos
func (self *Shell) RandomChangeCommit(message string) *Shell {
index := self.randomFileContentIndex
self.randomFileContentIndex++
randomFileName := fmt.Sprintf("random-%d.go", index)
self.CreateFileAndAdd(randomFileName, RandomFileContents[index%len(RandomFileContents)])
return self.Commit(message)
}
func (self *Shell) SetConfig(key string, value string) *Shell {
self.RunCommand([]string{"git", "config", "--local", key, value})
return self
@ -361,3 +373,10 @@ func (self *Shell) Chdir(path string) *Shell {
return self
}
func (self *Shell) SetAuthor(authorName string, authorEmail string) *Shell {
self.RunCommand([]string{"git", "config", "--local", "user.name", authorName})
self.RunCommand([]string{"git", "config", "--local", "user.email", authorEmail})
return self
}

View File

@ -40,12 +40,12 @@ var RebaseFromMarkedBase = NewIntegrationTest(NewIntegrationTestArgs{
NavigateToLine(Contains("active one")).
Press(keys.Commits.MarkCommitAsBaseForRebase).
Lines(
Contains("active three"),
Contains("active two"),
Contains("active three").Contains("✓"),
Contains("active two").Contains("✓"),
Contains("↑↑↑ Will rebase from here ↑↑↑ active one"),
Contains("three"),
Contains("two"),
Contains("one"),
Contains("three").DoesNotContain("✓"),
Contains("two").DoesNotContain("✓"),
Contains("one").DoesNotContain("✓"),
)
t.Views().Information().Content(Contains("Marked a base commit for rebase"))
@ -66,13 +66,13 @@ var RebaseFromMarkedBase = NewIntegrationTest(NewIntegrationTestArgs{
Confirm()
t.Views().Commits().Lines(
Contains("active three"),
Contains("active two"),
Contains("target two"),
Contains("target one"),
Contains("three"),
Contains("two"),
Contains("one"),
Contains("active three").DoesNotContain("✓"),
Contains("active two").DoesNotContain("✓"),
Contains("target two").DoesNotContain("✓"),
Contains("target one").DoesNotContain("✓"),
Contains("three").DoesNotContain("✓"),
Contains("two").DoesNotContain("✓"),
Contains("one").DoesNotContain("✓"),
)
},
})

View File

@ -0,0 +1,81 @@
package demo
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var RebaseOnto = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Rebase with '--onto' flag. We start with a feature branch on the develop branch that we want to rebase onto the master branch",
ExtraCmdArgs: []string{},
Skip: false,
IsDemo: true,
SetupConfig: func(config *config.AppConfig) {
config.UserConfig.Gui.NerdFontsVersion = "3"
},
SetupRepo: func(shell *Shell) {
shell.CreateNCommitsWithRandomMessages(60)
shell.NewBranch("develop")
shell.SetAuthor("Joe Blow", "joeblow@gmail.com")
shell.RandomChangeCommit("Develop commit 1")
shell.RandomChangeCommit("Develop commit 2")
shell.RandomChangeCommit("Develop commit 3")
shell.SetAuthor("Jesse Duffield", "jesseduffield@gmail.com")
shell.NewBranch("feature/demo")
shell.RandomChangeCommit("Feature commit 1")
shell.RandomChangeCommit("Feature commit 2")
shell.RandomChangeCommit("Feature commit 3")
shell.CloneIntoRemote("origin")
shell.SetBranchUpstream("feature/demo", "origin/feature/demo")
shell.SetBranchUpstream("develop", "origin/develop")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.SetCaptionPrefix("Rebase from marked base commit")
t.Wait(1000)
// first we focus the commits view, then expand to show the branches against each commit
// Then we go back to normal value, mark the last develop branch commit as the marked commit
// Then go to the branches view and press 'r' on the master branch to rebase onto it
// then we force push our changes.
t.Views().Commits().
Focus().
Press(keys.Universal.PrevScreenMode).
Wait(500).
NavigateToLine(Contains("Develop commit 3")).
Wait(500).
Press(keys.Commits.MarkCommitAsBaseForRebase).
Wait(1000).
Press(keys.Universal.NextScreenMode).
Wait(500)
t.Views().Branches().
Focus().
Wait(500).
NavigateToLine(Contains("master")).
Wait(500).
Press(keys.Branches.RebaseBranch).
Tap(func() {
t.ExpectPopup().Menu().
Title(Contains("Rebase 'feature/demo' from marked base onto 'master'")).
Select(Contains("Simple rebase")).
Confirm()
}).
Wait(1000).
Press(keys.Universal.Push).
Tap(func() {
t.ExpectPopup().Confirmation().
Title(Contains("Force push")).
Content(AnyString()).
Wait(500).
Confirm()
})
},
})

View File

@ -98,6 +98,7 @@ var tests = []*components.IntegrationTest{
demo.Filter,
demo.InteractiveRebase,
demo.NukeWorkingTree,
demo.RebaseOnto,
demo.StageLines,
demo.Undo,
demo.WorktreeCreateFromBranches,