Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae98797bca | ||
|
|
595aca2a4b | ||
|
|
2691477aff | ||
|
|
8ca71eeb36 | ||
|
|
d3a3c8d87d | ||
|
|
ee622d044e | ||
|
|
99035959a1 | ||
|
|
0092c9d08d | ||
|
|
befa35645e | ||
|
|
7a690f9078 | ||
|
|
dafac52a4c | ||
|
|
1c84f77319 | ||
|
|
8d8bdb948b | ||
|
|
cdcfeb396f | ||
|
|
f5b9ad8c00 | ||
|
|
8263d15b03 |
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -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() }}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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}})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
|
||||
@@ -73,7 +73,6 @@ func chineseTranslationSet() TranslationSet {
|
||||
PullWait: "拉取中……",
|
||||
PushWait: "推送中……",
|
||||
FetchWait: "正在抓取……",
|
||||
FileNoMergeCons: "该文件没有合并冲突",
|
||||
LcSoftReset: "软重置",
|
||||
AlreadyCheckedOutBranch: "您已经检出了这个分支",
|
||||
SureForceCheckout: "您确定要强制检出吗?您将丢失所有本地更改",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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:`,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
20
scripts/bisect.sh
Executable 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
|
||||
2
test/integration/confirmQuit/config/config.yml
Normal file
2
test/integration/confirmQuit/config/config.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
disableStartupPopups: true
|
||||
confirmOnQuit: true
|
||||
@@ -0,0 +1 @@
|
||||
myfile1
|
||||
1
test/integration/confirmQuit/expected/.git_keep/HEAD
Normal file
1
test/integration/confirmQuit/expected/.git_keep/HEAD
Normal file
@@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
||||
10
test/integration/confirmQuit/expected/.git_keep/config
Normal file
10
test/integration/confirmQuit/expected/.git_keep/config
Normal 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
|
||||
@@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
||||
BIN
test/integration/confirmQuit/expected/.git_keep/index
Normal file
BIN
test/integration/confirmQuit/expected/.git_keep/index
Normal file
Binary file not shown.
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
0000000000000000000000000000000000000000 544efed4e669ec3bd64b44799175bffac95035f5 CI <CI@example.com> 1642239015 +1100 commit (initial): myfile1
|
||||
@@ -0,0 +1 @@
|
||||
0000000000000000000000000000000000000000 544efed4e669ec3bd64b44799175bffac95035f5 CI <CI@example.com> 1642239015 +1100 commit (initial): myfile1
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
544efed4e669ec3bd64b44799175bffac95035f5
|
||||
1
test/integration/confirmQuit/expected/myfile1
Normal file
1
test/integration/confirmQuit/expected/myfile1
Normal file
@@ -0,0 +1 @@
|
||||
test1
|
||||
1
test/integration/confirmQuit/recording.json
Normal file
1
test/integration/confirmQuit/recording.json
Normal 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}]}
|
||||
12
test/integration/confirmQuit/setup.sh
Normal file
12
test/integration/confirmQuit/setup.sh
Normal 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"
|
||||
4
test/integration/confirmQuit/test.json
Normal file
4
test/integration/confirmQuit/test.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "quit with a confirm",
|
||||
"speed": 20
|
||||
}
|
||||
31
test/integration/customCommandsComplex/config/config.yml
Normal file
31
test/integration/customCommandsComplex/config/config.yml
Normal 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
|
||||
@@ -0,0 +1 @@
|
||||
test
|
||||
@@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
||||
@@ -0,0 +1,10 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = false
|
||||
logallrefupdates = true
|
||||
ignorecase = true
|
||||
precomposeunicode = true
|
||||
[user]
|
||||
email = CI@example.com
|
||||
name = CI
|
||||
@@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
||||
BIN
test/integration/customCommandsComplex/expected/.git_keep/index
Normal file
BIN
test/integration/customCommandsComplex/expected/.git_keep/index
Normal file
Binary file not shown.
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
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.
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
x<01>ÍA
|
||||
ƒ0@Ñ®sŠÙÊL:#)¸ò1™PÁ!")ØÛ×#tûyðS5[Ë¥íª€*©`”¹ë5df¥ 9<><39>T:žùž…KLïâ§½ëãÏqzém[õ–ª
|
||||
Binary file not shown.
Binary file not shown.
@@ -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>
|
||||
@@ -0,0 +1 @@
|
||||
5428838691c97ac192c8b8e1c3f573d8541a94b6
|
||||
1
test/integration/customCommandsComplex/expected/myfile1
Normal file
1
test/integration/customCommandsComplex/expected/myfile1
Normal file
@@ -0,0 +1 @@
|
||||
test1
|
||||
1
test/integration/customCommandsComplex/expected/myfile2
Normal file
1
test/integration/customCommandsComplex/expected/myfile2
Normal file
@@ -0,0 +1 @@
|
||||
test2
|
||||
1
test/integration/customCommandsComplex/expected/myfile3
Normal file
1
test/integration/customCommandsComplex/expected/myfile3
Normal file
@@ -0,0 +1 @@
|
||||
test3
|
||||
1
test/integration/customCommandsComplex/expected/myfile4
Normal file
1
test/integration/customCommandsComplex/expected/myfile4
Normal file
@@ -0,0 +1 @@
|
||||
test4
|
||||
@@ -0,0 +1 @@
|
||||
myfile2 Branch: #master haha true master
|
||||
1
test/integration/customCommandsComplex/recording.json
Normal file
1
test/integration/customCommandsComplex/recording.json
Normal 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}]}
|
||||
21
test/integration/customCommandsComplex/setup.sh
Normal file
21
test/integration/customCommandsComplex/setup.sh
Normal 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"
|
||||
4
test/integration/customCommandsComplex/test.json
Normal file
4
test/integration/customCommandsComplex/test.json
Normal 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
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
disableStartupPopups: true
|
||||
customCommands:
|
||||
- key: 'N'
|
||||
description: 'Resolve conflict'
|
||||
context: 'files'
|
||||
command: 'echo "master2" > 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
|
||||
#
|
||||
@@ -0,0 +1 @@
|
||||
ref: refs/heads/other
|
||||
@@ -0,0 +1 @@
|
||||
769c8b8d89700f6f196b8331159150746a839662
|
||||
@@ -0,0 +1,10 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = false
|
||||
logallrefupdates = true
|
||||
ignorecase = true
|
||||
precomposeunicode = true
|
||||
[user]
|
||||
email = CI@example.com
|
||||
name = CI
|
||||
@@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
||||
Binary file not shown.
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user