Compare commits

...

16 Commits

Author SHA1 Message Date
Jesse Duffield
ae98797bca do not show branch graph when in filtering mode 2022-01-17 19:24:10 +11:00
Jesse Duffield
595aca2a4b make integration test pass 2022-01-17 19:14:59 +11:00
Jesse Duffield
2691477aff allow sandbox mode with integration tests 2022-01-17 19:14:59 +11:00
Jesse Duffield
8ca71eeb36 add git bisect run script 2022-01-17 19:14:59 +11:00
Jesse Duffield
d3a3c8d87d add integration test for merge conflicts resolved externally 2022-01-17 19:14:59 +11:00
Jesse Duffield
ee622d044e add integration test for staging view 2022-01-17 19:14:59 +11:00
Jesse Duffield
99035959a1 fix merge scroll bug 2022-01-16 23:16:05 +11:00
Jesse Duffield
0092c9d08d fix bug with subprocess 2022-01-16 03:32:09 +00:00
Jesse Duffield
befa35645e fix bug which prevented quitting with confirm 2022-01-15 20:35:25 +11:00
Jesse Duffield
7a690f9078 appease CI 2022-01-15 15:34:01 +11:00
Jesse Duffield
dafac52a4c see if this fixes CI linting 2022-01-15 15:34:01 +11:00
Jesse Duffield
1c84f77319 always specify upstream when pushing/pulling 2022-01-15 15:34:01 +11:00
Jesse Duffield
8d8bdb948b avoid deadlock in merge panel 2022-01-15 14:15:41 +11:00
Jesse Duffield
cdcfeb396f stop refreshing the screen so much 2022-01-15 14:15:41 +11:00
Jesse Duffield
f5b9ad8c00 add complex custom command integration test 2022-01-15 10:14:19 +11:00
Jesse Duffield
8263d15b03 fix issue where custom command would not open a menu 2022-01-15 10:14:19 +11:00
208 changed files with 1924 additions and 395 deletions

View File

@@ -100,3 +100,6 @@ jobs:
find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \;
exit 1
fi
- name: errors
run: golangci-lint run
if: ${{ failure() }}

View File

