Waiting for coverage results... If this message persists check your coverage integration. Go to coverage settings

Move to next stageable line when adding a line to a custom patch
Closed

stefanhaller merged move-to-next-stageable-line-when-staging-in-custom-patch into master

#4675

Last updated

No information.

pkg/commands/patch/format.go

Diff coverage

100 %

@@ -72,30 +72,22 @@ func (self *patchPresenter) format() string {

72 73lineIdx++ 74} 75appendFormattedLine := func(line string, style style.TextStyle) { 76formattedLine := self.formatLine( 77line, 78style, 79lineIdx, 80) 81 82appendLine(formattedLine) 83} 84 85for _, line := range self.patch.header { 86appendFormattedLine(line, theme.DefaultTextColor.SetBold())  87} 88 89for _, hunk := range self.patch.hunks { 90appendLine( 91self.formatLine( 92hunk.formatHeaderStart(), 93style.FgCyan, 94lineIdx, 95) + 96// we're splitting the line into two parts: the diff header and the context 97// We explicitly pass 'included' as false here so that we're only tagging the 98// first half of the line as included if the line is indeed included. 99self.formatLineAux( 100hunk.headerContext, 101theme.DefaultTextColor,
72 73lineIdx++ 74}          75 76for _, line := range self.patch.header { 77// always passing false for 'included' here because header lines are not part of the patch 
1 hits
78appendLine(self.formatLineAux(line, theme.DefaultTextColor.SetBold(), false))
1 hits
79} 80 81for _, hunk := range self.patch.hunks { 82appendLine( 83self.formatLineAux(
1 hits
84hunk.formatHeaderStart(), 85style.FgCyan, 86false,
1 hits
87) + 88// we're splitting the line into two parts: the diff header and the context 89// We explicitly pass 'included' as false for both because these are not part
1 hits
90// of the actual patch
1 hits
91self.formatLineAux( 92hunk.headerContext, 93theme.DefaultTextColor,

@@ -104,7 +96,12 @@ func (self *patchPresenter) format() string {

104) 105 106for _, line := range hunk.bodyLines { 107appendFormattedLine(line.Content, self.patchLineStyle(line))      108} 109} 
96) 97 98for _, line := range hunk.bodyLines { 99style := self.patchLineStyle(line) 
1 hits
100if line.IsChange() {
2 hits
101appendLine(self.formatLine(line.Content, style, lineIdx))
1 hits
102} else {
2 hits
103appendLine(self.formatLineAux(line.Content, style, false))
1 hits
104}
1 hits
105} 106}

pkg/commands/patch/patch.go

Diff coverage

92.3 %

@@ -115,15 +115,22 @@ func (self *Patch) HunkContainingLine(idx int) int {

115return -1 116} 117 118// Returns the patch line index of the next change (i.e. addition or deletion). 119func (self *Patch) GetNextChangeIdx(idx int) int {   120idx = lo.Clamp(idx, 0, self.LineCount()-1) 121 122lines := self.Lines() 123      124for i, line := range lines[idx:] { 125if line.isChange() { 126return i + idx 127} 128} 
115return -1 116} 117 118// Returns the patch line index of the next change (i.e. addition or deletion) 119// that matches the same "included" state, given the includedLines. If you don't 120// care about included states, pass nil for includedLines and false for included. 121func (self *Patch) GetNextChangeIdxOfSameIncludedState(idx int, includedLines []int, included bool) (int, bool) { 
1 hits
122idx = lo.Clamp(idx, 0, self.LineCount()-1) 123 124lines := self.Lines() 125 126isMatch := func(i int, line *PatchLine) bool {
2 hits
127sameIncludedState := lo.Contains(includedLines, i) == included
1 hits
128return line.IsChange() && sameIncludedState
1 hits
129}
1 hits
130 131for i, line := range lines[idx:] { 132if isMatch(i+idx, line) {
2 hits
133return i + idx, true
1 hits
134} 135}

