mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-04-21 12:16:54 +02:00
remove snapshot approach for new integration tests
This commit is contained in:
parent
bc4ace8357
commit
e3c6738535
@ -70,19 +70,15 @@ If you've opened an integration test file in your editor you can run that file b
|
|||||||
The test will run in a VSCode terminal:
|
The test will run in a VSCode terminal:
|
||||||

|

|
||||||
|
|
||||||
### 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
|
### 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.
|
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
|
## 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.
|
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
|
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.
|
If you have rewritten an integration test under the new pattern, be sure to delete the old integration test directory.
|
||||||
|
@ -29,19 +29,12 @@ func RunCLI(testNames []string, slow bool, sandbox bool) {
|
|||||||
keyPressDelay = SLOW_KEY_PRESS_DELAY
|
keyPressDelay = SLOW_KEY_PRESS_DELAY
|
||||||
}
|
}
|
||||||
|
|
||||||
var mode components.Mode
|
|
||||||
if sandbox {
|
|
||||||
mode = components.SANDBOX
|
|
||||||
} else {
|
|
||||||
mode = getModeFromEnv()
|
|
||||||
}
|
|
||||||
|
|
||||||
err := components.RunTests(
|
err := components.RunTests(
|
||||||
getTestsToRun(testNames),
|
getTestsToRun(testNames),
|
||||||
log.Printf,
|
log.Printf,
|
||||||
runCmdInTerminal,
|
runCmdInTerminal,
|
||||||
runAndPrintFatalError,
|
runAndPrintFatalError,
|
||||||
mode,
|
sandbox,
|
||||||
keyPressDelay,
|
keyPressDelay,
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
@ -95,22 +88,6 @@ func runCmdInTerminal(cmd *exec.Cmd) error {
|
|||||||
return cmd.Run()
|
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 {
|
func tryConvert(numStr string, defaultVal int) int {
|
||||||
num, err := strconv.Atoi(numStr)
|
num, err := strconv.Atoi(numStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -46,7 +46,7 @@ func TestIntegration(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
components.CHECK_SNAPSHOT,
|
false,
|
||||||
0,
|
0,
|
||||||
// allowing two attempts at the test. If a test fails intermittently,
|
// allowing two attempts at the test. If a test fails intermittently,
|
||||||
// there may be a concurrency issue that we need to resolve.
|
// there may be a concurrency issue that we need to resolve.
|
||||||
|
@ -82,7 +82,7 @@ func RunTUI() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
suspendAndRunTest(currentTest, components.SANDBOX, 0)
|
suspendAndRunTest(currentTest, true, 0)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@ -95,7 +95,7 @@ func RunTUI() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
suspendAndRunTest(currentTest, components.ASK_TO_UPDATE_SNAPSHOT, 0)
|
suspendAndRunTest(currentTest, false, 0)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@ -108,7 +108,7 @@ func RunTUI() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
suspendAndRunTest(currentTest, components.ASK_TO_UPDATE_SNAPSHOT, SLOW_KEY_PRESS_DELAY)
|
suspendAndRunTest(currentTest, false, SLOW_KEY_PRESS_DELAY)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}); err != 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 {
|
if err := gocui.Screen.Suspend(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
runTuiTest(test, mode, keyPressDelay)
|
runTuiTest(test, sandbox, keyPressDelay)
|
||||||
|
|
||||||
fmt.Fprintf(os.Stdout, "\n%s", style.FgGreen.Sprint("press enter to return"))
|
fmt.Fprintf(os.Stdout, "\n%s", style.FgGreen.Sprint("press enter to return"))
|
||||||
fmt.Scanln() // wait for enter press
|
fmt.Scanln() // wait for enter press
|
||||||
@ -367,13 +367,13 @@ func quit(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return gocui.ErrQuit
|
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(
|
err := components.RunTests(
|
||||||
[]*components.IntegrationTest{test},
|
[]*components.IntegrationTest{test},
|
||||||
log.Printf,
|
log.Printf,
|
||||||
runCmdInTerminal,
|
runCmdInTerminal,
|
||||||
runAndPrintError,
|
runAndPrintError,
|
||||||
mode,
|
sandbox,
|
||||||
keyPressDelay,
|
keyPressDelay,
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
@ -18,29 +18,12 @@ const (
|
|||||||
SANDBOX_ENV_VAR = "SANDBOX"
|
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(
|
func RunTests(
|
||||||
tests []*IntegrationTest,
|
tests []*IntegrationTest,
|
||||||
logf func(format string, formatArgs ...interface{}),
|
logf func(format string, formatArgs ...interface{}),
|
||||||
runCmd func(cmd *exec.Cmd) error,
|
runCmd func(cmd *exec.Cmd) error,
|
||||||
testWrapper func(test *IntegrationTest, f func() error),
|
testWrapper func(test *IntegrationTest, f func() error),
|
||||||
mode Mode,
|
sandbox bool,
|
||||||
keyPressDelay int,
|
keyPressDelay int,
|
||||||
maxAttempts int,
|
maxAttempts int,
|
||||||
) error {
|
) error {
|
||||||
@ -65,7 +48,7 @@ func RunTests(
|
|||||||
)
|
)
|
||||||
|
|
||||||
for i := 0; i < maxAttempts; i++ {
|
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 err != nil {
|
||||||
if i == maxAttempts-1 {
|
if i == maxAttempts-1 {
|
||||||
return err
|
return err
|
||||||
@ -89,7 +72,7 @@ func runTest(
|
|||||||
projectRootDir string,
|
projectRootDir string,
|
||||||
logf func(format string, formatArgs ...interface{}),
|
logf func(format string, formatArgs ...interface{}),
|
||||||
runCmd func(cmd *exec.Cmd) error,
|
runCmd func(cmd *exec.Cmd) error,
|
||||||
mode Mode,
|
sandbox bool,
|
||||||
keyPressDelay int,
|
keyPressDelay int,
|
||||||
) error {
|
) error {
|
||||||
if test.Skip() {
|
if test.Skip() {
|
||||||
@ -103,7 +86,7 @@ func runTest(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd, err := getLazygitCommand(test, paths, projectRootDir, mode, keyPressDelay)
|
cmd, err := getLazygitCommand(test, paths, projectRootDir, sandbox, keyPressDelay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -113,7 +96,7 @@ func runTest(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return HandleSnapshots(paths, logf, test, mode)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareTestDir(
|
func prepareTestDir(
|
||||||
@ -151,7 +134,7 @@ func createFixture(test *IntegrationTest, paths Paths) error {
|
|||||||
return nil
|
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()
|
osCommand := oscommands.NewDummyOSCommand()
|
||||||
|
|
||||||
templateConfigDir := filepath.Join(rootDir, "test", "default_test_config")
|
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 := osCommand.Cmd.New(cmdStr)
|
||||||
|
|
||||||
cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", TEST_NAME_ENV_VAR, test.Name()))
|
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"))
|
cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", "SANDBOX", "true"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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...)
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
commit 10
|
|
@ -1 +0,0 @@
|
|||||||
ref: refs/heads/master
|
|
@ -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
|
|
@ -1 +0,0 @@
|
|||||||
Unnamed repository; edit this file 'description' to name the repository.
|
|
Binary file not shown.
@ -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
|
|
@ -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
|
|
@ -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
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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ъ�ќч�љЈы;@
|
|
Binary file not shown.
@ -1,2 +0,0 @@
|
|||||||
xŤÍM
|
|
||||||
Â@@a×sŠěI&?6P¤ĐUŹQKŠ‚ĂHÁăŰEŕöńÁ[j)Ď$Ý©m ĘäWröŚ(ĽŢQVöč˛:™;şjFIó§=ęăý8
ńťËű—Ą–�‘ˇ‰Â™1íuź´ř“�Ň�”+
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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”Ð
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,2 +0,0 @@
|
|||||||
xЌЋK
|
|
||||||
В0@]зіd&яЎ«c’LQ°¶”Я"АЕЫ<ЮвХeћп€гЎoЄP§в]ЦДL-;ьrтѕ0ЧiтЃ„&5«lъм ЩI+(6ОVD8СЦJBr!8«
СyхЫІБ0ВyЇъ–y}и©.у(Fў€С8"љЭоS]яМ= ›d…:А
|
|
@ -1 +0,0 @@
|
|||||||
x+)JMU060a040031QHËĚ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ô4Ŕv5
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +0,0 @@
|
|||||||
# pack-refs with: peeled fully-peeled sorted
|
|
@ -1 +0,0 @@
|
|||||||
670ea6605e6780007c543b3d034bcf49c898290d
|
|
@ -1 +0,0 @@
|
|||||||
file01 content
|
|
@ -1 +0,0 @@
|
|||||||
file02 content
|
|
@ -1 +0,0 @@
|
|||||||
file03 content
|
|
@ -1 +0,0 @@
|
|||||||
file04 content
|
|
@ -1 +0,0 @@
|
|||||||
file05 content
|
|
@ -1 +0,0 @@
|
|||||||
file06 content
|
|
@ -1 +0,0 @@
|
|||||||
file07 content
|
|
@ -1 +0,0 @@
|
|||||||
file08 content
|
|
@ -1 +0,0 @@
|
|||||||
file09 content
|
|
@ -1 +0,0 @@
|
|||||||
file10 content
|
|
@ -1 +0,0 @@
|
|||||||
commit 10
|
|
@ -1 +0,0 @@
|
|||||||
ref: refs/heads/master
|
|
@ -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
|
|
@ -1 +0,0 @@
|
|||||||
Unnamed repository; edit this file 'description' to name the repository.
|
|
Binary file not shown.
@ -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
|
|
@ -1,15 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 5f80f6e6bd2410efe5b5f613adc5b4fcf50e930d CI <CI@example.com> 1661161824 +1000 commit (initial): only commit on master
|
|
||||||
5f80f6e6bd2410efe5b5f613adc5b4fcf50e930d 5f80f6e6bd2410efe5b5f613adc5b4fcf50e930d CI <CI@example.com> 1661161824 +1000 checkout: moving from master to other
|
|
||||||
5f80f6e6bd2410efe5b5f613adc5b4fcf50e930d e739ef9112312917b4fc678e40e2372cc8fda25f CI <CI@example.com> 1661161824 +1000 commit: commit 01
|
|
||||||
e739ef9112312917b4fc678e40e2372cc8fda25f 0d4e099996a7c3dc58120cfe1ec34973b309d0e0 CI <CI@example.com> 1661161824 +1000 commit: commit 02
|
|
||||||
0d4e099996a7c3dc58120cfe1ec34973b309d0e0 3a899150d167edb02f0ef2ab43bf446d23d90310 CI <CI@example.com> 1661161824 +1000 commit: commit 03
|
|
||||||
3a899150d167edb02f0ef2ab43bf446d23d90310 7f8a9a8381ae3fe82a0c43385d3a5bb25cf90699 CI <CI@example.com> 1661161824 +1000 commit: commit 04
|
|
||||||
7f8a9a8381ae3fe82a0c43385d3a5bb25cf90699 d4779d400f252d1de4e76ef79e3b4d554fffadb5 CI <CI@example.com> 1661161824 +1000 commit: commit 05
|
|
||||||
d4779d400f252d1de4e76ef79e3b4d554fffadb5 41bffcc8f25d268518574e91483a55e66124574c CI <CI@example.com> 1661161824 +1000 commit: commit 06
|
|
||||||
41bffcc8f25d268518574e91483a55e66124574c 5c9b8a0a95c49c1674753ad827246ce05965e7fe CI <CI@example.com> 1661161824 +1000 commit: commit 07
|
|
||||||
5c9b8a0a95c49c1674753ad827246ce05965e7fe 9cdece2525ed2b34f940b168a2f628984468a8c6 CI <CI@example.com> 1661161824 +1000 commit: commit 08
|
|
||||||
9cdece2525ed2b34f940b168a2f628984468a8c6 e4fca3fab85b7a477a2de101e917fe43538e0302 CI <CI@example.com> 1661161824 +1000 commit: commit 09
|
|
||||||
e4fca3fab85b7a477a2de101e917fe43538e0302 dff71e43e297a66eb7ec85b7b95ece827751cdbd CI <CI@example.com> 1661161824 +1000 commit: commit 10
|
|
||||||
dff71e43e297a66eb7ec85b7b95ece827751cdbd 5f80f6e6bd2410efe5b5f613adc5b4fcf50e930d CI <CI@example.com> 1661161824 +1000 checkout: moving from other to master
|
|
||||||
5f80f6e6bd2410efe5b5f613adc5b4fcf50e930d 5c9b8a0a95c49c1674753ad827246ce05965e7fe CI <CI@example.com> 1661161824 +1000 checkout: moving from master to 5c9b8a0a95c49c1674753ad827246ce05965e7fe
|
|
||||||
5c9b8a0a95c49c1674753ad827246ce05965e7fe 5f80f6e6bd2410efe5b5f613adc5b4fcf50e930d CI <CI@example.com> 1661161825 +1000 checkout: moving from 5c9b8a0a95c49c1674753ad827246ce05965e7fe to master
|
|
@ -1 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 5f80f6e6bd2410efe5b5f613adc5b4fcf50e930d CI <CI@example.com> 1661161824 +1000 commit (initial): only commit on master
|
|
@ -1,11 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 5f80f6e6bd2410efe5b5f613adc5b4fcf50e930d CI <CI@example.com> 1661161824 +1000 branch: Created from HEAD
|
|
||||||
5f80f6e6bd2410efe5b5f613adc5b4fcf50e930d e739ef9112312917b4fc678e40e2372cc8fda25f CI <CI@example.com> 1661161824 +1000 commit: commit 01
|
|
||||||
e739ef9112312917b4fc678e40e2372cc8fda25f 0d4e099996a7c3dc58120cfe1ec34973b309d0e0 CI <CI@example.com> 1661161824 +1000 commit: commit 02
|
|
||||||
0d4e099996a7c3dc58120cfe1ec34973b309d0e0 3a899150d167edb02f0ef2ab43bf446d23d90310 CI <CI@example.com> 1661161824 +1000 commit: commit 03
|
|
||||||
3a899150d167edb02f0ef2ab43bf446d23d90310 7f8a9a8381ae3fe82a0c43385d3a5bb25cf90699 CI <CI@example.com> 1661161824 +1000 commit: commit 04
|
|
||||||
7f8a9a8381ae3fe82a0c43385d3a5bb25cf90699 d4779d400f252d1de4e76ef79e3b4d554fffadb5 CI <CI@example.com> 1661161824 +1000 commit: commit 05
|
|
||||||
d4779d400f252d1de4e76ef79e3b4d554fffadb5 41bffcc8f25d268518574e91483a55e66124574c CI <CI@example.com> 1661161824 +1000 commit: commit 06
|
|
||||||
41bffcc8f25d268518574e91483a55e66124574c 5c9b8a0a95c49c1674753ad827246ce05965e7fe CI <CI@example.com> 1661161824 +1000 commit: commit 07
|
|
||||||
5c9b8a0a95c49c1674753ad827246ce05965e7fe 9cdece2525ed2b34f940b168a2f628984468a8c6 CI <CI@example.com> 1661161824 +1000 commit: commit 08
|
|
||||||
9cdece2525ed2b34f940b168a2f628984468a8c6 e4fca3fab85b7a477a2de101e917fe43538e0302 CI <CI@example.com> 1661161824 +1000 commit: commit 09
|
|
||||||
e4fca3fab85b7a477a2de101e917fe43538e0302 dff71e43e297a66eb7ec85b7b95ece827751cdbd CI <CI@example.com> 1661161824 +1000 commit: commit 10
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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”Ð
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,6 +0,0 @@
|
|||||||
xŤÎA
|
|
||||||
1@Q×=Eö‚4mÓf@D�ŐŁ¦)
|
|
||||||
Ö†
|
|
||||||
ß.<€ŰĎ[|Y[{tŔ)ú®
|
|
||||||
–'+©rÍ\P�RuA�rĽĄÄ>z_Ľ™Äly×W‡€·ZE¸:*.2!S
|
|
||||||
:a`ź‰4Fta1ůÝďëóçyąę'·í©'YŰp(ŚČ.ŔµfÔ1ŐőOţó`“ůŐ9ň
|
|
@ -1,3 +0,0 @@
|
|||||||
x��A
|
|
||||||
Â0E]ç³d¦C"BW=F’NQèt¤FÐÛðnÿýWMõÞÀ3Ú.TbæÊja))#£T–8s
|
|
||||||
1.E„’˯v³Æ Îãt•wÖÇ*§jzégì=ûŽ]§=ÒäϹ³mýÀOÛ@ó³»îЊ2$
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +0,0 @@
|
|||||||
x+)JMU060a040031QHËĚ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ô4Ŕv5
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +0,0 @@
|
|||||||
# pack-refs with: peeled fully-peeled sorted
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user