@@ -33,17 +33,32 @@ git commit -am "myfile1"
## Running tests
### From a TUI
You can run/record/sandbox tests via a TUI with the following command:
```
go run test/lazyintegration/main.go
```
This TUI makes much of the following documentation redundant, but feel free to read through anyway!
### From command line
To run all tests - assuming you're at the project root:
```
go test ./pkg/gui/
```
To run them in parallel
```
PARALLEL=true go test ./pkg/gui
```
To run a single test
```
go test ./pkg/gui -run /<test name>
# For example, to run the `tags` test:
@@ -51,29 +66,35 @@ go test ./pkg/gui -run /tags
```
To run a test at a certain speed
```
SPEED=2 go test ./pkg/gui -run /<test name>
```
To update a snapshot
```
UPDATE_SNAPSHOTS=true go test ./pkg/gui -run /<test name>
MODE=updateSnapshot go test ./pkg/gui -run /<test name>
```
## Creating a new test
To create a new test:
1) Copy and paste an existing test directory and rename the new directory to whatever you want the test name to be. Update the test.json file's description to describe your test.
2) Update the `setup.sh` any way you like
3) If you want to have a config folder for just that test, create a `config` directory to contain a `config.yml` and optionally a `state.yml` file. Otherwise, the `test/default_test_config` directory will be used.
4) From the lazygit root directory, run:
1. Copy and paste an existing test directory and rename the new directory to whatever you want the test name to be. Update the test.json file's description to describe your test.
2. Update the `setup.sh` any way you like
3. If you want to have a config folder for just that test, create a `config` directory to contain a `config.yml` and optionally a `state.yml` file. Otherwise, the `test/default_test_config` directory will be used.
4. From the lazygit root directory, run:
```
RECORD_EVENTS=true go test ./pkg/gui -run /<test name>
MODE=record go test ./pkg/gui -run /<test name>
```
5) Feel free to re-attempt recording as many times as you like. In the absence of a proper testing framework, the more deliberate your keypresses, the better!
6) Once satisfied with the recording, stage all the newly created files: `test.json`, `setup.sh`, `recording.json` and the `expected` directory that contains a copy of the repo you created.
5. Feel free to re-attempt recording as many times as you like. In the absence of a proper testing framework, the more deliberate your keypresses, the better!
6. Once satisfied with the recording, stage all the newly created files: `test.json`, `setup.sh`, `recording.json` and the `expected` directory that contains a copy of the repo you created.
The resulting directory will look like:
```
actual/ (the resulting repo after running the test, ignored by git)
expected/ (the 'snapshot' repo)
@@ -85,6 +106,14 @@ recording.json
Feel free to create a hierarchy of directories in the `test/integration` directory to group tests by feature.
## Sandboxing
The integration tests serve a secondary purpose of providing a setup for easy sandboxing. If you want to run a test in sandbox mode (meaning the session won't be recorded and we won't create/update snapshots), go:
```
MODE=sandbox go test ./pkg/gui -run /<test name>
```
## Feedback
If you think this process can be improved, let me know! It shouldn't be too hard to change things.

View File

@@ -136,7 +136,7 @@ func NewGitCommandAux(
Tag: tagCommands,
WorkingTree: workingTreeCommands,
Loaders: Loaders{
Branches: loaders.NewBranchLoader(cmn, branchCommands.GetRawBranches, branchCommands.CurrentBranchName),
Branches: loaders.NewBranchLoader(cmn, branchCommands.GetRawBranches, branchCommands.CurrentBranchName, configCommands),
CommitFiles: loaders.NewCommitFileLoader(cmn, cmd),
Commits: loaders.NewCommitLoader(cmn, cmd, dotGitDir, branchCommands.CurrentBranchName, statusCommands.RebaseMode),
Files: fileLoader,

View File

@@ -108,13 +108,8 @@ func (self *BranchCommands) GetGraphCmdObj(branchName string) oscommands.ICmdObj
return self.cmd.New(utils.ResolvePlaceholderString(branchLogCmdTemplate, templateValues)).DontLog()
}
func (self *BranchCommands) SetCurrentBranchUpstream(upstream string) error {
return self.cmd.New("git branch --set-upstream-to=" + self.cmd.Quote(upstream)).Run()
}
func (self *BranchCommands) GetUpstream(branchName string) (string, error) {
output, err := self.cmd.New(fmt.Sprintf("git rev-parse --abbrev-ref --symbolic-full-name %s@{u}", self.cmd.Quote(branchName))).DontLog().RunWithOutput()
return strings.TrimSpace(output), err
func (self *BranchCommands) SetCurrentBranchUpstream(remoteName string, remoteBranchName string) error {
return self.cmd.New(fmt.Sprintf("git branch --set-upstream-to=%s/%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName))).Run()
}
func (self *BranchCommands) SetUpstream(remoteName string, remoteBranchName string, branchName string) error {

View File

@@ -4,6 +4,7 @@ import (
"regexp"
"strings"
"github.com/jesseduffield/go-git/v5/config"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
@@ -20,27 +21,34 @@ import (
// if we find out we need to use one of these functions in the git.go file, we
// can just pull them out of here and put them there and then call them from in here
type BranchLoaderConfigCommands interface {
Branches() (map[string]*config.Branch, error)
}
// BranchLoader returns a list of Branch objects for the current repo
type BranchLoader struct {
*common.Common
getRawBranches func() (string, error)
getCurrentBranchName func() (string, string, error)
config BranchLoaderConfigCommands
}
func NewBranchLoader(
cmn *common.Common,
getRawBranches func() (string, error),
getCurrentBranchName func() (string, string, error),
config BranchLoaderConfigCommands,
) *BranchLoader {
return &BranchLoader{
Common: cmn,
getRawBranches: getRawBranches,
getCurrentBranchName: getCurrentBranchName,
config: config,
}
}
// Load the list of branches for the current repo
func (self *BranchLoader) Load(reflogCommits []*models.Commit) []*models.Branch {
func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch, error) {
branches := self.obtainBranches()
reflogBranches := self.obtainReflogBranches(reflogCommits)
@@ -77,11 +85,25 @@ outer:
if !foundHead {
currentBranchName, currentBranchDisplayName, err := self.getCurrentBranchName()
if err != nil {
panic(err)
return nil, err
}
branches = append([]*models.Branch{{Name: currentBranchName, DisplayName: currentBranchDisplayName, Head: true, Recency: " *"}}, branches...)
}
return branches
configBranches, err := self.config.Branches()
if err != nil {
return nil, err
}
for _, branch := range branches {
match := configBranches[branch.Name]
if match != nil {
branch.UpstreamRemote = match.Remote
branch.UpstreamBranch = match.Merge.Short()
}
}
return branches, nil
}
func (self *BranchLoader) obtainBranches() []*models.Branch {
@@ -116,12 +138,13 @@ func (self *BranchLoader) obtainBranches() []*models.Branch {
upstreamName := split[2]
if upstreamName == "" {
// if we're here then it means we do not have a local version of the remote.
// The branch might still be tracking a remote though, we just don't know
// how many commits ahead/behind it is
branches = append(branches, branch)
continue
}
branch.UpstreamName = upstreamName
track := split[3]
re := regexp.MustCompile(`ahead (\d+)`)
match := re.FindStringSubmatch(track)

View File

@@ -5,12 +5,16 @@ package models
type Branch struct {
Name string
// the displayname is something like '(HEAD detached at 123asdf)', whereas in that case the name would be '123asdf'
DisplayName string
Recency string
Pushables string
Pullables string
UpstreamName string
Head bool
DisplayName string
Recency string
Pushables string
Pullables string
Head bool
// if we have a named remote locally this will be the name of that remote e.g.
// 'origin' or 'tiwood'. If we don't have the remote locally it'll look like
// 'git@github.com:tiwood/lazygit.git'
UpstreamRemote string
UpstreamBranch string
}
func (b *Branch) RefName() string {
@@ -25,22 +29,26 @@ func (b *Branch) Description() string {
return b.RefName()
}
// this method does not consider the case where the git config states that a branch is tracking the config.
// The Pullables value here is based on whether or not we saw an upstream when doing `git branch`
func (b *Branch) IsTrackingRemote() bool {
return b.IsRealBranch() && b.Pullables != "?"
return b.UpstreamRemote != ""
}
// we know that the remote branch is not stored locally based on our pushable/pullable
// count being question marks.
func (b *Branch) RemoteBranchStoredLocally() bool {
return b.IsTrackingRemote() && b.Pushables != "?" && b.Pullables != "?"
}
func (b *Branch) MatchesUpstream() bool {
return b.IsRealBranch() && b.Pushables == "0" && b.Pullables == "0"
return b.RemoteBranchStoredLocally() && b.Pushables == "0" && b.Pullables == "0"
}
func (b *Branch) HasCommitsToPush() bool {
return b.IsRealBranch() && b.Pushables != "0"
return b.RemoteBranchStoredLocally() && b.Pushables != "0"
}
func (b *Branch) HasCommitsToPull() bool {
return b.IsRealBranch() && b.Pullables != "0"
return b.RemoteBranchStoredLocally() && b.Pullables != "0"
}
// for when we're in a detached head state

View File

@@ -51,6 +51,8 @@ func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
return "", err
}
self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand")
if cmdObj.ShouldLog() {
self.logCmdObj(cmdObj)
}

View File

@@ -4,7 +4,6 @@ import (
"sync"
"time"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@@ -96,11 +95,13 @@ func (gui *Gui) renderAppStatus() {
defer ticker.Stop()
for range ticker.C {
appStatus := gui.statusManager.getStatusString()
gui.OnUIThread(func() error {
return gui.renderString(gui.Views.AppStatus, appStatus)
})
if appStatus == "" {
gui.renderString(gui.Views.AppStatus, "")
return
}
gui.renderString(gui.Views.AppStatus, appStatus)
}
})
}
@@ -117,7 +118,7 @@ func (gui *Gui) WithWaitingStatus(message string, f func() error) error {
gui.renderAppStatus()
if err := f(); err != nil {
gui.g.Update(func(g *gocui.Gui) error {
gui.OnUIThread(func() error {
return gui.surfaceError(err)
})
}

View File

@@ -60,7 +60,12 @@ func (gui *Gui) refreshBranches() {
}
}
gui.State.Branches = gui.Git.Loaders.Branches.Load(reflogCommits)
branches, err := gui.Git.Loaders.Branches.Load(reflogCommits)
if err != nil {
_ = gui.surfaceError(err)
}
gui.State.Branches = branches
if err := gui.postRefreshUpdate(gui.State.Contexts.Branches); err != nil {
gui.Log.Error(err)
@@ -392,25 +397,19 @@ func (gui *Gui) handleFastForward() error {
if !branch.IsTrackingRemote() {
return gui.createErrorPanel(gui.Tr.FwdNoUpstream)
}
if !branch.RemoteBranchStoredLocally() {
return gui.createErrorPanel(gui.Tr.FwdNoLocalUpstream)
}
if branch.HasCommitsToPush() {
return gui.createErrorPanel(gui.Tr.FwdCommitsToPush)
}
upstream, err := gui.Git.Branch.GetUpstream(branch.Name)
if err != nil {
return gui.surfaceError(err)
}
action := gui.Tr.Actions.FastForwardBranch
split := strings.Split(upstream, "/")
remoteName := split[0]
remoteBranchName := strings.Join(split[1:], "/")
message := utils.ResolvePlaceholderString(
gui.Tr.Fetching,
map[string]string{
"from": fmt.Sprintf("%s/%s", remoteName, remoteBranchName),
"from": fmt.Sprintf("%s/%s", branch.UpstreamRemote, branch.UpstreamBranch),
"to": branch.Name,
},
)
@@ -421,7 +420,7 @@ func (gui *Gui) handleFastForward() error {
_ = gui.pullWithLock(PullFilesOptions{action: action, FastForwardOnly: true})
} else {
gui.logAction(action)
err := gui.Git.Sync.FastForward(branch.Name, remoteName, remoteBranchName)
err := gui.Git.Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
gui.handleCredentialsPopup(err)
_ = gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{BRANCHES}})
}

View File

@@ -226,16 +226,12 @@ func (gui *Gui) enterCommitFile(opts OnFocusOpts) error {
if gui.Git.Patch.PatchManager.Active() && gui.Git.Patch.PatchManager.To != gui.State.CommitFileManager.GetParent() {
return gui.ask(askOpts{
title: gui.Tr.DiscardPatch,
prompt: gui.Tr.DiscardPatchConfirm,
handlersManageFocus: true,
title: gui.Tr.DiscardPatch,
prompt: gui.Tr.DiscardPatchConfirm,
handleConfirm: func() error {
gui.Git.Patch.PatchManager.Reset()
return enterTheFile()
},
handleClose: func() error {
return gui.pushContext(gui.State.Contexts.CommitFiles)
},
})
}

View File

@@ -40,8 +40,7 @@ func (gui *Gui) handleCommitMessageFocused() error {
},
)
gui.renderString(gui.Views.Options, message)
return nil
return gui.renderString(gui.Views.Options, message)
}
func (gui *Gui) getBufferLength(view *gocui.View) string {

View File

@@ -36,8 +36,8 @@ type askOpts struct {
type promptOpts struct {
title string
initialContent string
handleConfirm func(string) error
findSuggestionsFunc func(string) []*types.Suggestion
handleConfirm func(string) error
}
func (gui *Gui) ask(opts askOpts) error {
@@ -54,32 +54,32 @@ func (gui *Gui) createLoaderPanel(prompt string) error {
func (gui *Gui) wrappedConfirmationFunction(handlersManageFocus bool, function func() error) func() error {
return func() error {
if function != nil {
if err := function(); err != nil {
return err
}
}
if err := gui.closeConfirmationPrompt(handlersManageFocus); err != nil {
return err
}
if function != nil {
if err := function(); err != nil {
return gui.surfaceError(err)
}
}
return nil
}
}
func (gui *Gui) wrappedPromptConfirmationFunction(handlersManageFocus bool, function func(string) error, getResponse func() string) func() error {
return func() error {
if err := gui.closeConfirmationPrompt(handlersManageFocus); err != nil {
return err
}
if function != nil {
if err := function(getResponse()); err != nil {
return gui.surfaceError(err)
}
}
if err := gui.closeConfirmationPrompt(handlersManageFocus); err != nil {
return err
}
return nil
}
}
@@ -176,45 +176,43 @@ func (gui *Gui) prepareConfirmationPanel(
suggestionsView.Title = fmt.Sprintf(gui.Tr.SuggestionsTitle, gui.UserConfig.Keybinding.Universal.TogglePanel)
}
gui.g.Update(func(g *gocui.Gui) error {
return gui.pushContext(gui.State.Contexts.Confirmation)
})
return nil
}
func (gui *Gui) createPopupPanel(opts createPopupPanelOpts) error {
gui.g.Update(func(g *gocui.Gui) error {
// remove any previous keybindings
gui.clearConfirmationViewKeyBindings()
// remove any previous keybindings
gui.clearConfirmationViewKeyBindings()
err := gui.prepareConfirmationPanel(
opts.title,
opts.prompt,
opts.hasLoader,
opts.findSuggestionsFunc,
opts.editable,
)
if err != nil {
err := gui.prepareConfirmationPanel(
opts.title,
opts.prompt,
opts.hasLoader,
opts.findSuggestionsFunc,
opts.editable,
)
if err != nil {
return err
}
confirmationView := gui.Views.Confirmation
confirmationView.Editable = opts.editable
confirmationView.Editor = gocui.EditorFunc(gui.defaultEditor)
if opts.editable {
textArea := confirmationView.TextArea
textArea.Clear()
textArea.TypeString(opts.prompt)
confirmationView.RenderTextArea()
} else {
if err := gui.renderString(confirmationView, opts.prompt); err != nil {
return err
}
confirmationView := gui.Views.Confirmation
confirmationView.Editable = opts.editable
confirmationView.Editor = gocui.EditorFunc(gui.defaultEditor)
}
if opts.editable {
textArea := confirmationView.TextArea
textArea.Clear()
textArea.TypeString(opts.prompt)
confirmationView.RenderTextArea()
} else {
if err := gui.renderStringSync(confirmationView, opts.prompt); err != nil {
return err
}
}
if err := gui.setKeyBindings(opts); err != nil {
return err
}
return gui.setKeyBindings(opts)
})
return nil
return gui.pushContext(gui.State.Contexts.Confirmation)
}
func (gui *Gui) setKeyBindings(opts createPopupPanelOpts) error {
@@ -226,7 +224,7 @@ func (gui *Gui) setKeyBindings(opts createPopupPanelOpts) error {
},
)
gui.renderString(gui.Views.Options, actions)
_ = gui.renderString(gui.Views.Options, actions)
var onConfirm func() error
if opts.handleConfirmPrompt != nil {
onConfirm = gui.wrappedPromptConfirmationFunction(opts.handlersManageFocus, opts.handleConfirmPrompt, func() string { return gui.Views.Confirmation.TextArea.GetContent() })
@@ -329,5 +327,9 @@ func (gui *Gui) surfaceError(err error) error {
return nil
}
if err == gocui.ErrQuit {
return err
}
return gui.createErrorPanel(err.Error())
}

View File

@@ -71,21 +71,17 @@ func (gui *Gui) currentContextKeyIgnoringPopups() ContextKey {
// use replaceContext when you don't want to return to the original context upon
// hitting escape: you want to go that context's parent instead.
func (gui *Gui) replaceContext(c Context) error {
gui.g.Update(func(*gocui.Gui) error {
gui.State.ContextManager.Lock()
defer gui.State.ContextManager.Unlock()
gui.State.ContextManager.Lock()
defer gui.State.ContextManager.Unlock()
if len(gui.State.ContextManager.ContextStack) == 0 {
gui.State.ContextManager.ContextStack = []Context{c}
} else {
// replace the last item with the given item
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack[0:len(gui.State.ContextManager.ContextStack)-1], c)
}
if len(gui.State.ContextManager.ContextStack) == 0 {
gui.State.ContextManager.ContextStack = []Context{c}
} else {
// replace the last item with the given item
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack[0:len(gui.State.ContextManager.ContextStack)-1], c)
}
return gui.activateContext(c)
})
return nil
return gui.activateContext(c)
}
func (gui *Gui) pushContext(c Context, opts ...OnFocusOpts) error {
@@ -94,14 +90,6 @@ func (gui *Gui) pushContext(c Context, opts ...OnFocusOpts) error {
return errors.New("cannot pass multiple opts to pushContext")
}
gui.g.Update(func(*gocui.Gui) error {
return gui.pushContextDirect(c, opts...)
})
return nil
}
func (gui *Gui) pushContextDirect(c Context, opts ...OnFocusOpts) error {
gui.State.ContextManager.Lock()
// push onto stack
@@ -139,14 +127,6 @@ func (gui *Gui) pushContextWithView(viewName string) error {
}
func (gui *Gui) returnFromContext() error {
gui.g.Update(func(*gocui.Gui) error {
return gui.returnFromContextSync()
})
return nil
}
func (gui *Gui) returnFromContextSync() error {
gui.State.ContextManager.Lock()
if len(gui.State.ContextManager.ContextStack) == 1 {

View File

@@ -3,7 +3,6 @@ package gui
import (
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@@ -13,7 +12,7 @@ type credentials chan string
// promptUserForCredential wait for a username, password or passphrase input from the credentials popup
func (gui *Gui) promptUserForCredential(passOrUname oscommands.CredentialType) string {
gui.credentials = make(chan string)
gui.g.Update(func(g *gocui.Gui) error {
gui.OnUIThread(func() error {
credentialsView := gui.Views.Credentials
switch passOrUname {
case oscommands.Username:
@@ -68,8 +67,7 @@ func (gui *Gui) handleCredentialsViewFocused() error {
},
)
gui.renderString(gui.Views.Options, message)
return nil
return gui.renderString(gui.Views.Options, message)
}
// handleCredentialsPopup handles the views after executing a command that might ask for credentials

View File

@@ -4,10 +4,25 @@ import (
"errors"
)
var CONTEXT_KEYS_SHOWING_DIFFS = []ContextKey{
FILES_CONTEXT_KEY,
COMMIT_FILES_CONTEXT_KEY,
STASH_CONTEXT_KEY,
BRANCH_COMMITS_CONTEXT_KEY,
SUB_COMMITS_CONTEXT_KEY,
MAIN_STAGING_CONTEXT_KEY,
MAIN_PATCH_BUILDING_CONTEXT_KEY,
}
func isShowingDiff(gui *Gui) bool {
key := gui.currentStaticContext().GetKey()
return key == FILES_CONTEXT_KEY || key == COMMIT_FILES_CONTEXT_KEY || key == STASH_CONTEXT_KEY || key == BRANCH_COMMITS_CONTEXT_KEY || key == SUB_COMMITS_CONTEXT_KEY || key == MAIN_STAGING_CONTEXT_KEY || key == MAIN_PATCH_BUILDING_CONTEXT_KEY
for _, contextKey := range CONTEXT_KEYS_SHOWING_DIFFS {
if key == contextKey {
return true
}
}
return false
}
func (gui *Gui) IncreaseContextInDiffView() error {

View File

@@ -27,6 +27,7 @@ func setupGuiForTest(gui *Gui) {
gui.g = &gocui.Gui{}
gui.Views.Main, _ = gui.prepareView("main")
gui.Views.Secondary, _ = gui.prepareView("secondary")
gui.Views.Options, _ = gui.prepareView("options")
gui.Git.Patch.PatchManager = &patch.PatchManager{}
_, _ = gui.refreshLineByLinePanel(diffForTest, "", false, 11)
}
@@ -47,7 +48,7 @@ func TestIncreasesContextInDiffViewByOneInContextWithDiff(t *testing.T) {
context := c(gui)
setupGuiForTest(gui)
gui.UserConfig.Git.DiffContextSize = 1
_ = gui.pushContextDirect(context)
_ = gui.pushContext(context)
_ = gui.IncreaseContextInDiffView()
@@ -64,7 +65,9 @@ func TestDoesntIncreaseContextInDiffViewInContextWithoutDiff(t *testing.T) {
func(gui *Gui) Context { return gui.State.Contexts.ReflogCommits },
func(gui *Gui) Context { return gui.State.Contexts.RemoteBranches },
func(gui *Gui) Context { return gui.State.Contexts.Tags },
func(gui *Gui) Context { return gui.State.Contexts.Merging },
// not testing this because it will kick straight back to the files context
// upon pushing the context
// func(gui *Gui) Context { return gui.State.Contexts.Merging },
func(gui *Gui) Context { return gui.State.Contexts.CommandLog },
}
@@ -73,7 +76,7 @@ func TestDoesntIncreaseContextInDiffViewInContextWithoutDiff(t *testing.T) {
context := c(gui)
setupGuiForTest(gui)
gui.UserConfig.Git.DiffContextSize = 1
_ = gui.pushContextDirect(context)
_ = gui.pushContext(context)
_ = gui.IncreaseContextInDiffView()
@@ -97,7 +100,7 @@ func TestDecreasesContextInDiffViewByOneInContextWithDiff(t *testing.T) {
context := c(gui)
setupGuiForTest(gui)
gui.UserConfig.Git.DiffContextSize = 2
_ = gui.pushContextDirect(context)
_ = gui.pushContext(context)
_ = gui.DecreaseContextInDiffView()
@@ -114,7 +117,9 @@ func TestDoesntDecreaseContextInDiffViewInContextWithoutDiff(t *testing.T) {
func(gui *Gui) Context { return gui.State.Contexts.ReflogCommits },
func(gui *Gui) Context { return gui.State.Contexts.RemoteBranches },
func(gui *Gui) Context { return gui.State.Contexts.Tags },
func(gui *Gui) Context { return gui.State.Contexts.Merging },
// not testing this because it will kick straight back to the files context
// upon pushing the context
// func(gui *Gui) Context { return gui.State.Contexts.Merging },
func(gui *Gui) Context { return gui.State.Contexts.CommandLog },
}
@@ -123,7 +128,7 @@ func TestDoesntDecreaseContextInDiffViewInContextWithoutDiff(t *testing.T) {
context := c(gui)
setupGuiForTest(gui)
gui.UserConfig.Git.DiffContextSize = 2
_ = gui.pushContextDirect(context)
_ = gui.pushContext(context)
_ = gui.DecreaseContextInDiffView()
@@ -135,7 +140,7 @@ func TestDoesntIncreaseContextInDiffViewInContextWhenInPatchBuildingMode(t *test
gui := NewDummyGui()
setupGuiForTest(gui)
gui.UserConfig.Git.DiffContextSize = 2
_ = gui.pushContextDirect(gui.State.Contexts.CommitFiles)
_ = gui.pushContext(gui.State.Contexts.CommitFiles)
gui.Git.Patch.PatchManager.Start("from", "to", false, false)
errorCount := 0
@@ -157,7 +162,7 @@ func TestDoesntDecreaseContextInDiffViewInContextWhenInPatchBuildingMode(t *test
gui := NewDummyGui()
setupGuiForTest(gui)
gui.UserConfig.Git.DiffContextSize = 2
_ = gui.pushContextDirect(gui.State.Contexts.CommitFiles)
_ = gui.pushContext(gui.State.Contexts.CommitFiles)
gui.Git.Patch.PatchManager.Start("from", "to", false, false)
errorCount := 0

View File

@@ -44,8 +44,8 @@ func (gui *Gui) currentDiffTerminals() []string {
branch := gui.getSelectedBranch()
if branch != nil {
names := []string{branch.ID()}
if branch.UpstreamName != "" {
names = append(names, branch.UpstreamName)
if branch.IsTrackingRemote() {
names = append(names, branch.ID()+"@{u}")
}
return names
}

View File

@@ -5,7 +5,6 @@ import (
"regexp"
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
"github.com/jesseduffield/lazygit/pkg/commands/models"
@@ -55,7 +54,7 @@ func (gui *Gui) filesRenderToMain() error {
}
if node.File != nil && node.File.HasInlineMergeConflicts {
return gui.refreshMergePanelWithLock()
return gui.renderConflictsFromFilesPanel()
}
cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.State.IgnoreWhitespaceInDiffView)
@@ -98,7 +97,7 @@ func (gui *Gui) refreshFilesAndSubmodules() error {
return err
}
gui.g.Update(func(g *gocui.Gui) error {
gui.OnUIThread(func() error {
if err := gui.postRefreshUpdate(gui.State.Contexts.Submodules); err != nil {
gui.Log.Error(err)
}
@@ -110,7 +109,7 @@ func (gui *Gui) refreshFilesAndSubmodules() error {
}
}
if gui.currentContext().GetKey() == FILES_CONTEXT_KEY || (g.CurrentView() == gui.Views.Main && ContextKey(g.CurrentView().Context) == MAIN_MERGING_CONTEXT_KEY) {
if gui.currentContext().GetKey() == FILES_CONTEXT_KEY || (gui.g.CurrentView() == gui.Views.Main && ContextKey(gui.g.CurrentView().Context) == MAIN_MERGING_CONTEXT_KEY) {
newSelectedPath := gui.getSelectedPath()
alreadySelected := selectedPath != "" && newSelectedPath == selectedPath
if !alreadySelected {
@@ -183,7 +182,7 @@ func (gui *Gui) enterFile(opts OnFocusOpts) error {
}
if file.HasInlineMergeConflicts {
return gui.handleSwitchToMerge()
return gui.switchToMerge()
}
if file.HasMergeConflicts {
return gui.createErrorPanel(gui.Tr.FileStagingRequirements)
@@ -202,7 +201,7 @@ func (gui *Gui) handleFilePress() error {
file := node.File
if file.HasInlineMergeConflicts {
return gui.handleSwitchToMerge()
return gui.switchToMerge()
}
if file.HasUnstagedChanges {
@@ -407,14 +406,11 @@ func (gui *Gui) handleCommitPress() error {
}
}
gui.g.Update(func(g *gocui.Gui) error {
if err := gui.pushContext(gui.State.Contexts.CommitMessage); err != nil {
return err
}
if err := gui.pushContext(gui.State.Contexts.CommitMessage); err != nil {
return err
}
gui.RenderCommitLength()
return nil
})
gui.RenderCommitLength()
return nil
}
@@ -659,42 +655,40 @@ func (gui *Gui) handlePullFiles() error {
// if we have no upstream branch we need to set that first
if !currentBranch.IsTrackingRemote() {
// see if we have this branch in our config with an upstream
branches, err := gui.Git.Config.Branches()
if err != nil {
return gui.surfaceError(err)
}
for branchName, branch := range branches {
if branchName == currentBranch.Name {
return gui.pullFiles(PullFilesOptions{RemoteName: branch.Remote, BranchName: branch.Name, action: action})
}
}
suggestedRemote := getSuggestedRemote(gui.State.Remotes)
return gui.prompt(promptOpts{
title: gui.Tr.EnterUpstream,
initialContent: suggestedRemote + "/" + currentBranch.Name,
findSuggestionsFunc: gui.getRemoteBranchesSuggestionsFunc("/"),
initialContent: suggestedRemote + " " + currentBranch.Name,
findSuggestionsFunc: gui.getRemoteBranchesSuggestionsFunc(" "),
handleConfirm: func(upstream string) error {
if err := gui.Git.Branch.SetCurrentBranchUpstream(upstream); err != nil {
var upstreamBranch, upstreamRemote string
split := strings.Split(upstream, " ")
if len(split) != 2 {
return gui.createErrorPanel(gui.Tr.InvalidUpstream)
}
upstreamRemote = split[0]
upstreamBranch = split[1]
if err := gui.Git.Branch.SetCurrentBranchUpstream(upstreamRemote, upstreamBranch); err != nil {
errorMessage := err.Error()
if strings.Contains(errorMessage, "does not exist") {
errorMessage = fmt.Sprintf("upstream branch %s not found.\nIf you expect it to exist, you should fetch (with 'f').\nOtherwise, you should push (with 'shift+P')", upstream)
}
return gui.createErrorPanel(errorMessage)
}
return gui.pullFiles(PullFilesOptions{action: action})
return gui.pullFiles(PullFilesOptions{UpstreamRemote: upstreamRemote, UpstreamBranch: upstreamBranch, action: action})
},
})
}
return gui.pullFiles(PullFilesOptions{action: action})
return gui.pullFiles(PullFilesOptions{UpstreamRemote: currentBranch.UpstreamRemote, UpstreamBranch: currentBranch.UpstreamBranch, action: action})
}
type PullFilesOptions struct {
RemoteName string
BranchName string
UpstreamRemote string
UpstreamBranch string
FastForwardOnly bool
action string
}
@@ -718,8 +712,8 @@ func (gui *Gui) pullWithLock(opts PullFilesOptions) error {
err := gui.Git.Sync.Pull(
git_commands.PullOptions{
RemoteName: opts.RemoteName,
BranchName: opts.BranchName,
RemoteName: opts.UpstreamRemote,
BranchName: opts.UpstreamBranch,
FastForwardOnly: opts.FastForwardOnly,
},
)
@@ -786,28 +780,18 @@ func (gui *Gui) pushFiles() error {
}
if currentBranch.IsTrackingRemote() {
opts := pushOpts{
force: false,
upstreamRemote: currentBranch.UpstreamRemote,
upstreamBranch: currentBranch.UpstreamBranch,
}
if currentBranch.HasCommitsToPull() {
return gui.requestToForcePush()
opts.force = true
return gui.requestToForcePush(opts)
} else {
return gui.push(pushOpts{})
return gui.push(opts)
}
} else {
// see if we have an upstream for this branch in our config
upstreamRemote, upstreamBranch, err := gui.upstreamForBranchInConfig(currentBranch.Name)
if err != nil {
return gui.surfaceError(err)
}
if upstreamBranch != "" {
return gui.push(
pushOpts{
force: false,
upstreamRemote: upstreamRemote,
upstreamBranch: upstreamBranch,
},
)
}
suggestedRemote := getSuggestedRemote(gui.State.Remotes)
if gui.Git.Config.GetPushToCurrent() {
@@ -854,7 +838,7 @@ func getSuggestedRemote(remotes []*models.Remote) string {
return remotes[0].Name
}
func (gui *Gui) requestToForcePush() error {
func (gui *Gui) requestToForcePush(opts pushOpts) error {
forcePushDisabled := gui.UserConfig.Git.DisableForcePushing
if forcePushDisabled {
return gui.createErrorPanel(gui.Tr.ForcePushDisabled)
@@ -864,36 +848,17 @@ func (gui *Gui) requestToForcePush() error {
title: gui.Tr.ForcePush,
prompt: gui.Tr.ForcePushPrompt,
handleConfirm: func() error {
return gui.push(pushOpts{force: true})
return gui.push(opts)
},
})
}
func (gui *Gui) upstreamForBranchInConfig(branchName string) (string, string, error) {
branches, err := gui.Git.Config.Branches()
if err != nil {
return "", "", err
}
for configBranchName, configBranch := range branches {
if configBranchName == branchName {
return configBranch.Remote, configBranchName, nil
}
}
return "", "", nil
}
func (gui *Gui) handleSwitchToMerge() error {
func (gui *Gui) switchToMerge() error {
file := gui.getSelectedFile()
if file == nil {
return nil
}
if !file.HasInlineMergeConflicts {
return gui.createErrorPanel(gui.Tr.FileNoMergeCons)
}
return gui.pushContext(gui.State.Contexts.Merging)
}

View File

@@ -645,7 +645,11 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess oscommands.ICmdObj) (bool,
gui.PauseBackgroundThreads = false
return cmdErr == nil, gui.surfaceError(cmdErr)
if cmdErr != nil {
return false, gui.surfaceError(cmdErr)
}
return true, nil
}
func (gui *Gui) runSubprocess(cmdObj oscommands.ICmdObj) error { //nolint:unparam
@@ -768,3 +772,9 @@ func (gui *Gui) setColorScheme() error {
return nil
}
func (gui *Gui) OnUIThread(f func() error) {
gui.g.Update(func(*gocui.Gui) error {
return f()
})
}

View File

@@ -39,8 +39,7 @@ import (
// original playback speed. Speed may be a decimal.
func Test(t *testing.T) {
record := false
updateSnapshots := os.Getenv("UPDATE_SNAPSHOTS") != ""
mode := integration.GetModeFromEnv()
speedEnv := os.Getenv("SPEED")
includeSkipped := os.Getenv("INCLUDE_SKIPPED") != ""
@@ -53,8 +52,7 @@ func Test(t *testing.T) {
assert.NoError(t, err)
})
},
updateSnapshots,
record,
mode,
speedEnv,
func(t *testing.T, expected string, actual string, prefix string) {
assert.Equal(t, expected, actual, fmt.Sprintf("Unexpected %s. Expected:\n%s\nActual:\n%s\n", prefix, expected, actual))

View File

@@ -48,7 +48,7 @@ func (gui *Gui) createAllViews() error {
gui.Views.SearchPrefix.BgColor = gocui.ColorDefault
gui.Views.SearchPrefix.FgColor = gocui.ColorGreen
gui.Views.SearchPrefix.Frame = false
gui.setViewContentSync(gui.Views.SearchPrefix, SEARCH_PREFIX)
gui.setViewContent(gui.Views.SearchPrefix, SEARCH_PREFIX)
gui.Views.Stash.Title = gui.Tr.StashTitle
gui.Views.Stash.FgColor = theme.GocuiDefaultTextColor
@@ -235,7 +235,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
gui.Views.CommitFiles.Visible = gui.getViewNameForWindow(gui.State.Contexts.CommitFiles.GetWindowName()) == "commitFiles"
if gui.State.OldInformation != informationStr {
gui.setViewContentSync(gui.Views.Information, informationStr)
gui.setViewContent(gui.Views.Information, informationStr)
gui.State.OldInformation = informationStr
}

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"github.com/go-errors/errors"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/gui/lbl"
)
@@ -172,15 +171,11 @@ func (gui *Gui) focusSelection(state *LblPanelState) error {
newOrigin := state.CalculateOrigin(origin, bufferHeight)
gui.g.Update(func(*gocui.Gui) error {
if err := stagingView.SetOriginY(newOrigin); err != nil {
return err
}
if err := stagingView.SetOriginY(newOrigin); err != nil {
return err
}
return stagingView.SetCursor(0, selectedLineIdx-newOrigin)
})
return nil
return stagingView.SetCursor(0, selectedLineIdx-newOrigin)
}
func (gui *Gui) handleToggleSelectRange() error {

View File

@@ -229,6 +229,10 @@ func (gui *Gui) subCommitsListContext() IListContext {
}
func (gui *Gui) shouldShowGraph() bool {
if gui.State.Modes.Filtering.Active() {
return false
}
value := gui.UserConfig.Git.Log.ShowGraph
switch value {
case "always":

View File

@@ -39,7 +39,7 @@ func (gui *Gui) getMenuOptions() map[string]string {
}
func (gui *Gui) handleMenuClose() error {
return gui.returnFromContextSync()
return gui.returnFromContext()
}
type createMenuOptions struct {

View File

@@ -90,6 +90,7 @@ func (gui *Gui) handlePickHunk() error {
if err := gui.handleCompleteMerge(); err != nil {
return err
}
return nil
}
return gui.refreshMergePanel()
})
@@ -151,11 +152,19 @@ func (gui *Gui) refreshMergePanelWithLock() error {
return gui.withMergeConflictLock(gui.refreshMergePanel)
}
func (gui *Gui) refreshMergePanel() error {
panelState := gui.State.Panels.Merging
// not re-using state here because we can run into issues with mutexes when
// doing that.
func (gui *Gui) renderConflictsFromFilesPanel() error {
state := mergeconflicts.NewState()
_, err := gui.renderConflicts(state, false)
return err
}
func (gui *Gui) renderConflicts(state *mergeconflicts.State, hasFocus bool) (bool, error) {
cat, err := gui.catSelectedFile()
if err != nil {
return gui.refreshMainViews(refreshMainOpts{
return false, gui.refreshMainViews(refreshMainOpts{
main: &viewUpdateOpts{
title: "",
task: NewRenderStringTask(err.Error()),
@@ -163,20 +172,24 @@ func (gui *Gui) refreshMergePanel() error {
})
}
panelState.SetConflictsFromCat(cat)
state.SetConflictsFromCat(cat)
if panelState.NoConflicts() {
return gui.handleCompleteMerge()
if state.NoConflicts() {
return false, gui.handleCompleteMerge()
}
hasFocus := gui.currentViewName() == "main"
content := mergeconflicts.ColoredConflictFile(cat, panelState.State, hasFocus)
content := mergeconflicts.ColoredConflictFile(cat, state, hasFocus)
if err := gui.scrollToConflict(); err != nil {
return err
if !gui.State.Panels.Merging.UserVerticalScrolling {
// TODO: find a way to not have to do this OnUIThread thing. Why doesn't it work
// without it given that we're calling the 'no scroll' variant below?
gui.OnUIThread(func() error {
gui.centerYPos(gui.Views.Main, state.GetConflictMiddle())
return nil
})
}
return gui.refreshMainViews(refreshMainOpts{
return true, gui.refreshMainViews(refreshMainOpts{
main: &viewUpdateOpts{
title: gui.Tr.MergeConflictsTitle,
task: NewRenderStringWithoutScrollTask(content),
@@ -185,6 +198,19 @@ func (gui *Gui) refreshMergePanel() error {
})
}
func (gui *Gui) refreshMergePanel() error {
conflictsFound, err := gui.renderConflicts(gui.State.Panels.Merging.State, true)
if err != nil {
return err
}
if !conflictsFound {
return gui.handleCompleteMerge()
}
return nil
}
func (gui *Gui) catSelectedFile() (string, error) {
item := gui.getSelectedFile()
if item == nil {
@@ -203,28 +229,11 @@ func (gui *Gui) catSelectedFile() (string, error) {
return cat, nil
}
func (gui *Gui) scrollToConflict() error {
if gui.State.Panels.Merging.UserVerticalScrolling {
return nil
}
panelState := gui.State.Panels.Merging
if panelState.NoConflicts() {
return nil
}
gui.centerYPos(gui.Views.Main, panelState.GetConflictMiddle())
return nil
}
func (gui *Gui) centerYPos(view *gocui.View, y int) {
ox, _ := view.Origin()
_, height := view.Size()
newOriginY := int(math.Max(0, float64(y-(height/2))))
gui.g.Update(func(g *gocui.Gui) error {
return view.SetOrigin(ox, newOriginY)
})
_ = view.SetOrigin(ox, newOriginY)
}
func (gui *Gui) getMergingOptions() map[string]string {
@@ -240,18 +249,11 @@ func (gui *Gui) getMergingOptions() map[string]string {
}
func (gui *Gui) handleEscapeMerge() error {
gui.takeOverMergeConflictScrolling()
gui.State.Panels.Merging.Reset()
if err := gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}}); err != nil {
return err
}
// it's possible this method won't be called from the merging view so we need to
// ensure we only 'return' focus if we already have it
if gui.g.CurrentView() == gui.Views.Main {
return gui.pushContext(gui.State.Contexts.Files)
}
return nil
return gui.escapeMerge()
}
func (gui *Gui) handleCompleteMerge() error {
@@ -261,16 +263,26 @@ func (gui *Gui) handleCompleteMerge() error {
if err := gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}}); err != nil {
return err
}
// if we got conflicts after unstashing, we don't want to call any git
// commands to continue rebasing/merging here
if gui.Git.Status.WorkingTreeState() == enums.REBASE_MODE_NONE {
return gui.handleEscapeMerge()
}
// if there are no more files with merge conflicts, we should ask whether the user wants to continue
if !gui.anyFilesWithMergeConflicts() {
if gui.Git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE && !gui.anyFilesWithMergeConflicts() {
return gui.promptToContinueRebase()
}
return gui.handleEscapeMerge()
return gui.escapeMerge()
}
func (gui *Gui) escapeMerge() error {
gui.takeOverMergeConflictScrolling()
gui.State.Panels.Merging.Reset()
// it's possible this method won't be called from the merging view so we need to
// ensure we only 'return' focus if we already have it
if gui.g.CurrentView() == gui.Views.Main {
return gui.pushContext(gui.State.Contexts.Files)
}
return nil
}
// promptToContinueRebase asks the user if they want to continue the rebase/merge that's in progress

View File

@@ -43,7 +43,13 @@ func getBranchDisplayStrings(b *models.Branch, fullDescription bool, diffed bool
res := []string{recencyColor.Sprint(b.Recency), coloredName}
if fullDescription {
return append(res, style.FgYellow.Sprint(b.UpstreamName))
return append(
res,
fmt.Sprintf("%s %s",
style.FgYellow.Sprint(b.UpstreamRemote),
style.FgYellow.Sprint(b.UpstreamBranch),
),
)
}
return res
}

View File

@@ -4,7 +4,6 @@ import (
"os"
"path/filepath"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/env"
@@ -77,19 +76,15 @@ func (gui *Gui) dispatchSwitchToRepo(path string, reuse bool) error {
}
gui.Git = newGitCommand
gui.g.Update(func(*gocui.Gui) error {
// these two mutexes are used by our background goroutines (triggered via `gui.goEvery`. We don't want to
// switch to a repo while one of these goroutines is in the process of updating something
gui.Mutexes.FetchMutex.Lock()
defer gui.Mutexes.FetchMutex.Unlock()
// these two mutexes are used by our background goroutines (triggered via `gui.goEvery`. We don't want to
// switch to a repo while one of these goroutines is in the process of updating something
gui.Mutexes.FetchMutex.Lock()
defer gui.Mutexes.FetchMutex.Unlock()
gui.Mutexes.RefreshingFilesMutex.Lock()
defer gui.Mutexes.RefreshingFilesMutex.Unlock()
gui.Mutexes.RefreshingFilesMutex.Lock()
defer gui.Mutexes.RefreshingFilesMutex.Unlock()
gui.resetState("", reuse)
return nil
})
gui.resetState("", reuse)
return nil
}

View File

@@ -47,7 +47,7 @@ func (gui *Gui) onSelectItemWrapper(innerFunc func(int) error) func(int, int, in
return func(y int, index int, total int) error {
if total == 0 {
gui.renderString(
return gui.renderString(
gui.Views.Search,
fmt.Sprintf(
"no matches for '%s' %s",
@@ -55,9 +55,8 @@ func (gui *Gui) onSelectItemWrapper(innerFunc func(int) error) func(int, int, in
theme.OptionsFgColor.Sprintf("%s: exit search mode", gui.getKeyDisplay(keybindingConfig.Universal.Return)),
),
)
return nil
}
gui.renderString(
_ = gui.renderString(
gui.Views.Search,
fmt.Sprintf(
"matches for '%s' (%d of %d) %s",

View File

@@ -102,21 +102,13 @@ func (gui *Gui) handleResetSelection() error {
if !gui.UserConfig.Gui.SkipUnstageLineWarning {
return gui.ask(askOpts{
title: gui.Tr.UnstageLinesTitle,
prompt: gui.Tr.UnstageLinesPrompt,
handlersManageFocus: true,
title: gui.Tr.UnstageLinesTitle,
prompt: gui.Tr.UnstageLinesPrompt,
handleConfirm: func() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
if err := gui.pushContext(gui.State.Contexts.Staging); err != nil {
return err
}
return gui.applySelection(true, state)
})
},
handleClose: func() error {
return gui.pushContext(gui.State.Contexts.Staging)
},
})
} else {
return gui.applySelection(true, state)

View File

@@ -68,8 +68,7 @@ func (gui *Gui) newStringTaskWithKey(view *gocui.View, str string, key string) e
manager := gui.getManager(view)
f := func(stop chan struct{}) error {
gui.renderString(view, str)
return nil
return gui.renderString(view, str)
}
if err := manager.NewTask(f, key); err != nil {

View File

@@ -52,10 +52,14 @@ func (gui *Gui) startUpdating(newVersion string) {
func (gui *Gui) onUpdateFinish(statusId int, err error) error {
gui.State.Updating = false
gui.statusManager.removeStatus(statusId)
gui.renderString(gui.Views.AppStatus, "")
if err != nil {
return gui.createErrorPanel("Update failed: " + err.Error())
}
gui.OnUIThread(func() error {
_ = gui.renderString(gui.Views.AppStatus, "")
if err != nil {
return gui.createErrorPanel("Update failed: " + err.Error())
}
return nil
})
return nil
}

View File

@@ -180,7 +180,7 @@ func (gui *Gui) refreshSidePanels(options refreshOptions) error {
}
if options.mode == BLOCK_UI {
gui.g.Update(func(g *gocui.Gui) error {
gui.OnUIThread(func() error {
f()
return nil
})
@@ -201,32 +201,19 @@ func (gui *Gui) cleanString(s string) string {
return utils.NormalizeLinefeeds(output)
}
func (gui *Gui) setViewContentSync(v *gocui.View, s string) {
func (gui *Gui) setViewContent(v *gocui.View, s string) {
v.SetContent(gui.cleanString(s))
}
func (gui *Gui) setViewContent(v *gocui.View, s string) {
gui.g.Update(func(*gocui.Gui) error {
gui.setViewContentSync(v, s)
return nil
})
}
// renderString resets the origin of a view and sets its content
func (gui *Gui) renderString(view *gocui.View, s string) {
gui.g.Update(func(*gocui.Gui) error {
return gui.renderStringSync(view, s)
})
}
func (gui *Gui) renderStringSync(view *gocui.View, s string) error {
func (gui *Gui) renderString(view *gocui.View, s string) error {
if err := view.SetOrigin(0, 0); err != nil {
return err
}
if err := view.SetCursor(0, 0); err != nil {
return err
}
gui.setViewContentSync(view, s)
gui.setViewContent(view, s)
return nil
}
@@ -240,7 +227,7 @@ func (gui *Gui) optionsMapToString(optionsMap map[string]string) string {
}
func (gui *Gui) renderOptionsMap(optionsMap map[string]string) {
gui.renderString(gui.Views.Options, gui.optionsMapToString(optionsMap))
_ = gui.renderString(gui.Views.Options, gui.optionsMapToString(optionsMap))
}
func (gui *Gui) currentViewName() string {
@@ -391,5 +378,5 @@ func getTabbedView(gui *Gui) *gocui.View {
}
func (gui *Gui) render() {
gui.g.Update(func(g *gocui.Gui) error { return nil })
gui.OnUIThread(func() error { return nil })
}

View File

@@ -73,7 +73,6 @@ func chineseTranslationSet() TranslationSet {
PullWait: "拉取中……",
PushWait: "推送中……",
FetchWait: "正在抓取……",
FileNoMergeCons: "该文件没有合并冲突",
LcSoftReset: "软重置",
AlreadyCheckedOutBranch: "您已经检出了这个分支",
SureForceCheckout: "您确定要强制检出吗?您将丢失所有本地更改",

View File

@@ -44,7 +44,6 @@ func dutchTranslationSet() TranslationSet {
PullWait: "Pullen...",
PushWait: "Pushen...",
FetchWait: "Fetchen...",
FileNoMergeCons: "Dit bestand heeft geen merge conflicten",
LcSoftReset: "zacht reset",
AlreadyCheckedOutBranch: "Je hebt deze branch al uitgecheckt",
SureForceCheckout: "Weet je zeker dat je het uitchecken wil forceren? Al je lokale verandering zullen worden verwijdert",

View File

@@ -58,7 +58,6 @@ type TranslationSet struct {
PullWait string
PushWait string
FetchWait string
FileNoMergeCons string
LcSoftReset string
AlreadyCheckedOutBranch string
SureForceCheckout string
@@ -189,6 +188,7 @@ type TranslationSet struct {
ConfirmRebase string
ConfirmMerge string
FwdNoUpstream string
FwdNoLocalUpstream string
FwdCommitsToPush string
ErrorOccurred string
NoRoom string
@@ -282,6 +282,7 @@ type TranslationSet struct {
LcEnterFile string
ExitLineByLineMode string
EnterUpstream string
InvalidUpstream string
ReturnToRemotesList string
LcAddNewRemote string
LcNewRemoteName string
@@ -608,7 +609,6 @@ func EnglishTranslationSet() TranslationSet {
PullWait: "Pulling...",
PushWait: "Pushing...",
FetchWait: "Fetching...",
FileNoMergeCons: "This file has no inline merge conflicts",
LcSoftReset: "soft reset",
AlreadyCheckedOutBranch: "You have already checked out this branch",
SureForceCheckout: "Are you sure you want force checkout? You will lose all local changes",
@@ -740,6 +740,7 @@ func EnglishTranslationSet() TranslationSet {
ConfirmRebase: "Are you sure you want to rebase '{{.checkedOutBranch}}' onto '{{.selectedBranch}}'?",
ConfirmMerge: "Are you sure you want to merge '{{.selectedBranch}}' into '{{.checkedOutBranch}}'?",
FwdNoUpstream: "Cannot fast-forward a branch with no upstream",
FwdNoLocalUpstream: "Cannot fast-forward a branch whose remote is not registered locally",
FwdCommitsToPush: "Cannot fast-forward a branch with commits to push",
ErrorOccurred: "An error occurred! Please create an issue at",
NoRoom: "Not enough room",
@@ -833,6 +834,7 @@ func EnglishTranslationSet() TranslationSet {
LcEnterFile: "enter file to add selected lines to the patch (or toggle directory collapsed)",
ExitLineByLineMode: `exit line-by-line mode`,
EnterUpstream: `Enter upstream as '<remote> <branchname>'`,
InvalidUpstream: "Invalid upstream. Must be in the format '<remote> <branchname>'",
ReturnToRemotesList: `Return to remotes list`,
LcAddNewRemote: `add new remote`,
LcNewRemoteName: `New remote name:`,

View File

@@ -39,7 +39,6 @@ func polishTranslationSet() TranslationSet {
PullWait: "Pobieranie zmian...",
PushWait: "Wysyłanie zmian...",
FetchWait: "Pobieram...",
FileNoMergeCons: "Brak konfliktów scalania w pliku",
AlreadyCheckedOutBranch: "Już przęłączono na tą gałąź",
SureForceCheckout: "Jesteś pewny, że chcesz wymusić przełączenie? Stracisz wszystkie lokalne zmiany",
ForceCheckoutBranch: "Wymuś przełączenie gałęzi",

View File

@@ -24,6 +24,36 @@ type Test struct {
Skip bool `json:"skip"`
}
type Mode int
const (
// default: for when we're just running a test and comparing to the snapshot
TEST = iota
// for when we want to record a test and set the snapshot based on the result
RECORD
// when we just want to use the setup of the test for our own sandboxing purposes.
// This does not record the session and does not create/update snapshots
SANDBOX
// running a test but updating the snapshot
UPDATE_SNAPSHOT
)
func GetModeFromEnv() Mode {
switch os.Getenv("MODE") {
case "record":
return RECORD
case "", "test":
return TEST
case "updateSnapshot":
return UPDATE_SNAPSHOT
case "sandbox":
return SANDBOX
default:
log.Fatalf("unknown test mode: %s, must be one of [test, record, update, sandbox]", os.Getenv("MODE"))
panic("unreachable")
}
}
// this function is used by both `go test` and from our lazyintegration gui, but
// errors need to be handled differently in each (for example go test is always
// working with *testing.T) so we pass in any differences as args here.
@@ -31,8 +61,7 @@ func RunTests(
logf func(format string, formatArgs ...interface{}),
runCmd func(cmd *exec.Cmd) error,
fnWrapper func(test *Test, f func(*testing.T) error),
updateSnapshots bool,
record bool,
mode Mode,
speedEnv string,
onFail func(t *testing.T, expected string, actual string, prefix string),
includeSkipped bool,
@@ -65,7 +94,7 @@ func RunTests(
}
fnWrapper(test, func(t *testing.T) error {
speeds := getTestSpeeds(test.Speed, updateSnapshots, speedEnv)
speeds := getTestSpeeds(test.Speed, mode, speedEnv)
testPath := filepath.Join(testDir, test.Name)
actualRepoDir := filepath.Join(testPath, "actual")
expectedRepoDir := filepath.Join(testPath, "expected")
@@ -73,10 +102,10 @@ func RunTests(
expectedRemoteDir := filepath.Join(testPath, "expected_remote")
logf("path: %s", testPath)
// three retries at normal speed for the sake of flakey tests
speeds = append(speeds, 1)
for i, speed := range speeds {
logf("%s: attempting test at speed %f\n", test.Name, speed)
if mode != SANDBOX && mode != RECORD {
logf("%s: attempting test at speed %f\n", test.Name, speed)
}
findOrCreateDir(testPath)
prepareIntegrationTestDir(actualRepoDir)
@@ -88,7 +117,7 @@ func RunTests(
configDir := filepath.Join(testPath, "used_config")
cmd, err := getLazygitCommand(testPath, rootDir, record, speed, test.ExtraCmdArgs)
cmd, err := getLazygitCommand(testPath, rootDir, mode, speed, test.ExtraCmdArgs)
if err != nil {
return err
}
@@ -98,7 +127,7 @@ func RunTests(
return err
}
if updateSnapshots {
if mode == UPDATE_SNAPSHOT || mode == RECORD {
err = oscommands.CopyDir(actualRepoDir, expectedRepoDir)
if err != nil {
return err
@@ -122,39 +151,41 @@ func RunTests(
}
}
actualRepo, expectedRepo, err := generateSnapshots(actualRepoDir, expectedRepoDir)
if err != nil {
return err
}
actualRemote := "remote folder does not exist"
expectedRemote := "remote folder does not exist"
if folderExists(expectedRemoteDir) {
actualRemote, expectedRemote, err = generateSnapshotsForRemote(actualRemoteDir, expectedRemoteDir)
if mode != SANDBOX {
actualRepo, expectedRepo, err := generateSnapshots(actualRepoDir, expectedRepoDir)
if err != nil {
return err
}
} else if folderExists(actualRemoteDir) {
actualRemote = "remote folder exists"
}
if expectedRepo == actualRepo && expectedRemote == actualRemote {
logf("%s: success at speed %f\n", test.Name, speed)
break
}
// if the snapshots and we haven't tried all playback speeds different we'll retry at a slower speed
if i == len(speeds)-1 {
// get the log file and print that
bytes, err := ioutil.ReadFile(filepath.Join(configDir, "development.log"))
if err != nil {
return err
actualRemote := "remote folder does not exist"
expectedRemote := "remote folder does not exist"
if folderExists(expectedRemoteDir) {
actualRemote, expectedRemote, err = generateSnapshotsForRemote(actualRemoteDir, expectedRemoteDir)
if err != nil {
return err
}
} else if folderExists(actualRemoteDir) {
actualRemote = "remote folder exists"
}
logf("%s", string(bytes))
if expectedRepo != actualRepo {
onFail(t, expectedRepo, actualRepo, "repo")
} else {
onFail(t, expectedRemote, actualRemote, "remote")
if expectedRepo == actualRepo && expectedRemote == actualRemote {
logf("%s: success at speed %f\n", test.Name, speed)
break
}
// if the snapshots and we haven't tried all playback speeds different we'll retry at a slower speed
if i == len(speeds)-1 {
// get the log file and print that
bytes, err := ioutil.ReadFile(filepath.Join(configDir, "development.log"))
if err != nil {
return err
}
logf("%s", string(bytes))
if expectedRepo != actualRepo {
onFail(t, expectedRepo, actualRepo, "repo")
} else {
onFail(t, expectedRemote, actualRemote, "remote")
}
}
}
}
@@ -231,8 +262,8 @@ func tempLazygitPath() string {
return filepath.Join("/tmp", "lazygit", "test_lazygit")
}
func getTestSpeeds(testStartSpeed float64, updateSnapshots bool, speedStr string) []float64 {
if updateSnapshots {
func getTestSpeeds(testStartSpeed float64, mode Mode, speedStr string) []float64 {
if mode != TEST {
// have to go at original speed if updating snapshots in case we go to fast and create a junk snapshot
return []float64{1.0}
}
@@ -254,7 +285,7 @@ func getTestSpeeds(testStartSpeed float64, updateSnapshots bool, speedStr string
if startSpeed > 5 {
speeds = append(speeds, 5)
}
speeds = append(speeds, 1)
speeds = append(speeds, 1, 1)
return speeds
}
@@ -400,7 +431,7 @@ func generateSnapshotsForRemote(actualDir string, expectedDir string) (string, s
return actual, expected, nil
}
func getLazygitCommand(testPath string, rootDir string, record bool, speed float64, extraCmdArgs string) (*exec.Cmd, error) {
func getLazygitCommand(testPath string, rootDir string, mode Mode, speed float64, extraCmdArgs string) (*exec.Cmd, error) {
osCommand := oscommands.NewDummyOSCommand()
replayPath := filepath.Join(testPath, "recording.json")
@@ -432,9 +463,10 @@ func getLazygitCommand(testPath string, rootDir string, record bool, speed float
cmdObj := osCommand.Cmd.New(cmdStr)
cmdObj.AddEnvVars(fmt.Sprintf("SPEED=%f", speed))
if record {
switch mode {
case RECORD:
cmdObj.AddEnvVars(fmt.Sprintf("RECORD_EVENTS_TO=%s", replayPath))
} else {
case TEST, UPDATE_SNAPSHOT:
cmdObj.AddEnvVars(fmt.Sprintf("REPLAY_EVENTS_FROM=%s", replayPath))
}

20
scripts/bisect.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/bin/sh
# How to use:
# 1) find a commit that is working fine.
# 2) Create an integration test capturing the fact that it works (Don't commit it). See https://github.com/jesseduffield/lazygit/blob/master/docs/Integration_Tests.md
# 3) checkout the commit that's known to be failing
# 4) run this script supplying the commit sha / tag name that works and the name of the newly created test
# usage: scripts/bisect.sh <ref that works> <integration test name>
# e.g. scripts/bisect.sh v0.32.1 mergeConflictsResolvedExternally
# It's assumed that the current commit (i.e. HEAD) is broken.
set -o pipefail
echo $1
echo $2
git bisect start HEAD $1
git bisect run go test ./pkg/gui -run /$2
git bisect reset

View File

@@ -0,0 +1,2 @@
disableStartupPopups: true
confirmOnQuit: true

View File

@@ -0,0 +1 @@
myfile1

View File

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

View File

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

View File

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

Binary file not shown.

View File

@@ -0,0 +1,7 @@
# 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

@@ -0,0 +1 @@
0000000000000000000000000000000000000000 544efed4e669ec3bd64b44799175bffac95035f5 CI <CI@example.com> 1642239015 +1100 commit (initial): myfile1

View File

@@ -0,0 +1 @@
0000000000000000000000000000000000000000 544efed4e669ec3bd64b44799175bffac95035f5 CI <CI@example.com> 1642239015 +1100 commit (initial): myfile1

View File

@@ -0,0 +1 @@
544efed4e669ec3bd64b44799175bffac95035f5

View File

@@ -0,0 +1 @@
test1

View File

@@ -0,0 +1 @@
{"KeyEvents":[{"Timestamp":466,"Mod":0,"Key":256,"Ch":113},{"Timestamp":890,"Mod":0,"Key":13,"Ch":13}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]}

View File

@@ -0,0 +1,12 @@
#!/bin/sh
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"

View File

@@ -0,0 +1,4 @@
{
"description": "quit with a confirm",
"speed": 20
}

View File

@@ -0,0 +1,31 @@
disableStartupPopups: true
customCommands:
- key: 'N'
description: 'Add file'
context: 'localBranches'
command: 'echo "{{index .PromptResponses 0}} {{index .PromptResponses 1}} {{index .PromptResponses 2}} {{ .SelectedLocalBranch.Name }}" > output.txt'
loadingText: 'Running custom command...'
prompts:
- type: 'menuFromCommand'
title: 'Title'
command: 'git log --oneline --pretty=%B'
filter: '(?P<commit_message>.*)'
valueFormat: '{{ .commit_message }}'
labelFormat: '{{ .commit_message | yellow }}'
- type: 'input'
title: 'Description'
initialValue: "{{ if .SelectedLocalBranch.Name }}Branch: #{{ .SelectedLocalBranch.Name }}{{end}}"
- type: 'menu'
title: 'yes or no'
options:
- name: 'no'
value: 'false'
- name: 'yes'
value: 'true'
gui:
theme:
activeBorderColor:
- green
- bold
SelectedRangeBgcolor:
- reverse

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
# 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

@@ -0,0 +1,5 @@
0000000000000000000000000000000000000000 ab38b1ca116f77648925d952e731f419db360cdb CI <CI@example.com> 1642201096 +1100 commit (initial): myfile1
ab38b1ca116f77648925d952e731f419db360cdb 4fdfedfd9d406506be8b02f5b863dbc08d43cc9f CI <CI@example.com> 1642201096 +1100 commit: myfile2
4fdfedfd9d406506be8b02f5b863dbc08d43cc9f 7dd93a4be3d27d40fbe791d6d77e0d2fedc4d785 CI <CI@example.com> 1642201096 +1100 commit: myfile3
7dd93a4be3d27d40fbe791d6d77e0d2fedc4d785 f708d3e3819470a69f6c8562ff1e68eef02f8cac CI <CI@example.com> 1642201096 +1100 commit: myfile4
f708d3e3819470a69f6c8562ff1e68eef02f8cac 5428838691c97ac192c8b8e1c3f573d8541a94b6 CI <CI@example.com> 1642201104 +1100 commit: test

View File

@@ -0,0 +1,5 @@
0000000000000000000000000000000000000000 ab38b1ca116f77648925d952e731f419db360cdb CI <CI@example.com> 1642201096 +1100 commit (initial): myfile1
ab38b1ca116f77648925d952e731f419db360cdb 4fdfedfd9d406506be8b02f5b863dbc08d43cc9f CI <CI@example.com> 1642201096 +1100 commit: myfile2
4fdfedfd9d406506be8b02f5b863dbc08d43cc9f 7dd93a4be3d27d40fbe791d6d77e0d2fedc4d785 CI <CI@example.com> 1642201096 +1100 commit: myfile3
7dd93a4be3d27d40fbe791d6d77e0d2fedc4d785 f708d3e3819470a69f6c8562ff1e68eef02f8cac CI <CI@example.com> 1642201096 +1100 commit: myfile4
f708d3e3819470a69f6c8562ff1e68eef02f8cac 5428838691c97ac192c8b8e1c3f573d8541a94b6 CI <CI@example.com> 1642201104 +1100 commit: test

View File

@@ -0,0 +1,2 @@
x<01>ÍA
ƒ0@Ñ®sŠÙÊL:#)¸ò1™PÁ!")ØÛ×#tûyðS5[Ë¥íª€*©`”¹ë5df¥ 9<><39>T:žùž…KLïâ§½ëãÏqzém[õ–ª

View File

@@ -0,0 +1,2 @@
x<01>ЮA
Т0@Qз9Eі<45>Ь$<24>I"BW=F<><46>`СиR"шээм~от<D0BE>ЕЕЅ[LtъЛЊu<D08A>1<03><>ЬaШ>АFvфCЩ!ЁйђЎЏnЃHђ<48>fѕтЂдYcBa<42>QA\U)$q&Пћcнэ8йы8нѕ<D0BD>лідKYлЭ"<22>s<EFBFBD><73>и<EFBFBD>ЬQ<D0AC>ЉЎrгОuy*<2A>3н9<D0BD>

View File

@@ -0,0 +1 @@
5428838691c97ac192c8b8e1c3f573d8541a94b6

View File

@@ -0,0 +1 @@
test1

View File

@@ -0,0 +1 @@
test2

View File

@@ -0,0 +1 @@
test3

View File

@@ -0,0 +1 @@
test4

View File

@@ -0,0 +1 @@
myfile2 Branch: #master haha true master

View File

@@ -0,0 +1 @@
{"KeyEvents":[{"Timestamp":623,"Mod":0,"Key":259,"Ch":0},{"Timestamp":1369,"Mod":0,"Key":256,"Ch":78},{"Timestamp":1904,"Mod":0,"Key":258,"Ch":0},{"Timestamp":2033,"Mod":0,"Key":258,"Ch":0},{"Timestamp":2328,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2848,"Mod":0,"Key":256,"Ch":32},{"Timestamp":3296,"Mod":0,"Key":256,"Ch":97},{"Timestamp":3616,"Mod":0,"Key":127,"Ch":127},{"Timestamp":3824,"Mod":0,"Key":256,"Ch":104},{"Timestamp":3879,"Mod":0,"Key":256,"Ch":97},{"Timestamp":3927,"Mod":0,"Key":256,"Ch":104},{"Timestamp":4000,"Mod":0,"Key":256,"Ch":97},{"Timestamp":4239,"Mod":0,"Key":13,"Ch":13},{"Timestamp":4809,"Mod":0,"Key":258,"Ch":0},{"Timestamp":5024,"Mod":0,"Key":13,"Ch":13},{"Timestamp":5824,"Mod":0,"Key":260,"Ch":0},{"Timestamp":6079,"Mod":0,"Key":256,"Ch":32},{"Timestamp":6376,"Mod":0,"Key":256,"Ch":99},{"Timestamp":6591,"Mod":0,"Key":256,"Ch":116},{"Timestamp":6640,"Mod":0,"Key":256,"Ch":101},{"Timestamp":6816,"Mod":0,"Key":256,"Ch":115},{"Timestamp":6856,"Mod":0,"Key":256,"Ch":116},{"Timestamp":7136,"Mod":0,"Key":13,"Ch":13},{"Timestamp":7487,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":36}]}

View File

@@ -0,0 +1,21 @@
#!/bin/sh
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"

View File

@@ -0,0 +1,4 @@
{
"description": "Invoke a custom command that creates a file, and then stage and commit that file. In this case we're using a more customised flow",
"speed": 5
}

View File

@@ -0,0 +1,6 @@
disableStartupPopups: true
customCommands:
- key: 'N'
description: 'Resolve conflict'
context: 'files'
command: 'echo "master2" > file'

View File

@@ -0,0 +1,20 @@
Merge branch 'master' into other
# Conflicts:
# file
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
# /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/mergeConflictsResolvedExternally/actual/.git/MERGE_HEAD
# and try again.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch other
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
# modified: file
#

View File

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

View File

@@ -0,0 +1 @@
769c8b8d89700f6f196b8331159150746a839662

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
# 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

@@ -0,0 +1,7 @@
0000000000000000000000000000000000000000 03959db9f70fcb4a8f0931e4ad64e1c9ec1016a4 CI <CI@example.com> 1642403134 +1100 commit (initial): master1
03959db9f70fcb4a8f0931e4ad64e1c9ec1016a4 03959db9f70fcb4a8f0931e4ad64e1c9ec1016a4 CI <CI@example.com> 1642403134 +1100 checkout: moving from master to other
03959db9f70fcb4a8f0931e4ad64e1c9ec1016a4 769c8b8d89700f6f196b8331159150746a839662 CI <CI@example.com> 1642403134 +1100 commit: other1
769c8b8d89700f6f196b8331159150746a839662 03959db9f70fcb4a8f0931e4ad64e1c9ec1016a4 CI <CI@example.com> 1642403134 +1100 checkout: moving from other to master
03959db9f70fcb4a8f0931e4ad64e1c9ec1016a4 691ee9e9d9c654c81214f56c514ff725f46cb9e4 CI <CI@example.com> 1642403134 +1100 commit: master2
691ee9e9d9c654c81214f56c514ff725f46cb9e4 769c8b8d89700f6f196b8331159150746a839662 CI <CI@example.com> 1642403134 +1100 checkout: moving from master to other
769c8b8d89700f6f196b8331159150746a839662 f2df244fb87b6ba1d2ab484d76c66baba168a867 CI <CI@example.com> 1642403138 +1100 commit (merge): Merge branch 'master' into other

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