@@ -131,13 +138,18 @@ func (self *Patch) GetNextChangeIdx(idx int) int {

131// return the index of the last change 132for i := len(lines) - 1; i >= 0; i-- { 133line := lines[i] 134if line.isChange() { 135return i 136} 137} 138 139// should not be possible 140return 0      141} 142 143// Returns the length of the patch in lines
138// return the index of the last change 139for i := len(lines) - 1; i >= 0; i-- { 140line := lines[i] 141if isMatch(i, line) { 
2 hits
142return i, true
1 hits
143} 144} 145 146return 0, false
Not covered
147} 148 149// Returns the patch line index of the next change (i.e. addition or deletion). 150func (self *Patch) GetNextChangeIdx(idx int) int {
1 hits
151result, _ := self.GetNextChangeIdxOfSameIncludedState(idx, nil, false)
1 hits
152return result
1 hits
153} 154 155// Returns the length of the patch in lines

pkg/commands/patch/patch_builder.go

Diff coverage

50 %

@@ -124,14 +124,6 @@ func (p *PatchBuilder) RemoveFile(filename string) error {

124return nil 125} 126 127func getIndicesForRange(first, last int) []int { 128indices := []int{} 129for i := first; i <= last; i++ { 130indices = append(indices, i) 131} 132return indices 133} 134 135func (p *PatchBuilder) getFileInfo(filename string) (*fileInfo, error) { 136info, ok := p.fileInfoMap[filename] 137if ok {
124return nil 125} 126         127func (p *PatchBuilder) getFileInfo(filename string) (*fileInfo, error) { 128info, ok := p.fileInfoMap[filename] 129if ok {

@@ -152,24 +144,24 @@ func (p *PatchBuilder) getFileInfo(filename string) (*fileInfo, error) {

152return info, nil 153} 154 155func (p *PatchBuilder) AddFileLineRange(filename string, firstLineIdx, lastLineIdx int) error { 156info, err := p.getFileInfo(filename) 157if err != nil { 158return err 159} 160info.mode = PART 161info.includedLineIndices = lo.Union(info.includedLineIndices, getIndicesForRange(firstLineIdx, lastLineIdx)) 162 163return nil 164} 165 166func (p *PatchBuilder) RemoveFileLineRange(filename string, firstLineIdx, lastLineIdx int) error { 167info, err := p.getFileInfo(filename) 168if err != nil { 169return err 170} 171info.mode = PART 172info.includedLineIndices, _ = lo.Difference(info.includedLineIndices, getIndicesForRange(firstLineIdx, lastLineIdx)) 173if len(info.includedLineIndices) == 0 { 174p.removeFile(info) 175}
144return info, nil 145} 146 147func (p *PatchBuilder) AddFileLineRange(filename string, lineIndices []int) error { 
1 hits
148info, err := p.getFileInfo(filename) 149if err != nil { 150return err 151} 152info.mode = PART 153info.includedLineIndices = lo.Union(info.includedLineIndices, lineIndices)
1 hits
154 155return nil 156} 157 158func (p *PatchBuilder) RemoveFileLineRange(filename string, lineIndices []int) error {
Not covered
159info, err := p.getFileInfo(filename) 160if err != nil { 161return err 162} 163info.mode = PART 164info.includedLineIndices, _ = lo.Difference(info.includedLineIndices, lineIndices)
Not covered
165if len(info.includedLineIndices) == 0 { 166p.removeFile(info) 167}

pkg/commands/patch/patch_line.go

Diff coverage

100 %

@@ -18,7 +18,7 @@ type PatchLine struct {

18Content string // something like '+ hello' (note the first character is not removed) 19} 20 21func (self *PatchLine) isChange() bool { 22return self.Kind == ADDITION || self.Kind == DELETION 23} 
18Content string // something like '+ hello' (note the first character is not removed) 19} 20 21func (self *PatchLine) IsChange() bool { 
1 hits
22return self.Kind == ADDITION || self.Kind == DELETION 23}

pkg/gui/controllers/patch_building_controller.go

Diff coverage

75 %

@@ -126,7 +126,6 @@ func (self *PatchBuildingController) toggleSelection() error {

126self.context().GetMutex().Lock() 127defer self.context().GetMutex().Unlock() 128 129toggleFunc := self.c.Git().Patch.PatchBuilder.AddFileLineRange 130filename := self.c.Contexts().CommitFiles.GetSelectedPath() 131if filename == "" { 132return nil
126self.context().GetMutex().Lock() 127defer self.context().GetMutex().Unlock() 128  129filename := self.c.Contexts().CommitFiles.GetSelectedPath() 130if filename == "" { 131return nil

@@ -134,19 +133,26 @@ func (self *PatchBuildingController) toggleSelection() error {

134 135state := self.context().GetState() 136        137includedLineIndices, err := self.c.Git().Patch.PatchBuilder.GetFileIncLineIndices(filename) 138if err != nil { 139return err 140} 141currentLineIsStaged := lo.Contains(includedLineIndices, state.GetSelectedPatchLineIdx()) 142if currentLineIsStaged {   143toggleFunc = self.c.Git().Patch.PatchBuilder.RemoveFileLineRange 144} 145 146// add range of lines to those set for the file 147firstLineIdx, lastLineIdx := state.SelectedPatchRange() 148 149if err := toggleFunc(filename, firstLineIdx, lastLineIdx); err != nil { 150// might actually want to return an error here 151self.c.Log.Error(err) 152}
133 134state := self.context().GetState() 135 136// Get added/deleted lines in the selected patch range 
1 hits
137lineIndicesToToggle := state.ChangeLinesInSelectedPatchRange()
1 hits
138if len(lineIndicesToToggle) == 0 {
1 hits
139// Only context lines or header lines selected, so nothing to do
Not covered
140return nil
Not covered
141}
Not covered
142 143includedLineIndices, err := self.c.Git().Patch.PatchBuilder.GetFileIncLineIndices(filename) 144if err != nil { 145return err 146} 147 148toggleFunc := self.c.Git().Patch.PatchBuilder.AddFileLineRange
1 hits
149firstSelectedChangeLineIsStaged := lo.Contains(includedLineIndices, lineIndicesToToggle[0])
1 hits
150if firstSelectedChangeLineIsStaged {
1 hits
151toggleFunc = self.c.Git().Patch.PatchBuilder.RemoveFileLineRange 152} 153 154// add range of lines to those set for the file 155if err := toggleFunc(filename, lineIndicesToToggle); err != nil {
1 hits
  156// might actually want to return an error here 157self.c.Log.Error(err) 158}

@@ -155,6 +161,8 @@ func (self *PatchBuildingController) toggleSelection() error {

155state.SetLineSelectMode() 156} 157   158return nil 159} 
161state.SetLineSelectMode() 162} 163 164state.SelectNextStageableLineOfSameIncludedState(self.context().GetIncludedLineIndices(), firstSelectedChangeLineIsStaged) 
1 hits
165
1 hits
166return nil 167}

pkg/gui/patch_exploring/state.go

Diff coverage

100 %

@@ -282,6 +282,20 @@ func (s *State) SelectedPatchRange() (int, int) {

282return s.patchLineIndices[start], s.patchLineIndices[end] 283} 284               285func (s *State) CurrentLineNumber() int { 286return s.patch.LineNumberOfLine(s.patchLineIndices[s.selectedLineIdx]) 287}
282return s.patchLineIndices[start], s.patchLineIndices[end] 283} 284 285// Returns the line indices of the selected patch range that are changes (i.e. additions or deletions) 286func (s *State) ChangeLinesInSelectedPatchRange() []int { 
1 hits
287viewStart, viewEnd := s.SelectedViewRange()
1 hits
288patchStart, patchEnd := s.patchLineIndices[viewStart], s.patchLineIndices[viewEnd]
1 hits
289lines := s.patch.Lines()
1 hits
290indices := []int{}
1 hits
291for i := patchStart; i <= patchEnd; i++ {
2 hits
292if lines[i].IsChange() {
2 hits
293indices = append(indices, i)
1 hits
294}
1 hits
295} 296return indices
1 hits
297} 298 299func (s *State) CurrentLineNumber() int { 300return s.patch.LineNumberOfLine(s.patchLineIndices[s.selectedLineIdx]) 301}

@@ -324,3 +338,11 @@ func wrapPatchLines(diff string, view *gocui.View) ([]int, []int) {

324view.Wrap, view.Editable, strings.TrimSuffix(diff, "\n"), view.InnerWidth(), view.TabWidth) 325return viewLineIndices, patchLineIndices 326}        
338view.Wrap, view.Editable, strings.TrimSuffix(diff, "\n"), view.InnerWidth(), view.TabWidth) 339return viewLineIndices, patchLineIndices 340} 341 342func (s *State) SelectNextStageableLineOfSameIncludedState(includedLines []int, included bool) { 
1 hits
343_, lastLineIdx := s.SelectedPatchRange()
1 hits
344patchLineIdx, found := s.patch.GetNextChangeIdxOfSameIncludedState(lastLineIdx+1, includedLines, included)
1 hits
345if found {
2 hits
346s.SelectLine(s.viewLineIndices[patchLineIdx])
1 hits
347}
1 hits
348}

