1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-02-09 13:47:11 +02:00

Merge pull request #2323 from jesseduffield/migrate-more-tests

This commit is contained in:
Jesse Duffield 2022-12-20 22:51:04 +11:00 committed by GitHub
commit 6ec88ce8ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
715 changed files with 179 additions and 1747 deletions

10
go.mod
View File

@ -18,7 +18,7 @@ require (
github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
github.com/jesseduffield/gocui v0.3.1-0.20221112081529-154bebde5bb5
github.com/jesseduffield/gocui v0.3.1-0.20221203233251-eeb9b3fb2494
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
@ -62,14 +62,14 @@ require (
github.com/onsi/ginkgo v1.10.3 // indirect
github.com/onsi/gomega v1.7.1 // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/rivo/uniseg v0.4.2 // indirect
github.com/rivo/uniseg v0.4.3 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/xanzy/ssh-agent v0.2.1 // indirect
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 // indirect
golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/term v0.2.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/term v0.3.0 // indirect
golang.org/x/text v0.5.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)

20
go.sum
View File

@ -72,8 +72,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
github.com/jesseduffield/gocui v0.3.1-0.20221112081529-154bebde5bb5 h1:gjk12IHGGASij7/kaKEbOJ6jP3lWZzGvDrE9wxiDMoY=
github.com/jesseduffield/gocui v0.3.1-0.20221112081529-154bebde5bb5/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
github.com/jesseduffield/gocui v0.3.1-0.20221203233251-eeb9b3fb2494 h1:SH16ZF/+7S8ZyzDq+sXpOLULBs6lRo6bnYvuRAbhYbk=
github.com/jesseduffield/gocui v0.3.1-0.20221203233251-eeb9b3fb2494/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
@ -137,8 +137,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/samber/lo v1.31.0 h1:Sfa+/064Tdo4SvlohQUQzBhgSer9v/coGvKQI/XLWAM=
@ -200,16 +200,16 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -63,26 +63,22 @@ You can pass the KEY_PRESS_DELAY env var to the test runner in order to set a de
### Running tests in VSCode
If you've opened an integration test file in your editor you can run that file by bringing up the command panel with `cmd+shift+p` and typing 'run task', then selecting the test task you want to run
If you've opened an integration test file in your editor you can run that file by bringing up the command panel with `cmd+shift+p` and typing 'run task', then selecting the test task you want to run
![image](https://user-images.githubusercontent.com/8456633/201500427-b86e129f-5f35-4d55-b7bd-fff5d8e4a04e.png)
![image](https://user-images.githubusercontent.com/8456633/201500431-903deb8c-c210-4054-8514-ab7088c7a839.png)
The test will run in a VSCode terminal:
![image](https://user-images.githubusercontent.com/8456633/201500446-b87abf11-9653-438f-8a9a-e0bf8abdb7ee.png)
### Snapshots
At the moment (this is subject to change) each test has a snapshot repo created after running for the first time. These snapshots live in `test/integration_new`, in folders named 'expected' (alongside the 'actual' folders which contain the resulting repo from the last test run). Whenever you run a test, the resultant repo will be compared against the snapshot repo and if they're different, you'll be asked whether you want to update the snapshot. If you want to update a snapshot without being prompted you can pass MODE=update to the test runner.
### Sandbox mode
Say you want to do a manual test of how lazygit handles merge-conflicts, but you can't be bothered actually finding a way to create merge conflicts in a repo. To make your life easier, you can simply run a merge-conflicts test in sandbox mode, meaning the setup step is run for you, and then instead of the test driving the lazygit session, you're allowed to drive it yourself.
To run a test in sandbox mode you can press 's' on a test in the test TUI or in the test runner pass MODE=sandbox or the --sandbox argument.
To run a test in sandbox mode you can press 's' on a test in the test TUI or in the test runner pass the --sandbox argument.
## Migration process
At the time of writing, most tests are created under an old approach, where you would record yourself in a lazygit session and then the test would replay the keybindings with the same timestamps. This old approach is great for writing tests quickly, but is much harder to maintain. It has to rely entirely on snapshots to determining if a test passes or fails, and can't do assertions along the way. It's also harder to grok what's the intention behind certain actions that take place within the test (e.g. was the recorder intentionally switching to another panel or was that just a misclick?).
At the time of writing, most tests are created under an old approach, where you would record yourself in a lazygit session and then the test would replay the keybindings with the same timestamps. This old approach is great for writing tests quickly, but is much harder to maintain. It has to rely on snapshots to determining if a test passes or fails, and can't do assertions along the way. It's also harder to grok what's the intention behind certain actions that take place within the test (e.g. was the recorder intentionally switching to another panel or was that just a misclick?).
At the moment, all the deprecated test code lives in pkg/integration/deprecated. Hopefully in the very near future we migrate everything across so that we don't need to maintain two systems.
@ -92,6 +88,6 @@ We should never write any new tests under the old method, and if a given test br
go run pkg/integration/deprecated/cmd/tui/main.go
```
The tests in the old format live in test/integration. In the old format, test definitions are co-located with the snapshots. The setup step is done in a `setup.sh` shell script and the `recording.json` file contains the recorded keypresses to be replayed during the test.
The tests in the old format live in test/integration. In the old format, test definitions are co-located with snapshots. The setup step is done in a `setup.sh` shell script and the `recording.json` file contains the recorded keypresses to be replayed during the test.
If you have rewritten an integration test under the new pattern, be sure to delete the old integration test directory.

View File

@ -29,19 +29,12 @@ func RunCLI(testNames []string, slow bool, sandbox bool) {
keyPressDelay = SLOW_KEY_PRESS_DELAY
}
var mode components.Mode
if sandbox {
mode = components.SANDBOX
} else {
mode = getModeFromEnv()
}
err := components.RunTests(
getTestsToRun(testNames),
log.Printf,
runCmdInTerminal,
runAndPrintFatalError,
mode,
sandbox,
keyPressDelay,
1,
)
@ -95,22 +88,6 @@ func runCmdInTerminal(cmd *exec.Cmd) error {
return cmd.Run()
}
func getModeFromEnv() components.Mode {
switch os.Getenv("MODE") {
case "", "ask":
return components.ASK_TO_UPDATE_SNAPSHOT
case "check":
return components.CHECK_SNAPSHOT
case "update":
return components.UPDATE_SNAPSHOT
case "sandbox":
return components.SANDBOX
default:
log.Fatalf("unknown test mode: %s, must be one of [ask, check, update, sandbox]", os.Getenv("MODE"))
panic("unreachable")
}
}
func tryConvert(numStr string, defaultVal int) int {
num, err := strconv.Atoi(numStr)
if err != nil {

View File

@ -46,7 +46,7 @@ func TestIntegration(t *testing.T) {
assert.NoError(t, err)
})
},
components.CHECK_SNAPSHOT,
false,
0,
// allowing two attempts at the test. If a test fails intermittently,
// there may be a concurrency issue that we need to resolve.

View File

@ -82,7 +82,7 @@ func RunTUI() {
return nil
}
suspendAndRunTest(currentTest, components.SANDBOX, 0)
suspendAndRunTest(currentTest, true, 0)
return nil
}); err != nil {
@ -95,7 +95,7 @@ func RunTUI() {
return nil
}
suspendAndRunTest(currentTest, components.ASK_TO_UPDATE_SNAPSHOT, 0)
suspendAndRunTest(currentTest, false, 0)
return nil
}); err != nil {
@ -108,7 +108,7 @@ func RunTUI() {
return nil
}
suspendAndRunTest(currentTest, components.ASK_TO_UPDATE_SNAPSHOT, SLOW_KEY_PRESS_DELAY)
suspendAndRunTest(currentTest, false, SLOW_KEY_PRESS_DELAY)
return nil
}); err != nil {
@ -268,12 +268,12 @@ func (self *app) wrapEditor(f func(v *gocui.View, key gocui.Key, ch rune, mod go
}
}
func suspendAndRunTest(test *components.IntegrationTest, mode components.Mode, keyPressDelay int) {
func suspendAndRunTest(test *components.IntegrationTest, sandbox bool, keyPressDelay int) {
if err := gocui.Screen.Suspend(); err != nil {
panic(err)
}
runTuiTest(test, mode, keyPressDelay)
runTuiTest(test, sandbox, keyPressDelay)
fmt.Fprintf(os.Stdout, "\n%s", style.FgGreen.Sprint("press enter to return"))
fmt.Scanln() // wait for enter press
@ -367,13 +367,13 @@ func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}
func runTuiTest(test *components.IntegrationTest, mode components.Mode, keyPressDelay int) {
func runTuiTest(test *components.IntegrationTest, sandbox bool, keyPressDelay int) {
err := components.RunTests(
[]*components.IntegrationTest{test},
log.Printf,
runCmdInTerminal,
runAndPrintError,
mode,
sandbox,
keyPressDelay,
1,
)

View File

@ -2,6 +2,8 @@ package components
import (
"fmt"
"os"
"regexp"
"strings"
"time"
@ -56,6 +58,16 @@ func NotContains(target string) *matcher {
}}
}
func MatchesRegexp(regexStr string) *matcher {
return &matcher{testFn: func(value string) (bool, string) {
matched, err := regexp.MatchString(regexStr, value)
if err != nil {
return false, fmt.Sprintf("Unexpected error parsing regular expression '%s': %s", regexStr, err.Error())
}
return matched, fmt.Sprintf("Expected '%s' to match regular expression '%s'", value, regexStr)
}}
}
func Equals(target string) *matcher {
return &matcher{testFn: func(value string) (bool, string) {
return target == value, fmt.Sprintf("Expected '%s' to equal '%s'", value, target)
@ -248,3 +260,19 @@ func (self *Assert) assertWithRetries(test func() (bool, string)) {
func (self *Assert) Fail(message string) {
self.gui.Fail(message)
}
// This does _not_ check the files panel, it actually checks the filesystem
func (self *Assert) FileSystemPathPresent(path string) {
self.assertWithRetries(func() (bool, string) {
_, err := os.Stat(path)
return err == nil, fmt.Sprintf("Expected path '%s' to exist, but it does not", path)
})
}
// This does _not_ check the files panel, it actually checks the filesystem
func (self *Assert) FileSystemPathNotPresent(path string) {
self.assertWithRetries(func() (bool, string) {
_, err := os.Stat(path)
return os.IsNotExist(err), fmt.Sprintf("Expected path '%s' to not exist, but it does", path)
})
}

View File

@ -18,29 +18,12 @@ const (
SANDBOX_ENV_VAR = "SANDBOX"
)
type Mode int
const (
// Default: if a snapshot test fails, the we'll be asked whether we want to update it
ASK_TO_UPDATE_SNAPSHOT Mode = iota
// fails the test if the snapshots don't match
CHECK_SNAPSHOT
// runs the test and updates the snapshot
UPDATE_SNAPSHOT
// This just makes use of the setup step of the test to get you into
// a lazygit session. Then you'll be able to do whatever you want. Useful
// when you want to test certain things without needing to manually set
// up the situation yourself.
// fails the test if the snapshots don't match
SANDBOX
)
func RunTests(
tests []*IntegrationTest,
logf func(format string, formatArgs ...interface{}),
runCmd func(cmd *exec.Cmd) error,
testWrapper func(test *IntegrationTest, f func() error),
mode Mode,
sandbox bool,
keyPressDelay int,
maxAttempts int,
) error {
@ -65,7 +48,7 @@ func RunTests(
)
for i := 0; i < maxAttempts; i++ {
err := runTest(test, paths, projectRootDir, logf, runCmd, mode, keyPressDelay)
err := runTest(test, paths, projectRootDir, logf, runCmd, sandbox, keyPressDelay)
if err != nil {
if i == maxAttempts-1 {
return err
@ -89,7 +72,7 @@ func runTest(
projectRootDir string,
logf func(format string, formatArgs ...interface{}),
runCmd func(cmd *exec.Cmd) error,
mode Mode,
sandbox bool,
keyPressDelay int,
) error {
if test.Skip() {
@ -103,7 +86,7 @@ func runTest(
return err
}
cmd, err := getLazygitCommand(test, paths, projectRootDir, mode, keyPressDelay)
cmd, err := getLazygitCommand(test, paths, projectRootDir, sandbox, keyPressDelay)
if err != nil {
return err
}
@ -113,7 +96,7 @@ func runTest(
return err
}
return HandleSnapshots(paths, logf, test, mode)
return nil
}
func prepareTestDir(
@ -151,7 +134,7 @@ func createFixture(test *IntegrationTest, paths Paths) error {
return nil
}
func getLazygitCommand(test *IntegrationTest, paths Paths, rootDir string, mode Mode, keyPressDelay int) (*exec.Cmd, error) {
func getLazygitCommand(test *IntegrationTest, paths Paths, rootDir string, sandbox bool, keyPressDelay int) (*exec.Cmd, error) {
osCommand := oscommands.NewDummyOSCommand()
templateConfigDir := filepath.Join(rootDir, "test", "default_test_config")
@ -170,7 +153,7 @@ func getLazygitCommand(test *IntegrationTest, paths Paths, rootDir string, mode
cmdObj := osCommand.Cmd.New(cmdStr)
cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", TEST_NAME_ENV_VAR, test.Name()))
if mode == SANDBOX {
if sandbox {
cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", "SANDBOX", "true"))
}

View File

@ -1,372 +0,0 @@
package components
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/stretchr/testify/assert"
)
// This creates and compares integration test snapshots.
type (
logf func(format string, formatArgs ...interface{})
)
func HandleSnapshots(paths Paths, logf logf, test *IntegrationTest, mode Mode) error {
return NewSnapshotter(paths, logf, test, mode).
handleSnapshots()
}
type Snapshotter struct {
paths Paths
logf logf
test *IntegrationTest
mode Mode
}
func NewSnapshotter(
paths Paths,
logf logf,
test *IntegrationTest,
mode Mode,
) *Snapshotter {
return &Snapshotter{
paths: paths,
logf: logf,
test: test,
mode: mode,
}
}
func (self *Snapshotter) handleSnapshots() error {
switch self.mode {
case UPDATE_SNAPSHOT:
return self.handleUpdate()
case CHECK_SNAPSHOT:
return self.handleCheck()
case ASK_TO_UPDATE_SNAPSHOT:
return self.handleAskToUpdate()
case SANDBOX:
self.logf("Sandbox session exited")
}
return nil
}
func (self *Snapshotter) handleUpdate() error {
if err := self.updateSnapshot(); err != nil {
return err
}
self.logf("Test passed: %s", self.test.Name())
return nil
}
func (self *Snapshotter) handleCheck() error {
self.logf("Comparing snapshots")
if err := self.compareSnapshots(); err != nil {
return err
}
self.logf("Test passed: %s", self.test.Name())
return nil
}
func (self *Snapshotter) handleAskToUpdate() error {
if _, err := os.Stat(self.paths.Expected()); os.IsNotExist(err) {
if err := self.updateSnapshot(); err != nil {
return err
}
self.logf("No existing snapshot found for %s. Created snapshot.", self.test.Name())
return nil
}
self.logf("Comparing snapshots...")
if err := self.compareSnapshots(); err != nil {
self.logf("%s", err)
// prompt user whether to update the snapshot (Y/N)
if promptUserToUpdateSnapshot() {
if err := self.updateSnapshot(); err != nil {
return err
}
self.logf("Snapshot updated: %s", self.test.Name())
} else {
return err
}
}
self.logf("Test passed: %s", self.test.Name())
return nil
}
func (self *Snapshotter) updateSnapshot() error {
// create/update snapshot
err := oscommands.CopyDir(self.paths.Actual(), self.paths.Expected())
if err != nil {
return err
}
if err := renameSpecialPaths(self.paths.Expected()); err != nil {
return err
}
return nil
}
func (self *Snapshotter) compareSnapshots() error {
// there are a couple of reasons we're not generating the snapshot in expectedDir directly:
// Firstly we don't want to have to revert our .git file back to .git_keep.
// Secondly, the act of calling git commands like 'git status' actually changes the index
// for some reason, and we don't want to leave your lazygit working tree dirty as a result.
expectedDirCopy := filepath.Join(os.TempDir(), "expected_dir_test", self.test.Name())
err := oscommands.CopyDir(self.paths.Expected(), expectedDirCopy)
if err != nil {
return err
}
defer func() {
err := os.RemoveAll(expectedDirCopy)
if err != nil {
panic(err)
}
}()
if err := restoreSpecialPaths(expectedDirCopy); err != nil {
return err
}
err = validateSameRepos(expectedDirCopy, self.paths.Actual())
if err != nil {
return err
}
// iterate through each repo in the expected dir and comparet to the corresponding repo in the actual dir
expectedFiles, err := ioutil.ReadDir(expectedDirCopy)
if err != nil {
return err
}
for _, f := range expectedFiles {
if !f.IsDir() {
return errors.New("unexpected file (as opposed to directory) in integration test 'expected' directory")
}
// get corresponding file name from actual dir
actualRepoPath := filepath.Join(self.paths.Actual(), f.Name())
expectedRepoPath := filepath.Join(expectedDirCopy, f.Name())
actualRepo, expectedRepo, err := generateSnapshots(actualRepoPath, expectedRepoPath)
if err != nil {
return err
}
if expectedRepo != actualRepo {
// get the log file and print it
bytes, err := os.ReadFile(filepath.Join(self.paths.Config(), "development.log"))
if err != nil {
return err
}
self.logf("%s", string(bytes))
return errors.New(getDiff(f.Name(), expectedRepo, actualRepo))
}
}
return nil
}
func promptUserToUpdateSnapshot() bool {
fmt.Println("Test failed. Update snapshot? (y/n)")
var input string
fmt.Scanln(&input)
return input == "y"
}
func generateSnapshots(actualDir string, expectedDir string) (string, string, error) {
actual, err := generateSnapshot(actualDir)
if err != nil {
return "", "", err
}
expected, err := generateSnapshot(expectedDir)
if err != nil {
return "", "", err
}
return actual, expected, nil
}
// note that we don't actually store this snapshot in the lazygit repo.
// Instead we store the whole expected git repo of our test, so that
// we can easily change what we want to compare without needing to regenerate
// snapshots for each test.
func generateSnapshot(dir string) (string, error) {
osCommand := oscommands.NewDummyOSCommand()
_, err := os.Stat(filepath.Join(dir, ".git"))
if err != nil {
return "git directory not found", nil
}
snapshot := ""
cmdStrs := []string{
`remote show -n origin`, // remote branches
// TODO: find a way to bring this back without breaking tests
// `ls-remote origin`,
`status`, // file tree
`log --pretty=%B|%an|%ae -p -1`, // log
`tag -n`, // tags
`stash list`, // stash
`submodule foreach 'git status'`, // submodule status
`submodule foreach 'git log --pretty=%B -p -1'`, // submodule log
`submodule foreach 'git tag -n'`, // submodule tags
`submodule foreach 'git stash list'`, // submodule stash
}
for _, cmdStr := range cmdStrs {
// ignoring error for now. If there's an error it could be that there are no results
output, _ := osCommand.Cmd.New(fmt.Sprintf("git -C %s %s", dir, cmdStr)).RunWithOutput()
snapshot += fmt.Sprintf("git %s:\n%s\n", cmdStr, output)
}
snapshot += "files in repo:\n"
err = filepath.Walk(dir, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if f.IsDir() {
if f.Name() == ".git" {
return filepath.SkipDir
}
return nil
}
bytes, err := os.ReadFile(path)
if err != nil {
return err
}
relativePath, err := filepath.Rel(dir, path)
if err != nil {
return err
}
snapshot += fmt.Sprintf("path: %s\ncontent:\n%s\n", relativePath, string(bytes))
return nil
})
if err != nil {
return "", err
}
return snapshot, nil
}
func getPathsToRename(dir string, needle string, contains string) []string {
pathsToRename := []string{}
err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if f.Name() == needle && (contains == "" || strings.Contains(path, contains)) {
pathsToRename = append(pathsToRename, path)
}
return nil
})
if err != nil {
panic(err)
}
return pathsToRename
}
var specialPathMappings = []struct{ original, new, contains string }{
// git refuses to track .git or .gitmodules in subdirectories so we need to rename them
{".git", ".git_keep", ""},
{".gitmodules", ".gitmodules_keep", ""},
// we also need git to ignore the contents of our test gitignore files so that
// we actually commit files that are ignored within the test.
{".gitignore", "lg_ignore_file", ""},
// this is the .git/info/exclude file. We're being a little more specific here
// so that we don't accidentally mess with some other file named 'exclude' in the test.
{"exclude", "lg_exclude_file", ".git/info/exclude"},
}
func renameSpecialPaths(dir string) error {
for _, specialPath := range specialPathMappings {
for _, path := range getPathsToRename(dir, specialPath.original, specialPath.contains) {
err := os.Rename(path, filepath.Join(filepath.Dir(path), specialPath.new))
if err != nil {
return err
}
}
}
return nil
}
func restoreSpecialPaths(dir string) error {
for _, specialPath := range specialPathMappings {
for _, path := range getPathsToRename(dir, specialPath.new, specialPath.contains) {
err := os.Rename(path, filepath.Join(filepath.Dir(path), specialPath.original))
if err != nil {
return err
}
}
}
return nil
}
// validates that the actual and expected dirs have the same repo names (doesn't actually check the contents of the repos)
func validateSameRepos(expectedDir string, actualDir string) error {
// iterate through each repo in the expected dir and compare to the corresponding repo in the actual dir
expectedFiles, err := ioutil.ReadDir(expectedDir)
if err != nil {
return err
}
var actualFiles []os.FileInfo
actualFiles, err = ioutil.ReadDir(actualDir)
if err != nil {
return err
}
expectedFileNames := slices.Map(expectedFiles, getFileName)
actualFileNames := slices.Map(actualFiles, getFileName)
if !slices.Equal(expectedFileNames, actualFileNames) {
return fmt.Errorf("expected and actual repo dirs do not match: expected: %s, actual: %s", expectedFileNames, actualFileNames)
}
return nil
}
func getFileName(f os.FileInfo) string {
return f.Name()
}
func getDiff(prefix string, expected string, actual string) string {
mockT := &MockTestingT{}
assert.Equal(mockT, expected, actual, fmt.Sprintf("Unexpected %s. Expected:\n%s\nActual:\n%s\n", prefix, expected, actual))
return mockT.err
}
type MockTestingT struct {
err string
}
func (self *MockTestingT) Errorf(format string, args ...interface{}) {
self.err += fmt.Sprintf(format, args...)
}

View File

@ -0,0 +1,34 @@
package commit
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var CommitMultiline = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Commit with a multi-line commit message",
ExtraCmdArgs: "",
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.CreateFile("myfile", "myfile content")
},
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
assert.CommitCount(0)
input.PrimaryAction()
input.PressKeys(keys.Files.CommitChanges)
input.Type("first line")
input.PressKeys(keys.Universal.AppendNewline)
input.PressKeys(keys.Universal.AppendNewline)
input.Type("third line")
input.Confirm()
assert.CommitCount(1)
assert.MatchHeadCommitMessage(Equals("first line"))
input.SwitchToCommitsWindow()
assert.MatchMainViewContent(MatchesRegexp("first line\n\\s*\n\\s*third line"))
},
})

View File

@ -0,0 +1,37 @@
package commit
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var Revert = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Reverts a commit",
ExtraCmdArgs: "",
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.CreateFile("myfile", "myfile content")
shell.GitAddAll()
shell.Commit("first commit")
},
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
assert.CommitCount(1)
input.SwitchToCommitsWindow()
input.PressKeys(keys.Commits.RevertCommit)
assert.InConfirm()
assert.MatchCurrentViewTitle(Equals("Revert commit"))
assert.MatchCurrentViewContent(MatchesRegexp("Are you sure you want to revert \\w+?"))
input.Confirm()
assert.CommitCount(2)
assert.MatchHeadCommitMessage(Contains("Revert \"first commit\""))
input.PreviousItem()
assert.MatchMainViewContent(Contains("-myfile content"))
assert.FileSystemPathNotPresent("myfile")
input.Wait(10)
},
})

View File

@ -36,6 +36,8 @@ var tests = []*components.IntegrationTest{
cherry_pick.CherryPick,
cherry_pick.CherryPickConflicts,
commit.Commit,
commit.CommitMultiline,
commit.Revert,
commit.NewBranch,
commit.Staged,
commit.Unstaged,

View File

@ -1,3 +0,0 @@
first line
third line

View File

@ -1 +0,0 @@
ref: refs/heads/master

View File

@ -1,10 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[user]
email = CI@example.com
name = CI

View File

@ -1 +0,0 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@ -1,7 +0,0 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
.DS_Store

View File

@ -1,5 +0,0 @@
0000000000000000000000000000000000000000 176069f0ded1db43eecb3b629a6077dba6c68295 CI <CI@example.com> 1645602422 +1100 commit (initial): myfile1
176069f0ded1db43eecb3b629a6077dba6c68295 9f1b5440546da24daad7014ccf3e1f4d81f9414b CI <CI@example.com> 1645602422 +1100 commit: myfile2
9f1b5440546da24daad7014ccf3e1f4d81f9414b 3933a268c502712421b7bfa04888319d6f108574 CI <CI@example.com> 1645602422 +1100 commit: myfile3
3933a268c502712421b7bfa04888319d6f108574 37128a3020849daa0847462d14c384cc74c42ae0 CI <CI@example.com> 1645602422 +1100 commit: myfile4
37128a3020849daa0847462d14c384cc74c42ae0 574013716a7f007a27b647b90cdbc78d006d792b CI <CI@example.com> 1645602427 +1100 commit: first line

View File

@ -1,5 +0,0 @@
0000000000000000000000000000000000000000 176069f0ded1db43eecb3b629a6077dba6c68295 CI <CI@example.com> 1645602422 +1100 commit (initial): myfile1
176069f0ded1db43eecb3b629a6077dba6c68295 9f1b5440546da24daad7014ccf3e1f4d81f9414b CI <CI@example.com> 1645602422 +1100 commit: myfile2
9f1b5440546da24daad7014ccf3e1f4d81f9414b 3933a268c502712421b7bfa04888319d6f108574 CI <CI@example.com> 1645602422 +1100 commit: myfile3
3933a268c502712421b7bfa04888319d6f108574 37128a3020849daa0847462d14c384cc74c42ae0 CI <CI@example.com> 1645602422 +1100 commit: myfile4
37128a3020849daa0847462d14c384cc74c42ae0 574013716a7f007a27b647b90cdbc78d006d792b CI <CI@example.com> 1645602427 +1100 commit: first line

View File

@ -1,2 +0,0 @@
x�ÍA
ƒ0@Ñ®sŠÙÊL:Ž))¸ò1™PÁ!")ØÛ×#tûyðS5[Ë¥íª€*©`”¹hÈÌJAr ©ô<ó= —˜:ï⧽ëãÏqzém[õ–ª @Â� gïáJ„èÎzNšþÉ�}˲*¹.Ñ,¹

View File

@ -1,2 +0,0 @@
x�ŽK
Ã0 D»ö)¼/YVbJ)d•c(²B ùáºÐã7Ð t5ÃcŒìëš«Eè.µ¨Zì„=E7%�ÔŒ€¬,Ó0¥I‘ÉûQÍÁE·j}pÙB¤.1Ÿ¨ÅäH|$‘@B§†ßuÞ‹í{~x=½É¾>¬k©i ƒ½:`NzžªúçÜL¹¼ª]ò¦ÆÔ9—ôë_�!?’

View File

@ -1 +0,0 @@
574013716a7f007a27b647b90cdbc78d006d792b

View File

@ -1 +0,0 @@
{"KeyEvents":[{"Timestamp":931,"Mod":0,"Key":256,"Ch":32},{"Timestamp":1467,"Mod":0,"Key":256,"Ch":99},{"Timestamp":2035,"Mod":0,"Key":256,"Ch":102},{"Timestamp":2090,"Mod":0,"Key":256,"Ch":105},{"Timestamp":2162,"Mod":0,"Key":256,"Ch":114},{"Timestamp":2259,"Mod":0,"Key":256,"Ch":115},{"Timestamp":2314,"Mod":0,"Key":256,"Ch":116},{"Timestamp":2411,"Mod":0,"Key":256,"Ch":32},{"Timestamp":2546,"Mod":0,"Key":256,"Ch":108},{"Timestamp":2578,"Mod":0,"Key":256,"Ch":105},{"Timestamp":2627,"Mod":0,"Key":256,"Ch":110},{"Timestamp":2691,"Mod":0,"Key":256,"Ch":101},{"Timestamp":3358,"Mod":4,"Key":13,"Ch":13},{"Timestamp":3577,"Mod":4,"Key":13,"Ch":13},{"Timestamp":3810,"Mod":0,"Key":256,"Ch":116},{"Timestamp":3874,"Mod":0,"Key":256,"Ch":104},{"Timestamp":3914,"Mod":0,"Key":256,"Ch":105},{"Timestamp":3986,"Mod":0,"Key":256,"Ch":114},{"Timestamp":4107,"Mod":0,"Key":256,"Ch":100},{"Timestamp":4195,"Mod":0,"Key":256,"Ch":32},{"Timestamp":4291,"Mod":0,"Key":256,"Ch":108},{"Timestamp":4322,"Mod":0,"Key":256,"Ch":105},{"Timestamp":4370,"Mod":0,"Key":256,"Ch":110},{"Timestamp":4426,"Mod":0,"Key":256,"Ch":101},{"Timestamp":4603,"Mod":0,"Key":13,"Ch":13},{"Timestamp":5267,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]}

View File

@ -1,24 +0,0 @@
#!/bin/sh
set -e
cd $1
git init
git config user.email "CI@example.com"
git config user.name "CI"
echo test1 > myfile1
git add .
git commit -am "myfile1"
echo test2 > myfile2
git add .
git commit -am "myfile2"
echo test3 > myfile3
git add .
git commit -am "myfile3"
echo test4 > myfile4
git add .
git commit -am "myfile4"
echo test5 > myfile5

View File

@ -1,4 +0,0 @@
{
"description": "stage a file and commit the change with a multiline commit message",
"speed": 15
}

View File

@ -1 +0,0 @@
ref: refs/heads/master

View File

@ -1,10 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[user]
email = CI@example.com
name = CI

View File

@ -1 +0,0 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@ -1,7 +0,0 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
.DS_Store

View File

@ -1,5 +0,0 @@
0000000000000000000000000000000000000000 9eaae8f342ca71c060b760870a715a6303905935 CI <CI@example.com> 1643148217 +1100 commit (initial): file0
9eaae8f342ca71c060b760870a715a6303905935 1727eeb6864e52a8967a1a494099359dbdfcc235 CI <CI@example.com> 1643148217 +1100 commit: file1
1727eeb6864e52a8967a1a494099359dbdfcc235 b781ffd3aa940dde39f73c9149b67e2b128de085 CI <CI@example.com> 1643148217 +1100 commit: file2
b781ffd3aa940dde39f73c9149b67e2b128de085 ab15072795e72a2061bc40060494c3ca2138b297 CI <CI@example.com> 1643148219 +1100 revert: Revert "file1"
ab15072795e72a2061bc40060494c3ca2138b297 7e03c9a9538a907c936de5c9a2154707b9ee541c CI <CI@example.com> 1643148227 +1100 commit (amend): revert commit

View File

@ -1,5 +0,0 @@
0000000000000000000000000000000000000000 9eaae8f342ca71c060b760870a715a6303905935 CI <CI@example.com> 1643148217 +1100 commit (initial): file0
9eaae8f342ca71c060b760870a715a6303905935 1727eeb6864e52a8967a1a494099359dbdfcc235 CI <CI@example.com> 1643148217 +1100 commit: file1
1727eeb6864e52a8967a1a494099359dbdfcc235 b781ffd3aa940dde39f73c9149b67e2b128de085 CI <CI@example.com> 1643148217 +1100 commit: file2
b781ffd3aa940dde39f73c9149b67e2b128de085 ab15072795e72a2061bc40060494c3ca2138b297 CI <CI@example.com> 1643148219 +1100 revert: Revert "file1"
ab15072795e72a2061bc40060494c3ca2138b297 7e03c9a9538a907c936de5c9a2154707b9ee541c CI <CI@example.com> 1643148227 +1100 commit (amend): revert commit

View File

@ -1,2 +0,0 @@
x+)JMU03c040031QHËÌI5`°±º²àŸÖ¶wÁ‡Þw.½ùhïTÓ[H
Œ$x~5(í;÷rÕ¢ðªþ–WÚó-Ôö9

View File

@ -1,4 +0,0 @@
x}ŽK
1]ç½$�O'�A„YÍ1òé `œ!DñøèÚmQõxymí6@Iy�tDfëÙ:MÒ„Xl
ŠÈ¡-šŒ"4)c[ìü�œÇZ‹Ž1Y
ëP�ÎMHäX%T¾°ôVÄ縮æ¦y¹ð;¶íΧ¼¶3 ½ï* pD”Rìt?5ø¿®ÜO�_Ü|3ñÂ<

View File

@ -1,2 +0,0 @@
x��K
Т0@]чй ��ќ& "tеcL� [J�o�ріёМВѕО /убI�8�ZЈж�мrЋо@ в" ЁЭ�кљ�їF�"9Ppт�)�ШР.9��ѕЉцкJAыЦk;є4ыћ4?хЫ}_хVЖўа�GQ_�Q'=Ї�ќЉЋЖЌ�ъЁ\9Ј

View File

@ -1,2 +0,0 @@
x+)JMU03c040031QHヒフI5`ーアコイ燹ヨカwチ�w.ス��モ[H
矢y�5�来ミ(桍ァ ^-ンW(x9

View File

@ -1 +0,0 @@
7e03c9a9538a907c936de5c9a2154707b9ee541c

View File

@ -1 +0,0 @@
test0

View File

@ -1 +0,0 @@
test2

View File

@ -1 +0,0 @@
{"KeyEvents":[{"Timestamp":614,"Mod":0,"Key":259,"Ch":0},{"Timestamp":749,"Mod":0,"Key":259,"Ch":0},{"Timestamp":980,"Mod":0,"Key":258,"Ch":0},{"Timestamp":1219,"Mod":0,"Key":256,"Ch":116},{"Timestamp":1854,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2469,"Mod":0,"Key":257,"Ch":0},{"Timestamp":2725,"Mod":0,"Key":257,"Ch":0},{"Timestamp":3892,"Mod":0,"Key":256,"Ch":114},{"Timestamp":5421,"Mod":2,"Key":21,"Ch":21},{"Timestamp":5854,"Mod":2,"Key":21,"Ch":21},{"Timestamp":6197,"Mod":2,"Key":21,"Ch":21},{"Timestamp":6469,"Mod":2,"Key":21,"Ch":21},{"Timestamp":6781,"Mod":2,"Key":21,"Ch":21},{"Timestamp":7078,"Mod":2,"Key":21,"Ch":21},{"Timestamp":7907,"Mod":0,"Key":256,"Ch":114},{"Timestamp":7989,"Mod":0,"Key":256,"Ch":101},{"Timestamp":8148,"Mod":0,"Key":256,"Ch":118},{"Timestamp":8285,"Mod":0,"Key":256,"Ch":101},{"Timestamp":8356,"Mod":0,"Key":256,"Ch":114},{"Timestamp":8493,"Mod":0,"Key":256,"Ch":116},{"Timestamp":8604,"Mod":0,"Key":256,"Ch":32},{"Timestamp":8700,"Mod":0,"Key":256,"Ch":102},{"Timestamp":9125,"Mod":0,"Key":127,"Ch":127},{"Timestamp":9189,"Mod":0,"Key":256,"Ch":99},{"Timestamp":9317,"Mod":0,"Key":256,"Ch":111},{"Timestamp":9509,"Mod":0,"Key":256,"Ch":109},{"Timestamp":9637,"Mod":0,"Key":256,"Ch":109},{"Timestamp":9685,"Mod":0,"Key":256,"Ch":105},{"Timestamp":9781,"Mod":0,"Key":256,"Ch":116},{"Timestamp":9918,"Mod":0,"Key":13,"Ch":13},{"Timestamp":11110,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]}

View File

@ -1,22 +0,0 @@
#!/bin/sh
set -e
cd $1
git init
git config user.email "CI@example.com"
git config user.name "CI"
echo test0 > file0
git add .
git commit -am file0
echo test1 > file1
git add .
git commit -am file1
echo test2 > file2
git add .
git commit -am file2

View File

@ -1 +0,0 @@
{ "description": "Reverting a commit. Note here that our snapshot test fails if the commit SHA is included in the message hence the renaming of the revert commit after creating it", "speed": 20 }

View File

@ -1 +0,0 @@
ref: refs/heads/master

View File

@ -1,12 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[user]
email = CI@example.com
name = CI
[commit]
gpgSign = false

View File

@ -1 +0,0 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@ -1,7 +0,0 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
.DS_Store

View File

@ -1,13 +0,0 @@
0000000000000000000000000000000000000000 18197bb6052becf371aca9ab58d8352cebd3bc29 CI <CI@example.com> 1661160645 +1000 commit (initial): commit 01
18197bb6052becf371aca9ab58d8352cebd3bc29 0ce746de5bee98147a370a19b4568b448fdedfcc CI <CI@example.com> 1661160645 +1000 commit: commit 02
0ce746de5bee98147a370a19b4568b448fdedfcc d4308139592744ccc7fa9ab0931812da9fdfcc1d CI <CI@example.com> 1661160645 +1000 commit: commit 03
d4308139592744ccc7fa9ab0931812da9fdfcc1d 0f77bf7bd7dd91550c927549af82d5b7c6f8a0d7 CI <CI@example.com> 1661160645 +1000 commit: commit 04
0f77bf7bd7dd91550c927549af82d5b7c6f8a0d7 685d0baa299ec29ff2c7a1ca9268abdd374adef2 CI <CI@example.com> 1661160645 +1000 commit: commit 05
685d0baa299ec29ff2c7a1ca9268abdd374adef2 483fcff024ff52df164dddea9ab5032370d14228 CI <CI@example.com> 1661160645 +1000 commit: commit 06
483fcff024ff52df164dddea9ab5032370d14228 f3f9cf9d8f02f35f955b868d277913fc45d724db CI <CI@example.com> 1661160645 +1000 commit: commit 07
f3f9cf9d8f02f35f955b868d277913fc45d724db a83ada2a0a285982aaa96baeddb70135532ed004 CI <CI@example.com> 1661160645 +1000 commit: commit 08
a83ada2a0a285982aaa96baeddb70135532ed004 a89b19d40efb59f1f77b5a6b59ed1a9898545d0d CI <CI@example.com> 1661160645 +1000 commit: commit 09
a89b19d40efb59f1f77b5a6b59ed1a9898545d0d 670ea6605e6780007c543b3d034bcf49c898290d CI <CI@example.com> 1661160645 +1000 commit: commit 10
670ea6605e6780007c543b3d034bcf49c898290d 685d0baa299ec29ff2c7a1ca9268abdd374adef2 CI <CI@example.com> 1661160646 +1000 checkout: moving from master to 685d0baa299ec29ff2c7a1ca9268abdd374adef2
685d0baa299ec29ff2c7a1ca9268abdd374adef2 0f77bf7bd7dd91550c927549af82d5b7c6f8a0d7 CI <CI@example.com> 1661160646 +1000 checkout: moving from 685d0baa299ec29ff2c7a1ca9268abdd374adef2 to 0f77bf7bd7dd91550c927549af82d5b7c6f8a0d7
0f77bf7bd7dd91550c927549af82d5b7c6f8a0d7 670ea6605e6780007c543b3d034bcf49c898290d CI <CI@example.com> 1661160647 +1000 checkout: moving from 0f77bf7bd7dd91550c927549af82d5b7c6f8a0d7 to master

View File

@ -1,10 +0,0 @@
0000000000000000000000000000000000000000 18197bb6052becf371aca9ab58d8352cebd3bc29 CI <CI@example.com> 1661160645 +1000 commit (initial): commit 01
18197bb6052becf371aca9ab58d8352cebd3bc29 0ce746de5bee98147a370a19b4568b448fdedfcc CI <CI@example.com> 1661160645 +1000 commit: commit 02
0ce746de5bee98147a370a19b4568b448fdedfcc d4308139592744ccc7fa9ab0931812da9fdfcc1d CI <CI@example.com> 1661160645 +1000 commit: commit 03
d4308139592744ccc7fa9ab0931812da9fdfcc1d 0f77bf7bd7dd91550c927549af82d5b7c6f8a0d7 CI <CI@example.com> 1661160645 +1000 commit: commit 04
0f77bf7bd7dd91550c927549af82d5b7c6f8a0d7 685d0baa299ec29ff2c7a1ca9268abdd374adef2 CI <CI@example.com> 1661160645 +1000 commit: commit 05
685d0baa299ec29ff2c7a1ca9268abdd374adef2 483fcff024ff52df164dddea9ab5032370d14228 CI <CI@example.com> 1661160645 +1000 commit: commit 06
483fcff024ff52df164dddea9ab5032370d14228 f3f9cf9d8f02f35f955b868d277913fc45d724db CI <CI@example.com> 1661160645 +1000 commit: commit 07
f3f9cf9d8f02f35f955b868d277913fc45d724db a83ada2a0a285982aaa96baeddb70135532ed004 CI <CI@example.com> 1661160645 +1000 commit: commit 08
a83ada2a0a285982aaa96baeddb70135532ed004 a89b19d40efb59f1f77b5a6b59ed1a9898545d0d CI <CI@example.com> 1661160645 +1000 commit: commit 09
a89b19d40efb59f1f77b5a6b59ed1a9898545d0d 670ea6605e6780007c543b3d034bcf49c898290d CI <CI@example.com> 1661160645 +1000 commit: commit 10

View File

@ -1,4 +0,0 @@
x�ЮA
1 @Qз=Eі�$M[[f5ЧHгы C�я,<�лЯ[|]z  cЋи Ой�Г�"saAЋ­Ык{п
�UЖњPc$N>йГsЊzn�$cb�d�ЄV�*#яq_6�fИLѓ­~ЄЏЯzвЅ_�B
��#!Ђйы>5ъ�ќч�љЈы;@

View File

@ -1,2 +0,0 @@
xŤÍM
Â@ @a×sŠěI&?6P¤ĐUŹQKŠ‚ĂHÁăŰEŕöńÁ[j)Ď$Ý©m ĘäWröŚ(ĽŢQVöč˛:™;şjFIó§=ęăý8 ńťËű—Ą–�‘ˇ‰Â™1íuź´ř“�Ň�”+

View File

@ -1,2 +0,0 @@
x+)JMU0¶0`040031QHËÌI50Ô+©(ap¿Þu}QýñÆ��zûëû„ŠìAVeVÅæþÏ»˜éýþM¦;>dHG궘k†¬Ê¬ÊúGÆâÎ ìv…q-ý|�m Ù\-O!«2«út@ªC,Ñ#_¤Îý뾦‰>±< ȪLÁª…ž÷ûþ7,qåµîÂ"ç•O´Y× «2«ï,=vèêŠËù“Íßß[¥Á�´ñ} ²*s°*ƒµ 5>f‹G<ÈܸiƒÖŽpdU`Uûµ™»j"¯Ü6\2©Ò í׿Loô‘UY‚U\MTç?öäúñ‰f?Í�j®9µ
I•¡XU¯§ÐÍS§·eú3Mج8oâ²Ï)gŽI”Ð

Some files were not shown because too many files have changed in this diff Show More