mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-07-13 01:30:53 +02:00
add integration test for staging view
This commit is contained in:
@ -0,0 +1 @@
|
||||
test
|
1
test/integration/staging/expected/.git_keep/HEAD
Normal file
1
test/integration/staging/expected/.git_keep/HEAD
Normal file
@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
10
test/integration/staging/expected/.git_keep/config
Normal file
10
test/integration/staging/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
|
1
test/integration/staging/expected/.git_keep/description
Normal file
1
test/integration/staging/expected/.git_keep/description
Normal file
@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
BIN
test/integration/staging/expected/.git_keep/index
Normal file
BIN
test/integration/staging/expected/.git_keep/index
Normal file
Binary file not shown.
7
test/integration/staging/expected/.git_keep/info/exclude
Normal file
7
test/integration/staging/expected/.git_keep/info/exclude
Normal 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
|
2
test/integration/staging/expected/.git_keep/logs/HEAD
Normal file
2
test/integration/staging/expected/.git_keep/logs/HEAD
Normal file
@ -0,0 +1,2 @@
|
||||
0000000000000000000000000000000000000000 6806c569c7c063ec7ed3d16da3faa32a1622bb4b CI <CI@example.com> 1642402468 +1100 commit (initial): file1
|
||||
6806c569c7c063ec7ed3d16da3faa32a1622bb4b 4253d2597aec2a480a8e9054250e6b4aa5b76d9e CI <CI@example.com> 1642402495 +1100 commit: test
|
@ -0,0 +1,2 @@
|
||||
0000000000000000000000000000000000000000 6806c569c7c063ec7ed3d16da3faa32a1622bb4b CI <CI@example.com> 1642402468 +1100 commit (initial): file1
|
||||
6806c569c7c063ec7ed3d16da3faa32a1622bb4b 4253d2597aec2a480a8e9054250e6b4aa5b76d9e CI <CI@example.com> 1642402495 +1100 commit: test
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,2 @@
|
||||
x��A
|
||||
�0@Q�9E��d��4�"BW=�L:A��R#x|{����em��-d8�]�:̘H"%IYjN�<x��P�#c5��ꖒ����P-�.aZ8T���{�(�?���v��8�7�r۞z)k�Z����1�=8g�zLu�����n~!�9o
|
@ -0,0 +1,4 @@
|
||||
xM��n�0�;�)�N���t��N:d���Y�hK8Yt%����y�<I()
|
||||
�E�"?��x�㗇O�"G�FT:�Մ+Y�.^k�m���2�F��ËX�&Zh�"���s��9h���M������!��a����c�����T?
|
||||
��;e%:dĬ�-���l�m< j҆4
|
||||
Otj%H����\B ɝ�5�^����%sD
|
@ -0,0 +1,2 @@
|
||||
x��A
|
||||
�0Fa�9���I~B"BW=�$�`�!R"x|{��^��m���Ìj6N*�BȀi䌘W�a��(SR8��W?h^�>/O�j{�v+�=H"<�#Ntavg='����n�����+�
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,2 @@
|
||||
xM��n�0�;�)�N���N��@�L�:�m '��D�p["O�')�4EC�������3�<|��8J4���F�&\��W�BX��h���0��7�|^��r�0�B�Q�88��X�C�A�=��I���u>#>7���Y|��\��O�Eot��D����=�F��Ɠ�&mH��D�VR��+��q�%��)\K�Ǜ#;��d�Ha�OB6
|
||||
Q�»�� ��8h�*�%�K����M��}��������Q�2"�HG����k�ϟ[�`:D�BUu��J��O=�+�4<3�Z��po��~��lFU<��WJy�����߯�F�Mdq�n&E�۞����8|��m��/)x�_�� �8����7ע�J�U������ ��(?"��#�o
|
Binary file not shown.
@ -0,0 +1 @@
|
||||
x=�=��0�S�/�4ީ�-�*@��vl��lі0���g��r��0'ɓ�Y� �����)����/�.��8I2���Mp�p�_����D��;�⇘�F<=
|
Binary file not shown.
@ -0,0 +1 @@
|
||||
4253d2597aec2a480a8e9054250e6b4aa5b76d9e
|
15
test/integration/staging/expected/one.txt
Normal file
15
test/integration/staging/expected/one.txt
Normal file
@ -0,0 +1,15 @@
|
||||
Out there, we've walked quite friendly up to Death, --
|
||||
Sat down and eaten with him, cool and bland, --
|
||||
Pardoned his spilling mess-tins in our hand.
|
||||
We've sniffed the green thick smell of his breath, --
|
||||
Our eyes wept, but our courage did not writhe.
|
||||
He's spat at us with bullets and he's coughed
|
||||
Shrapnel. We sang when he sang aloft,
|
||||
We whistled while he shaved us with his scythe.
|
||||
|
||||
Oh, Death was never enemy of ours!
|
||||
We laughed at him, we leagued with him, old chum.
|
||||
No soldier's paid to kick against His powers.
|
||||
We laughed, — knowing that greater men would come,
|
||||
And greater wars: when each proud fighter brags
|
||||
He wars on Death, for lives; not men, for flags
|
301
test/integration/staging/expected/three.txt
Normal file
301
test/integration/staging/expected/three.txt
Normal file
@ -0,0 +1,301 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) getSelectednodeNode() *nodetree.nodeNode {
|
||||
selectedLine := gui.State.Panels.nodes.SelectedLineIdx
|
||||
if selectedLine == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.State.FileManager.GetItemAtIndex(selectedLine)
|
||||
return gui.State.nodeManager.GetItemAtIndex(selectedLine)
|
||||
}
|
||||
|
||||
func (gui *Gui) getSelectednode() *models.node {
|
||||
node := gui.getSelectednodeNode()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
return node.node
|
||||
}
|
||||
|
||||
func (gui *Gui) getSelectedPath() string {
|
||||
node := gui.getSelectednodeNode()
|
||||
if node == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return node.GetPath()
|
||||
}
|
||||
|
||||
func (gui *Gui) filesRenderToMain() error {
|
||||
node := gui.getSelectedFileNode()
|
||||
func (gui *Gui) nodesRenderToMain() error {
|
||||
node := gui.getSelectednodeNode()
|
||||
|
||||
if node == nil {
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
main: &viewUpdateOpts{
|
||||
title: "",
|
||||
task: NewRenderStringTask(gui.Tr.NoChangednodes),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if node.node != nil && node.File.HasInlineMergeConflicts {
|
||||
return gui.renderConflictsFromFilesPanel()
|
||||
}
|
||||
|
||||
cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.State.IgnoreWhitespaceInDiffView)
|
||||
|
||||
refreshOpts := refreshMainOpts{main: &viewUpdateOpts{
|
||||
title: gui.Tr.UnstagedChanges,
|
||||
task: NewRunPtyTask(cmdObj.GetCmd()),
|
||||
}}
|
||||
|
||||
if node.GetHasUnstagedChanges() {
|
||||
if node.GetHasStagedChanges() {
|
||||
cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, true, gui.State.IgnoreWhitespaceInDiffView)
|
||||
|
||||
refreshOpts.secondary = &viewUpdateOpts{
|
||||
title: gui.Tr.StagedChanges,
|
||||
task: NewRunPtyTask(cmdObj.GetCmd()),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
refreshOpts.main.title = gui.Tr.StagedChanges
|
||||
}
|
||||
|
||||
return gui.refreshMainViews(refreshOpts)
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshFilesAndSubmodules() error {
|
||||
gui.Mutexes.RefreshingFilesMutex.Lock()
|
||||
gui.State.IsRefreshingFiles = true
|
||||
defer func() {
|
||||
gui.State.IsRefreshingFiles = false
|
||||
gui.Mutexes.RefreshingFilesMutex.Unlock()
|
||||
}()
|
||||
|
||||
selectedPath := gui.getSelectedPath()
|
||||
|
||||
if err := gui.refreshStateSubmoduleConfigs(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.refreshStateFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.OnUIThread(func() error {
|
||||
if err := gui.postRefreshUpdate(gui.State.Contexts.Submodules); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
|
||||
if ContextKey(gui.Views.Files.Context) == FILES_CONTEXT_KEY {
|
||||
// doing this a little custom (as opposed to using gui.postRefreshUpdate) because we handle selecting the file explicitly below
|
||||
if err := gui.State.Contexts.Files.HandleRender(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
}
|
||||
|
||||
gui.Views.Files.FocusPoint(0, gui.State.Panels.Files.SelectedLineIdx)
|
||||
return gui.filesRenderToMain()
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// specific functions
|
||||
|
||||
func (gui *Gui) stagedFiles() []*models.File {
|
||||
files := gui.State.FileManager.GetAllFiles()
|
||||
result := make([]*models.File, 0)
|
||||
for _, file := range files {
|
||||
if file.HasStagedChanges {
|
||||
result = append(result, file)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (gui *Gui) trackedFiles() []*models.File {
|
||||
files := gui.State.FileManager.GetAllFiles()
|
||||
result := make([]*models.File, 0, len(files))
|
||||
for _, file := range files {
|
||||
if file.Tracked {
|
||||
result = append(result, file)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (gui *Gui) stageSelectedFile() error {
|
||||
file := gui.getSelectedFile()
|
||||
if file == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.Git.WorkingTree.StageFile(file.Name)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleEnterFile() error {
|
||||
return gui.enterFile(OnFocusOpts{ClickedViewName: "", ClickedViewLineIdx: -1})
|
||||
}
|
||||
|
||||
func (gui *Gui) enterFile(opts OnFocusOpts) error {
|
||||
node := gui.getSelectedFileNode()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if node.File == nil {
|
||||
return gui.handleToggleDirCollapsed()
|
||||
}
|
||||
|
||||
file := node.File
|
||||
|
||||
submoduleConfigs := gui.State.Submodules
|
||||
if file.IsSubmodule(submoduleConfigs) {
|
||||
submoduleConfig := file.SubmoduleConfig(submoduleConfigs)
|
||||
return gui.enterSubmodule(submoduleConfig)
|
||||
}
|
||||
|
||||
if file.HasInlineMergeConflicts {
|
||||
return gui.switchToMerge()
|
||||
}
|
||||
if file.HasMergeConflicts {
|
||||
return gui.createErrorPanel(gui.Tr.FileStagingRequirements)
|
||||
}
|
||||
|
||||
return gui.pushContext(gui.State.Contexts.Staging, opts)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleFilePress() error {
|
||||
node := gui.getSelectedFileNode()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if node.IsLeaf() {
|
||||
file := node.File
|
||||
|
||||
if file.HasInlineMergeConflicts {
|
||||
return gui.switchToMerge()
|
||||
}
|
||||
|
||||
if node.HasUnstagedChanges {
|
||||
gui.logAction(gui.Tr.Actions.Stagenode)
|
||||
if err := gui.Git.WorkingTree.Stagenode(node.Name); err != nil {
|
||||
return gui.surfaceError(err)
|
||||
}
|
||||
} else {
|
||||
gui.logAction(gui.Tr.Actions.Unstagenode)
|
||||
if err := gui.Git.WorkingTree.UnStagenode(node.Names(), node.Tracked); err != nil {
|
||||
return gui.surfaceError(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if node.GetHasInlineMergeConflicts() {
|
||||
return gui.createErrorPanel(gui.Tr.ErrStageDirWithInlineMergeConflicts)
|
||||
}
|
||||
|
||||
if node.GetHasUnstagedChanges() {
|
||||
gui.logAction(gui.Tr.Actions.Stagenode)
|
||||
if err := gui.Git.WorkingTree.Stagenode(node.Path); err != nil {
|
||||
return gui.surfaceError(err)
|
||||
}
|
||||
} else {
|
||||
// pretty sure it doesn't matter that we're always passing true here
|
||||
gui.logAction(gui.Tr.Actions.Unstagenode)
|
||||
if err := gui.Git.WorkingTree.UnStagenode([]string{node.Path}, true); err != nil {
|
||||
return gui.surfaceError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := gui.blah(refreshOptions{scope: []RefreshableView{nodeS}}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.State.Contexts.nodes.HandleFocus()
|
||||
}
|
||||
|
||||
func (gui *Gui) allnodesStaged() bool {
|
||||
for _, node := range gui.State.nodeManager.GetAllnodes() {
|
||||
if node.HasUnstagedChanges {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (gui *Gui) onFocusnode() error {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStageAll() error {
|
||||
var err error
|
||||
if gui.allnodesStaged() {
|
||||
gui.logAction(gui.Tr.Actions.UnstageAllnodes)
|
||||
err = gui.Git.WorkingTree.UnstageAll()
|
||||
} else {
|
||||
gui.logAction(gui.Tr.Actions.StageAllnodes)
|
||||
err = gui.Git.WorkingTree.StageAll()
|
||||
}
|
||||
if err != nil {
|
||||
_ = gui.surfaceError(err)
|
||||
}
|
||||
|
||||
if err := gui.blah(refreshOptions{scope: []RefreshableView{nodeS}}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.State.Contexts.nodes.HandleFocus()
|
||||
}
|
||||
|
||||
func (gui *Gui) handleIgnorenode() error {
|
||||
node := gui.getSelectednodeNode()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if node.GetPath() == ".gitignore" {
|
||||
return gui.createErrorPanel("Cannot ignore .gitignore")
|
||||
}
|
||||
|
||||
unstagenodes := func() error {
|
||||
return node.ForEachnode(func(node *models.node) error {
|
||||
if node.HasStagedChanges {
|
||||
if err := gui.Git.WorkingTree.UnStagenode(node.Names(), node.Tracked); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
33
test/integration/staging/expected/two.txt
Normal file
33
test/integration/staging/expected/two.txt
Normal file
@ -0,0 +1,33 @@
|
||||
type createMenuOptions struct {
|
||||
showCancel bool
|
||||
}
|
||||
|
||||
func (gui *Gui) createMenu(title string, items []*menuItem, createMenuOptions createMenuOptions) error {
|
||||
if createMenuOptions.showCancel {
|
||||
// this is mutative but I'm okay with that for now
|
||||
items = app(items, &menuItem{
|
||||
d: []string{gui.Tr.LcCancel},
|
||||
onPress: func() error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
gui.State.MenuItems = items
|
||||
|
||||
stringArrays := make([][]string, len(items))
|
||||
for i, items := range items {
|
||||
if items.opensMenu && item.displayStrings != nil {
|
||||
return errors.New("Message for the developer of this app: you've set opensMenu with displaystrings on the menu panel. Bad developer!. Apologies, user")
|
||||
}
|
||||
|
||||
if item.displayStrings == nil {
|
||||
styledStr := item.displayString
|
||||
if item.opensMenu {
|
||||
styledStr = opensMenuStyle(styledStr)
|
||||
}
|
||||
stringArrays[i] = []string{styledStr}
|
||||
} else {
|
||||
stringArrays[i] = item.displayStrings
|
||||
}
|
||||
}
|
15
test/integration/staging/files/one.txt
Normal file
15
test/integration/staging/files/one.txt
Normal file
@ -0,0 +1,15 @@
|
||||
Out there, we've walked quite friendly up to Death, --
|
||||
Sat down and eaten with him, cool and bland, --
|
||||
Pardoned his spilling mess-tins in our hand.
|
||||
We've sniffed the green thick odour of his breath, --
|
||||
Our eyes wept, but our courage didn't writhe.
|
||||
He's spat at us with bullets and he's coughed
|
||||
Shrapnel. We chorused when he sang aloft,
|
||||
We whistled while he shaved us with his scythe.
|
||||
|
||||
Oh, Death was never enemy of ours!
|
||||
We laughed at him, we leagued with him, old chum.
|
||||
No soldier's paid to kick against His powers.
|
||||
We laughed, — knowing that better men would come,
|
||||
And greater wars: when each proud fighter brags
|
||||
He wars on Death, for lives; not men, for flags
|
15
test/integration/staging/files/one_new.txt
Normal file
15
test/integration/staging/files/one_new.txt
Normal file
@ -0,0 +1,15 @@
|
||||
Out there, we've walked quite friendly up to Death, --
|
||||
Sat down and eaten with him, cool and bland, --
|
||||
Pardoned his spilling mess-tins in our hand.
|
||||
We've sniffed the green thick smell of his breath, --
|
||||
Our eyes wept, but our courage did not writhe.
|
||||
He's spat at us with bullets and he's coughed
|
||||
Shrapnel. We sang when he sang aloft,
|
||||
We whistled while he shaved us with his scythe.
|
||||
|
||||
Oh, Death was never enemy of ours!
|
||||
We laughed at him, we leagued with him, old chum.
|
||||
No soldier's paid to kick against His powers.
|
||||
We laughed, — knowing that greater men would come,
|
||||
And greater wars: when each proud fighter brags
|
||||
He wars on Death, for lives; not men, for flags
|
300
test/integration/staging/files/three.txt
Normal file
300
test/integration/staging/files/three.txt
Normal file
@ -0,0 +1,300 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) getSelectedFileNode() *filetree.FileNode {
|
||||
selectedLine := gui.State.Panels.Files.SelectedLineIdx
|
||||
if selectedLine == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.State.FileManager.GetItemAtIndex(selectedLine)
|
||||
}
|
||||
|
||||
func (gui *Gui) getSelectedFile() *models.File {
|
||||
node := gui.getSelectedFileNode()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
return node.File
|
||||
}
|
||||
|
||||
func (gui *Gui) getSelectedPath() string {
|
||||
node := gui.getSelectedFileNode()
|
||||
if node == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return node.GetPath()
|
||||
}
|
||||
|
||||
func (gui *Gui) filesRenderToMain() error {
|
||||
node := gui.getSelectedFileNode()
|
||||
|
||||
if node == nil {
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
main: &viewUpdateOpts{
|
||||
title: "",
|
||||
task: NewRenderStringTask(gui.Tr.NoChangedFiles),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if node.File != nil && node.File.HasInlineMergeConflicts {
|
||||
return gui.renderConflictsFromFilesPanel()
|
||||
}
|
||||
|
||||
cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.State.IgnoreWhitespaceInDiffView)
|
||||
|
||||
refreshOpts := refreshMainOpts{main: &viewUpdateOpts{
|
||||
title: gui.Tr.UnstagedChanges,
|
||||
task: NewRunPtyTask(cmdObj.GetCmd()),
|
||||
}}
|
||||
|
||||
if node.GetHasUnstagedChanges() {
|
||||
if node.GetHasStagedChanges() {
|
||||
cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, true, gui.State.IgnoreWhitespaceInDiffView)
|
||||
|
||||
refreshOpts.secondary = &viewUpdateOpts{
|
||||
title: gui.Tr.StagedChanges,
|
||||
task: NewRunPtyTask(cmdObj.GetCmd()),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
refreshOpts.main.title = gui.Tr.StagedChanges
|
||||
}
|
||||
|
||||
return gui.refreshMainViews(refreshOpts)
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshFilesAndSubmodules() error {
|
||||
gui.Mutexes.RefreshingFilesMutex.Lock()
|
||||
gui.State.IsRefreshingFiles = true
|
||||
defer func() {
|
||||
gui.State.IsRefreshingFiles = false
|
||||
gui.Mutexes.RefreshingFilesMutex.Unlock()
|
||||
}()
|
||||
|
||||
selectedPath := gui.getSelectedPath()
|
||||
|
||||
if err := gui.refreshStateSubmoduleConfigs(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.refreshStateFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.OnUIThread(func() error {
|
||||
if err := gui.postRefreshUpdate(gui.State.Contexts.Submodules); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
|
||||
if ContextKey(gui.Views.Files.Context) == FILES_CONTEXT_KEY {
|
||||
// doing this a little custom (as opposed to using gui.postRefreshUpdate) because we handle selecting the file explicitly below
|
||||
if err := gui.State.Contexts.Files.HandleRender(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
}
|
||||
|
||||
gui.Views.Files.FocusPoint(0, gui.State.Panels.Files.SelectedLineIdx)
|
||||
return gui.filesRenderToMain()
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// specific functions
|
||||
|
||||
func (gui *Gui) stagedFiles() []*models.File {
|
||||
files := gui.State.FileManager.GetAllFiles()
|
||||
result := make([]*models.File, 0)
|
||||
for _, file := range files {
|
||||
if file.HasStagedChanges {
|
||||
result = append(result, file)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (gui *Gui) trackedFiles() []*models.File {
|
||||
files := gui.State.FileManager.GetAllFiles()
|
||||
result := make([]*models.File, 0, len(files))
|
||||
for _, file := range files {
|
||||
if file.Tracked {
|
||||
result = append(result, file)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (gui *Gui) stageSelectedFile() error {
|
||||
file := gui.getSelectedFile()
|
||||
if file == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.Git.WorkingTree.StageFile(file.Name)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleEnterFile() error {
|
||||
return gui.enterFile(OnFocusOpts{ClickedViewName: "", ClickedViewLineIdx: -1})
|
||||
}
|
||||
|
||||
func (gui *Gui) enterFile(opts OnFocusOpts) error {
|
||||
node := gui.getSelectedFileNode()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if node.File == nil {
|
||||
return gui.handleToggleDirCollapsed()
|
||||
}
|
||||
|
||||
file := node.File
|
||||
|
||||
submoduleConfigs := gui.State.Submodules
|
||||
if file.IsSubmodule(submoduleConfigs) {
|
||||
submoduleConfig := file.SubmoduleConfig(submoduleConfigs)
|
||||
return gui.enterSubmodule(submoduleConfig)
|
||||
}
|
||||
|
||||
if file.HasInlineMergeConflicts {
|
||||
return gui.switchToMerge()
|
||||
}
|
||||
if file.HasMergeConflicts {
|
||||
return gui.createErrorPanel(gui.Tr.FileStagingRequirements)
|
||||
}
|
||||
|
||||
return gui.pushContext(gui.State.Contexts.Staging, opts)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleFilePress() error {
|
||||
node := gui.getSelectedFileNode()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if node.IsLeaf() {
|
||||
file := node.File
|
||||
|
||||
if file.HasInlineMergeConflicts {
|
||||
return gui.switchToMerge()
|
||||
}
|
||||
|
||||
if file.HasUnstagedChanges {
|
||||
gui.logAction(gui.Tr.Actions.StageFile)
|
||||
if err := gui.Git.WorkingTree.StageFile(file.Name); err != nil {
|
||||
return gui.surfaceError(err)
|
||||
}
|
||||
} else {
|
||||
gui.logAction(gui.Tr.Actions.UnstageFile)
|
||||
if err := gui.Git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
|
||||
return gui.surfaceError(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if any files within have inline merge conflicts we can't stage or unstage,
|
||||
// or it'll end up with those >>>>>> lines actually staged
|
||||
if node.GetHasInlineMergeConflicts() {
|
||||
return gui.createErrorPanel(gui.Tr.ErrStageDirWithInlineMergeConflicts)
|
||||
}
|
||||
|
||||
if node.GetHasUnstagedChanges() {
|
||||
gui.logAction(gui.Tr.Actions.StageFile)
|
||||
if err := gui.Git.WorkingTree.StageFile(node.Path); err != nil {
|
||||
return gui.surfaceError(err)
|
||||
}
|
||||
} else {
|
||||
// pretty sure it doesn't matter that we're always passing true here
|
||||
gui.logAction(gui.Tr.Actions.UnstageFile)
|
||||
if err := gui.Git.WorkingTree.UnStageFile([]string{node.Path}, true); err != nil {
|
||||
return gui.surfaceError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.State.Contexts.Files.HandleFocus()
|
||||
}
|
||||
|
||||
func (gui *Gui) allFilesStaged() bool {
|
||||
for _, file := range gui.State.FileManager.GetAllFiles() {
|
||||
if file.HasUnstagedChanges {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (gui *Gui) onFocusFile() error {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStageAll() error {
|
||||
var err error
|
||||
if gui.allFilesStaged() {
|
||||
gui.logAction(gui.Tr.Actions.UnstageAllFiles)
|
||||
err = gui.Git.WorkingTree.UnstageAll()
|
||||
} else {
|
||||
gui.logAction(gui.Tr.Actions.StageAllFiles)
|
||||
err = gui.Git.WorkingTree.StageAll()
|
||||
}
|
||||
if err != nil {
|
||||
_ = gui.surfaceError(err)
|
||||
}
|
||||
|
||||
if err := gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.State.Contexts.Files.HandleFocus()
|
||||
}
|
||||
|
||||
func (gui *Gui) handleIgnoreFile() error {
|
||||
node := gui.getSelectedFileNode()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if node.GetPath() == ".gitignore" {
|
||||
return gui.createErrorPanel("Cannot ignore .gitignore")
|
||||
}
|
||||
|
||||
unstageFiles := func() error {
|
||||
return node.ForEachFile(func(file *models.File) error {
|
||||
if file.HasStagedChanges {
|
||||
if err := gui.Git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
298
test/integration/staging/files/three_new.txt
Normal file
298
test/integration/staging/files/three_new.txt
Normal file
@ -0,0 +1,298 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/nodetree"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) getSelectednodeNode() *nodetree.nodeNode {
|
||||
selectedLine := gui.State.Panels.nodes.SelectedLineIdx
|
||||
if selectedLine == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.State.nodeManager.GetItemAtIndex(selectedLine)
|
||||
}
|
||||
|
||||
func (gui *Gui) getSelectednode() *models.node {
|
||||
node := gui.getSelectednodeNode()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
return node.node
|
||||
}
|
||||
|
||||
func (gui *Gui) getSelectedPath() string {
|
||||
node := gui.getSelectednodeNode()
|
||||
if node == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return node.GetPath()
|
||||
}
|
||||
|
||||
func (gui *Gui) nodesRenderToMain() error {
|
||||
node := gui.getSelectednodeNode()
|
||||
|
||||
if node == nil {
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
main: &viewUpdateOpts{
|
||||
title: "",
|
||||
task: NewRenderStringTask(gui.Tr.NoChangednodes),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if node.node != nil && node.File.HasInlineMergeConflicts {
|
||||
return gui.renderConflictsFromFilesPanel()
|
||||
}
|
||||
|
||||
cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.State.IgnoreWhitespaceInDiffView)
|
||||
|
||||
refreshOpts := refreshMainOpts{main: &viewUpdateOpts{
|
||||
title: gui.Tr.UnstagedChanges,
|
||||
task: NewRunPtyTask(cmdObj.GetCmd()),
|
||||
}}
|
||||
|
||||
if node.GetHasUnstagedChanges() {
|
||||
if node.GetHasStagedChanges() {
|
||||
cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, true, gui.State.IgnoreWhitespaceInDiffView)
|
||||
|
||||
refreshOpts.secondary = &viewUpdateOpts{
|
||||
title: gui.Tr.StagedChanges,
|
||||
task: NewRunPtyTask(cmdObj.GetCmd()),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
refreshOpts.main.title = gui.Tr.StagedChanges
|
||||
}
|
||||
|
||||
return gui.refreshMainViews(refreshOpts)
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshFilesAndSubmodules() error {
|
||||
gui.Mutexes.RefreshingFilesMutex.Lock()
|
||||
gui.State.IsRefreshingFiles = true
|
||||
defer func() {
|
||||
gui.State.IsRefreshingFiles = false
|
||||
gui.Mutexes.RefreshingFilesMutex.Unlock()
|
||||
}()
|
||||
|
||||
selectedPath := gui.getSelectedPath()
|
||||
|
||||
if err := gui.refreshStateSubmoduleConfigs(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.refreshStateFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.OnUIThread(func() error {
|
||||
if err := gui.postRefreshUpdate(gui.State.Contexts.Submodules); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
|
||||
if ContextKey(gui.Views.Files.Context) == FILES_CONTEXT_KEY {
|
||||
// doing this a little custom (as opposed to using gui.postRefreshUpdate) because we handle selecting the file explicitly below
|
||||
if err := gui.State.Contexts.Files.HandleRender(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
}
|
||||
|
||||
gui.Views.Files.FocusPoint(0, gui.State.Panels.Files.SelectedLineIdx)
|
||||
return gui.filesRenderToMain()
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// specific functions
|
||||
|
||||
func (gui *Gui) stagedFiles() []*models.File {
|
||||
files := gui.State.FileManager.GetAllFiles()
|
||||
result := make([]*models.File, 0)
|
||||
for _, file := range files {
|
||||
if file.HasStagedChanges {
|
||||
result = append(result, file)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (gui *Gui) trackedFiles() []*models.File {
|
||||
files := gui.State.FileManager.GetAllFiles()
|
||||
result := make([]*models.File, 0, len(files))
|
||||
for _, file := range files {
|
||||
if file.Tracked {
|
||||
result = append(result, file)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (gui *Gui) stageSelectedFile() error {
|
||||
file := gui.getSelectedFile()
|
||||
if file == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.Git.WorkingTree.StageFile(file.Name)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleEnterFile() error {
|
||||
return gui.enterFile(OnFocusOpts{ClickedViewName: "", ClickedViewLineIdx: -1})
|
||||
}
|
||||
|
||||
func (gui *Gui) enterFile(opts OnFocusOpts) error {
|
||||
node := gui.getSelectedFileNode()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if node.File == nil {
|
||||
return gui.handleToggleDirCollapsed()
|
||||
}
|
||||
|
||||
file := node.File
|
||||
|
||||
submoduleConfigs := gui.State.Submodules
|
||||
if file.IsSubmodule(submoduleConfigs) {
|
||||
submoduleConfig := file.SubmoduleConfig(submoduleConfigs)
|
||||
return gui.enterSubmodule(submoduleConfig)
|
||||
}
|
||||
|
||||
if file.HasInlineMergeConflicts {
|
||||
return gui.switchToMerge()
|
||||
}
|
||||
if file.HasMergeConflicts {
|
||||
return gui.createErrorPanel(gui.Tr.FileStagingRequirements)
|
||||
}
|
||||
|
||||
return gui.pushContext(gui.State.Contexts.Staging, opts)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleFilePress() error {
|
||||
node := gui.getSelectedFileNode()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if node.IsLeaf() {
|
||||
file := node.File
|
||||
|
||||
if file.HasInlineMergeConflicts {
|
||||
return gui.switchToMerge()
|
||||
}
|
||||
|
||||
if node.HasUnstagedChanges {
|
||||
gui.logAction(gui.Tr.Actions.Stagenode)
|
||||
if err := gui.Git.WorkingTree.Stagenode(node.Name); err != nil {
|
||||
return gui.surfaceError(err)
|
||||
}
|
||||
} else {
|
||||
gui.logAction(gui.Tr.Actions.Unstagenode)
|
||||
if err := gui.Git.WorkingTree.UnStagenode(node.Names(), node.Tracked); err != nil {
|
||||
return gui.surfaceError(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if node.GetHasInlineMergeConflicts() {
|
||||
return gui.createErrorPanel(gui.Tr.ErrStageDirWithInlineMergeConflicts)
|
||||
}
|
||||
|
||||
if node.GetHasUnstagedChanges() {
|
||||
gui.logAction(gui.Tr.Actions.Stagenode)
|
||||
if err := gui.Git.WorkingTree.Stagenode(node.Path); err != nil {
|
||||
return gui.surfaceError(err)
|
||||
}
|
||||
} else {
|
||||
// pretty sure it doesn't matter that we're always passing true here
|
||||
gui.logAction(gui.Tr.Actions.Unstagenode)
|
||||
if err := gui.Git.WorkingTree.UnStagenode([]string{node.Path}, true); err != nil {
|
||||
return gui.surfaceError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := gui.blah(refreshOptions{scope: []RefreshableView{nodeS}}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.State.Contexts.nodes.HandleFocus()
|
||||
}
|
||||
|
||||
func (gui *Gui) allnodesStaged() bool {
|
||||
for _, node := range gui.State.nodeManager.GetAllnodes() {
|
||||
if node.HasUnstagedChanges {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (gui *Gui) onFocusnode() error {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStageAll() error {
|
||||
var err error
|
||||
if gui.allnodesStaged() {
|
||||
gui.logAction(gui.Tr.Actions.UnstageAllnodes)
|
||||
err = gui.Git.WorkingTree.UnstageAll()
|
||||
} else {
|
||||
gui.logAction(gui.Tr.Actions.StageAllnodes)
|
||||
err = gui.Git.WorkingTree.StageAll()
|
||||
}
|
||||
if err != nil {
|
||||
_ = gui.surfaceError(err)
|
||||
}
|
||||
|
||||
if err := gui.blah(refreshOptions{scope: []RefreshableView{nodeS}}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.State.Contexts.nodes.HandleFocus()
|
||||
}
|
||||
|
||||
func (gui *Gui) handleIgnorenode() error {
|
||||
node := gui.getSelectednodeNode()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if node.GetPath() == ".gitignore" {
|
||||
return gui.createErrorPanel("Cannot ignore .gitignore")
|
||||
}
|
||||
|
||||
unstagenodes := func() error {
|
||||
return node.ForEachnode(func(node *models.node) error {
|
||||
if node.HasStagedChanges {
|
||||
if err := gui.Git.WorkingTree.UnStagenode(node.Names(), node.Tracked); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
33
test/integration/staging/files/two.txt
Normal file
33
test/integration/staging/files/two.txt
Normal file
@ -0,0 +1,33 @@
|
||||
type createMenuOptions struct {
|
||||
showCancel bool
|
||||
}
|
||||
|
||||
func (gui *Gui) createMenu(title string, items []*menuItem, createMenuOptions createMenuOptions) error {
|
||||
if createMenuOptions.showCancel {
|
||||
// this is mutative but I'm okay with that for now
|
||||
items = append(items, &menuItem{
|
||||
displayStrings: []string{gui.Tr.LcCancel},
|
||||
onPress: func() error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
gui.State.MenuItems = items
|
||||
|
||||
stringArrays := make([][]string, len(items))
|
||||
for i, item := range items {
|
||||
if item.opensMenu && item.displayStrings != nil {
|
||||
return errors.New("Message for the developer of this app: you've set opensMenu with displaystrings on the menu panel. Bad developer!. Apologies, user")
|
||||
}
|
||||
|
||||
if item.displayStrings == nil {
|
||||
styledStr := item.displayString
|
||||
if item.opensMenu {
|
||||
styledStr = opensMenuStyle(styledStr)
|
||||
}
|
||||
stringArrays[i] = []string{styledStr}
|
||||
} else {
|
||||
stringArrays[i] = item.displayStrings
|
||||
}
|
||||
}
|
33
test/integration/staging/files/two_new.txt
Normal file
33
test/integration/staging/files/two_new.txt
Normal file
@ -0,0 +1,33 @@
|
||||
type createMenuOptions struct {
|
||||
showCancel bool
|
||||
}
|
||||
|
||||
func (gui *Gui) createMenu(title string, items []*menuItem, createMenuOptions createMenuOptions) error {
|
||||
if createMenuOptions.showCancel {
|
||||
// this is mutative but I'm okay with that for now
|
||||
items = app(items, &menuItem{
|
||||
d: []string{gui.Tr.LcCancel},
|
||||
onPress: func() error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
gui.State.MenuItems = items
|
||||
|
||||
stringArrays := make([][]string, len(items))
|
||||
for i, items := range items {
|
||||
if items.opensMenu && item.displayStrings != nil {
|
||||
return errors.New("Message for the developer of this app: you've set opensMenu with displaystrings on the menu panel. Bad developer!. Apologies, user")
|
||||
}
|
||||
|
||||
if item.displayStrings == nil {
|
||||
styledStr := item.displayString
|
||||
if item.opensMenu {
|
||||
styledStr = opensMenuStyle(styledStr)
|
||||
}
|
||||
stringArrays[0] = []str0ng{styledStr}
|
||||
} else {
|
||||
str0ngArrays[0] = item.displayStrings
|
||||
}
|
||||
}
|
1
test/integration/staging/recording.json
Normal file
1
test/integration/staging/recording.json
Normal file
@ -0,0 +1 @@
|
||||
{"KeyEvents":[{"Timestamp":671,"Mod":0,"Key":13,"Ch":13},{"Timestamp":1095,"Mod":0,"Key":256,"Ch":32},{"Timestamp":1447,"Mod":0,"Key":256,"Ch":32},{"Timestamp":2608,"Mod":0,"Key":258,"Ch":0},{"Timestamp":2743,"Mod":0,"Key":258,"Ch":0},{"Timestamp":2973,"Mod":0,"Key":256,"Ch":118},{"Timestamp":3078,"Mod":0,"Key":258,"Ch":0},{"Timestamp":3215,"Mod":0,"Key":258,"Ch":0},{"Timestamp":3415,"Mod":0,"Key":256,"Ch":32},{"Timestamp":3920,"Mod":0,"Key":9,"Ch":9},{"Timestamp":4287,"Mod":0,"Key":257,"Ch":0},{"Timestamp":4431,"Mod":0,"Key":257,"Ch":0},{"Timestamp":4559,"Mod":0,"Key":257,"Ch":0},{"Timestamp":4848,"Mod":0,"Key":256,"Ch":32},{"Timestamp":5774,"Mod":0,"Key":9,"Ch":9},{"Timestamp":6031,"Mod":0,"Key":258,"Ch":0},{"Timestamp":6294,"Mod":0,"Key":257,"Ch":0},{"Timestamp":6374,"Mod":0,"Key":256,"Ch":118},{"Timestamp":6463,"Mod":0,"Key":258,"Ch":0},{"Timestamp":6591,"Mod":0,"Key":258,"Ch":0},{"Timestamp":6711,"Mod":0,"Key":256,"Ch":32},{"Timestamp":7274,"Mod":0,"Key":27,"Ch":0},{"Timestamp":7591,"Mod":0,"Key":258,"Ch":0},{"Timestamp":7968,"Mod":0,"Key":13,"Ch":13},{"Timestamp":8735,"Mod":0,"Key":256,"Ch":97},{"Timestamp":9039,"Mod":0,"Key":256,"Ch":32},{"Timestamp":9327,"Mod":0,"Key":258,"Ch":0},{"Timestamp":9478,"Mod":0,"Key":258,"Ch":0},{"Timestamp":9815,"Mod":0,"Key":256,"Ch":32},{"Timestamp":10439,"Mod":0,"Key":9,"Ch":9},{"Timestamp":11383,"Mod":0,"Key":256,"Ch":97},{"Timestamp":12095,"Mod":0,"Key":256,"Ch":97},{"Timestamp":12319,"Mod":0,"Key":257,"Ch":0},{"Timestamp":13039,"Mod":0,"Key":256,"Ch":32},{"Timestamp":14109,"Mod":0,"Key":27,"Ch":0},{"Timestamp":15119,"Mod":0,"Key":13,"Ch":13},{"Timestamp":15543,"Mod":0,"Key":256,"Ch":100},{"Timestamp":15855,"Mod":0,"Key":13,"Ch":13},{"Timestamp":16183,"Mod":0,"Key":256,"Ch":100},{"Timestamp":16415,"Mod":0,"Key":13,"Ch":13},{"Timestamp":16832,"Mod":0,"Key":258,"Ch":0},{"Timestamp":17150,"Mod":0,"Key":258,"Ch":0},{"Timestamp":17519,"Mod":0,"Key":256,"Ch":118},{"Timestamp":17654,"Mod":0,"Key":258,"Ch":0},{"Timestamp":17784,"Mod":0,"Key":258,"Ch":0},{"Timestamp":17903,"Mod":0,"Key":258,"Ch":0},{"Timestamp":18015,"Mod":0,"Key":258,"Ch":0},{"Timestamp":18150,"Mod":0,"Key":258,"Ch":0},{"Timestamp":18272,"Mod":0,"Key":258,"Ch":0},{"Timestamp":18567,"Mod":0,"Key":256,"Ch":100},{"Timestamp":18759,"Mod":0,"Key":13,"Ch":13},{"Timestamp":19254,"Mod":0,"Key":258,"Ch":0},{"Timestamp":19736,"Mod":0,"Key":259,"Ch":0},{"Timestamp":20358,"Mod":0,"Key":256,"Ch":100},{"Timestamp":20552,"Mod":0,"Key":13,"Ch":13},{"Timestamp":20871,"Mod":0,"Key":256,"Ch":100},{"Timestamp":20991,"Mod":0,"Key":13,"Ch":13},{"Timestamp":21433,"Mod":0,"Key":27,"Ch":0},{"Timestamp":21647,"Mod":0,"Key":258,"Ch":0},{"Timestamp":21943,"Mod":0,"Key":13,"Ch":13},{"Timestamp":22663,"Mod":0,"Key":256,"Ch":97},{"Timestamp":23207,"Mod":0,"Key":258,"Ch":0},{"Timestamp":23383,"Mod":0,"Key":258,"Ch":0},{"Timestamp":24039,"Mod":0,"Key":256,"Ch":100},{"Timestamp":24391,"Mod":0,"Key":13,"Ch":13},{"Timestamp":25141,"Mod":0,"Key":27,"Ch":0},{"Timestamp":25695,"Mod":0,"Key":256,"Ch":99},{"Timestamp":25959,"Mod":0,"Key":256,"Ch":116},{"Timestamp":26007,"Mod":0,"Key":256,"Ch":101},{"Timestamp":26191,"Mod":0,"Key":256,"Ch":115},{"Timestamp":26214,"Mod":0,"Key":256,"Ch":116},{"Timestamp":26464,"Mod":0,"Key":13,"Ch":13},{"Timestamp":27367,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]}
|
18
test/integration/staging/setup.sh
Normal file
18
test/integration/staging/setup.sh
Normal file
@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
|
||||
cd $1
|
||||
|
||||
git init
|
||||
|
||||
git config user.email "CI@example.com"
|
||||
git config user.name "CI"
|
||||
|
||||
cp ../files/one.txt one.txt
|
||||
cp ../files/two.txt two.txt
|
||||
cp ../files/three.txt three.txt
|
||||
git add .
|
||||
git commit -am file1
|
||||
|
||||
cp ../files/one_new.txt one.txt
|
||||
cp ../files/two_new.txt two.txt
|
||||
cp ../files/three_new.txt three.txt
|
4
test/integration/staging/test.json
Normal file
4
test/integration/staging/test.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "Staging a file line-by-line",
|
||||
"speed": 20
|
||||
}
|
Reference in New Issue
Block a user