pkg/integration/tests/patch_building/move_to_index_partial.go

pkg/integration/tests/patch_building/specific_selection.go

Diff coverage

100 %

@@ -66,18 +66,20 @@ var SpecificSelection = NewIntegrationTest(NewIntegrationTestArgs{

66Contains(` 1f`), 67). 68PressPrimaryAction(). 69// unlike in the staging panel, we don't remove lines from the patch building panel 70// upon 'adding' them. So the same lines will be selected 71SelectedLines( 72Contains(`@@ -1,6 +1,6 @@`), 73Contains(`-1a`), 74Contains(`+aa`), 75Contains(` 1b`), 76Contains(`-1c`), 77Contains(`+cc`), 78Contains(` 1d`), 79Contains(` 1e`), 80Contains(` 1f`),     81). 82Tap(func() { 83t.Views().Information().Content(Contains("Building patch"))
66Contains(` 1f`), 67). 68PressPrimaryAction().   69SelectedLines( 70Contains(`@@ -17,9 +17,9 @@`), 
1 hits
71Contains(` 1q`),
1 hits
72Contains(` 1r`),
1 hits
73Contains(` 1s`),
1 hits
74Contains(`-1t`),
1 hits
75Contains(`-1u`),
1 hits
76Contains(`-1v`),
1 hits
77Contains(`+tt`),
1 hits
78Contains(`+uu`),
1 hits
79Contains(`+vv`),
1 hits
80Contains(` 1w`),
1 hits
81Contains(` 1x`),
1 hits
82Contains(` 1y`),
1 hits
83). 84Tap(func() { 85t.Views().Information().Content(Contains("Building patch"))

@@ -106,12 +108,21 @@ var SpecificSelection = NewIntegrationTest(NewIntegrationTestArgs{

106Contains("+2a"), 107). 108PressPrimaryAction().    109NavigateToLine(Contains("+2c")). 110Press(keys.Universal.ToggleRangeSelect). 111NavigateToLine(Contains("+2e")). 112PressPrimaryAction().    113NavigateToLine(Contains("+2g")). 114PressPrimaryAction().    115Tap(func() { 116t.Views().Information().Content(Contains("Building patch")) 
108Contains("+2a"), 109). 110PressPrimaryAction(). 111SelectedLines( 
1 hits
112Contains("+2b"),
1 hits
113).
1 hits
114NavigateToLine(Contains("+2c")). 115Press(keys.Universal.ToggleRangeSelect). 116NavigateToLine(Contains("+2e")). 117PressPrimaryAction(). 118SelectedLines(
1 hits
119Contains("+2f"),
1 hits
120).
1 hits
121NavigateToLine(Contains("+2g")). 122PressPrimaryAction(). 123SelectedLines(
1 hits
124Contains("+2h"),
1 hits
125).
1 hits
126Tap(func() { 127t.Views().Information().Content(Contains("Building patch"))