mirror of
https://github.com/jesseduffield/lazygit.git
synced 2024-11-24 08:52:21 +02:00
Merge pull request #2471 from stefanhaller/improve-custom-patch-conflict-handling
This commit is contained in:
commit
804a134aa5
@ -75,19 +75,11 @@ func (hunk *PatchHunk) updatedLines(lineIndices []int, reverse bool) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func transformedFirstChar(firstChar string, reverse bool, isLineSelected bool) string {
|
func transformedFirstChar(firstChar string, reverse bool, isLineSelected bool) string {
|
||||||
|
linesToKeepInPatchContext := "-"
|
||||||
if reverse {
|
if reverse {
|
||||||
if !isLineSelected && firstChar == "+" {
|
linesToKeepInPatchContext = "+"
|
||||||
return " "
|
|
||||||
} else if firstChar == "-" {
|
|
||||||
return "+"
|
|
||||||
} else if firstChar == "+" {
|
|
||||||
return "-"
|
|
||||||
} else {
|
|
||||||
return firstChar
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if !isLineSelected && firstChar == linesToKeepInPatchContext {
|
||||||
if !isLineSelected && firstChar == "-" {
|
|
||||||
return " "
|
return " "
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,14 +92,14 @@ func (hunk *PatchHunk) formatHeader(oldStart int, oldLength int, newStart int, n
|
|||||||
|
|
||||||
func (hunk *PatchHunk) formatWithChanges(lineIndices []int, reverse bool, startOffset int) (int, string) {
|
func (hunk *PatchHunk) formatWithChanges(lineIndices []int, reverse bool, startOffset int) (int, string) {
|
||||||
bodyLines := hunk.updatedLines(lineIndices, reverse)
|
bodyLines := hunk.updatedLines(lineIndices, reverse)
|
||||||
startOffset, header, ok := hunk.updatedHeader(bodyLines, startOffset, reverse)
|
startOffset, header, ok := hunk.updatedHeader(bodyLines, startOffset)
|
||||||
if !ok {
|
if !ok {
|
||||||
return startOffset, ""
|
return startOffset, ""
|
||||||
}
|
}
|
||||||
return startOffset, header + strings.Join(bodyLines, "")
|
return startOffset, header + strings.Join(bodyLines, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hunk *PatchHunk) updatedHeader(newBodyLines []string, startOffset int, reverse bool) (int, string, bool) {
|
func (hunk *PatchHunk) updatedHeader(newBodyLines []string, startOffset int) (int, string, bool) {
|
||||||
changeCount := nLinesWithPrefix(newBodyLines, []string{"+", "-"})
|
changeCount := nLinesWithPrefix(newBodyLines, []string{"+", "-"})
|
||||||
oldLength := nLinesWithPrefix(newBodyLines, []string{" ", "-"})
|
oldLength := nLinesWithPrefix(newBodyLines, []string{" ", "-"})
|
||||||
newLength := nLinesWithPrefix(newBodyLines, []string{"+", " "})
|
newLength := nLinesWithPrefix(newBodyLines, []string{"+", " "})
|
||||||
@ -117,12 +109,7 @@ func (hunk *PatchHunk) updatedHeader(newBodyLines []string, startOffset int, rev
|
|||||||
return startOffset, "", false
|
return startOffset, "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldStart int
|
oldStart := hunk.oldStart
|
||||||
if reverse {
|
|
||||||
oldStart = hunk.newStart
|
|
||||||
} else {
|
|
||||||
oldStart = hunk.oldStart
|
|
||||||
}
|
|
||||||
|
|
||||||
var newStartOffset int
|
var newStartOffset int
|
||||||
// if the hunk went from zero to positive length, we need to increment the starting point by one
|
// if the hunk went from zero to positive length, we need to increment the starting point by one
|
||||||
|
@ -162,7 +162,7 @@ func (p *PatchManager) RemoveFileLineRange(filename string, firstLineIdx, lastLi
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PatchManager) renderPlainPatchForFile(filename string, reverse bool, keepOriginalHeader bool) string {
|
func (p *PatchManager) renderPlainPatchForFile(filename string, reverse bool) string {
|
||||||
info, err := p.getFileInfo(filename)
|
info, err := p.getFileInfo(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Log.Error(err)
|
p.Log.Error(err)
|
||||||
@ -176,14 +176,18 @@ func (p *PatchManager) renderPlainPatchForFile(filename string, reverse bool, ke
|
|||||||
return info.diff
|
return info.diff
|
||||||
case PART:
|
case PART:
|
||||||
// generate a new diff with just the selected lines
|
// generate a new diff with just the selected lines
|
||||||
return ModifiedPatchForLines(p.Log, filename, info.diff, info.includedLineIndices, reverse, keepOriginalHeader)
|
return ModifiedPatchForLines(p.Log, filename, info.diff, info.includedLineIndices,
|
||||||
|
PatchOptions{
|
||||||
|
Reverse: reverse,
|
||||||
|
KeepOriginalHeader: true,
|
||||||
|
})
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PatchManager) RenderPatchForFile(filename string, plain bool, reverse bool, keepOriginalHeader bool) string {
|
func (p *PatchManager) RenderPatchForFile(filename string, plain bool, reverse bool) string {
|
||||||
patch := p.renderPlainPatchForFile(filename, reverse, keepOriginalHeader)
|
patch := p.renderPlainPatchForFile(filename, reverse)
|
||||||
if plain {
|
if plain {
|
||||||
return patch
|
return patch
|
||||||
}
|
}
|
||||||
@ -199,7 +203,7 @@ func (p *PatchManager) renderEachFilePatch(plain bool) []string {
|
|||||||
|
|
||||||
sort.Strings(filenames)
|
sort.Strings(filenames)
|
||||||
patches := slices.Map(filenames, func(filename string) string {
|
patches := slices.Map(filenames, func(filename string) string {
|
||||||
return p.RenderPatchForFile(filename, plain, false, true)
|
return p.RenderPatchForFile(filename, plain, false)
|
||||||
})
|
})
|
||||||
output := slices.Filter(patches, func(patch string) bool {
|
output := slices.Filter(patches, func(patch string) bool {
|
||||||
return patch != ""
|
return patch != ""
|
||||||
@ -240,42 +244,22 @@ func (p *PatchManager) GetFileIncLineIndices(filename string) ([]int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *PatchManager) ApplyPatches(reverse bool) error {
|
func (p *PatchManager) ApplyPatches(reverse bool) error {
|
||||||
// for whole patches we'll apply the patch in reverse
|
patch := ""
|
||||||
// but for part patches we'll apply a reverse patch forwards
|
|
||||||
|
applyFlags := []string{"index", "3way"}
|
||||||
|
if reverse {
|
||||||
|
applyFlags = append(applyFlags, "reverse")
|
||||||
|
}
|
||||||
|
|
||||||
for filename, info := range p.fileInfoMap {
|
for filename, info := range p.fileInfoMap {
|
||||||
if info.mode == UNSELECTED {
|
if info.mode == UNSELECTED {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
applyFlags := []string{"index", "3way"}
|
patch += p.RenderPatchForFile(filename, true, reverse)
|
||||||
reverseOnGenerate := false
|
|
||||||
if reverse {
|
|
||||||
if info.mode == WHOLE {
|
|
||||||
applyFlags = append(applyFlags, "reverse")
|
|
||||||
} else {
|
|
||||||
reverseOnGenerate = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
// first run we try with the original header, then without
|
|
||||||
for _, keepOriginalHeader := range []bool{true, false} {
|
|
||||||
patch := p.RenderPatchForFile(filename, true, reverseOnGenerate, keepOriginalHeader)
|
|
||||||
if patch == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err = p.applyPatch(patch, applyFlags...); err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return p.applyPatch(patch, applyFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// clears the patch
|
// clears the patch
|
||||||
|
@ -13,6 +13,19 @@ var (
|
|||||||
patchHeaderRegexp = regexp.MustCompile(`(?ms)(^diff.*?)^@@`)
|
patchHeaderRegexp = regexp.MustCompile(`(?ms)(^diff.*?)^@@`)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type PatchOptions struct {
|
||||||
|
// Create a patch that will applied in reverse with `git apply --reverse`.
|
||||||
|
// This affects how unselected lines are treated when only parts of a hunk
|
||||||
|
// are selected: usually, for unselected lines we change '-' lines to
|
||||||
|
// context lines and remove '+' lines, but when Reverse is true we need to
|
||||||
|
// turn '+' lines into context lines and remove '-' lines.
|
||||||
|
Reverse bool
|
||||||
|
|
||||||
|
// Whether to keep or discard the original diff header including the
|
||||||
|
// "index deadbeef..fa1afe1 100644" line.
|
||||||
|
KeepOriginalHeader bool
|
||||||
|
}
|
||||||
|
|
||||||
func GetHeaderFromDiff(diff string) string {
|
func GetHeaderFromDiff(diff string) string {
|
||||||
match := patchHeaderRegexp.FindStringSubmatch(diff)
|
match := patchHeaderRegexp.FindStringSubmatch(diff)
|
||||||
if len(match) <= 1 {
|
if len(match) <= 1 {
|
||||||
@ -76,7 +89,7 @@ func NewPatchModifier(log *logrus.Entry, filename string, diffText string) *Patc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *PatchModifier) ModifiedPatchForLines(lineIndices []int, reverse bool, keepOriginalHeader bool) string {
|
func (d *PatchModifier) ModifiedPatchForLines(lineIndices []int, opts PatchOptions) string {
|
||||||
// step one is getting only those hunks which we care about
|
// step one is getting only those hunks which we care about
|
||||||
hunksInRange := []*PatchHunk{}
|
hunksInRange := []*PatchHunk{}
|
||||||
outer:
|
outer:
|
||||||
@ -95,7 +108,8 @@ outer:
|
|||||||
formattedHunks := ""
|
formattedHunks := ""
|
||||||
var formattedHunk string
|
var formattedHunk string
|
||||||
for _, hunk := range hunksInRange {
|
for _, hunk := range hunksInRange {
|
||||||
startOffset, formattedHunk = hunk.formatWithChanges(lineIndices, reverse, startOffset)
|
startOffset, formattedHunk = hunk.formatWithChanges(
|
||||||
|
lineIndices, opts.Reverse, startOffset)
|
||||||
formattedHunks += formattedHunk
|
formattedHunks += formattedHunk
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +122,7 @@ outer:
|
|||||||
// it makes git confused e.g. when dealing with deleted/added files
|
// it makes git confused e.g. when dealing with deleted/added files
|
||||||
// but with building and applying patches the original header gives git
|
// but with building and applying patches the original header gives git
|
||||||
// information it needs to cleanly apply patches
|
// information it needs to cleanly apply patches
|
||||||
if keepOriginalHeader {
|
if opts.KeepOriginalHeader {
|
||||||
fileHeader = d.header
|
fileHeader = d.header
|
||||||
} else {
|
} else {
|
||||||
fileHeader = fmt.Sprintf("--- a/%s\n+++ b/%s\n", d.filename, d.filename)
|
fileHeader = fmt.Sprintf("--- a/%s\n+++ b/%s\n", d.filename, d.filename)
|
||||||
@ -117,13 +131,13 @@ outer:
|
|||||||
return fileHeader + formattedHunks
|
return fileHeader + formattedHunks
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *PatchModifier) ModifiedPatchForRange(firstLineIdx int, lastLineIdx int, reverse bool, keepOriginalHeader bool) string {
|
func (d *PatchModifier) ModifiedPatchForRange(firstLineIdx int, lastLineIdx int, opts PatchOptions) string {
|
||||||
// generate array of consecutive line indices from our range
|
// generate array of consecutive line indices from our range
|
||||||
selectedLines := []int{}
|
selectedLines := []int{}
|
||||||
for i := firstLineIdx; i <= lastLineIdx; i++ {
|
for i := firstLineIdx; i <= lastLineIdx; i++ {
|
||||||
selectedLines = append(selectedLines, i)
|
selectedLines = append(selectedLines, i)
|
||||||
}
|
}
|
||||||
return d.ModifiedPatchForLines(selectedLines, reverse, keepOriginalHeader)
|
return d.ModifiedPatchForLines(selectedLines, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *PatchModifier) OriginalPatchLength() int {
|
func (d *PatchModifier) OriginalPatchLength() int {
|
||||||
@ -134,14 +148,14 @@ func (d *PatchModifier) OriginalPatchLength() int {
|
|||||||
return d.hunks[len(d.hunks)-1].LastLineIdx()
|
return d.hunks[len(d.hunks)-1].LastLineIdx()
|
||||||
}
|
}
|
||||||
|
|
||||||
func ModifiedPatchForRange(log *logrus.Entry, filename string, diffText string, firstLineIdx int, lastLineIdx int, reverse bool, keepOriginalHeader bool) string {
|
func ModifiedPatchForRange(log *logrus.Entry, filename string, diffText string, firstLineIdx int, lastLineIdx int, opts PatchOptions) string {
|
||||||
p := NewPatchModifier(log, filename, diffText)
|
p := NewPatchModifier(log, filename, diffText)
|
||||||
return p.ModifiedPatchForRange(firstLineIdx, lastLineIdx, reverse, keepOriginalHeader)
|
return p.ModifiedPatchForRange(firstLineIdx, lastLineIdx, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ModifiedPatchForLines(log *logrus.Entry, filename string, diffText string, includedLineIndices []int, reverse bool, keepOriginalHeader bool) string {
|
func ModifiedPatchForLines(log *logrus.Entry, filename string, diffText string, includedLineIndices []int, opts PatchOptions) string {
|
||||||
p := NewPatchModifier(log, filename, diffText)
|
p := NewPatchModifier(log, filename, diffText)
|
||||||
return p.ModifiedPatchForLines(includedLineIndices, reverse, keepOriginalHeader)
|
return p.ModifiedPatchForLines(includedLineIndices, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// I want to know, given a hunk, what line a given index is on
|
// I want to know, given a hunk, what line a given index is on
|
||||||
|
@ -69,6 +69,20 @@ index e48a11c..b2ab81b 100644
|
|||||||
...
|
...
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const twoChangesInOneHunk = `diff --git a/filename b/filename
|
||||||
|
index 9320895..6d79956 100644
|
||||||
|
--- a/filename
|
||||||
|
+++ b/filename
|
||||||
|
@@ -1,5 +1,5 @@
|
||||||
|
apple
|
||||||
|
-grape
|
||||||
|
+kiwi
|
||||||
|
orange
|
||||||
|
-pear
|
||||||
|
+banana
|
||||||
|
lemon
|
||||||
|
`
|
||||||
|
|
||||||
const newFile = `diff --git a/newfile b/newfile
|
const newFile = `diff --git a/newfile b/newfile
|
||||||
new file mode 100644
|
new file mode 100644
|
||||||
index 0000000..4e680cc
|
index 0000000..4e680cc
|
||||||
@ -116,7 +130,6 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
filename: "filename",
|
filename: "filename",
|
||||||
firstLineIndex: -1,
|
firstLineIndex: -1,
|
||||||
lastLineIndex: -1,
|
lastLineIndex: -1,
|
||||||
reverse: false,
|
|
||||||
diffText: simpleDiff,
|
diffText: simpleDiff,
|
||||||
expected: "",
|
expected: "",
|
||||||
},
|
},
|
||||||
@ -125,7 +138,6 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
filename: "filename",
|
filename: "filename",
|
||||||
firstLineIndex: 5,
|
firstLineIndex: 5,
|
||||||
lastLineIndex: 5,
|
lastLineIndex: 5,
|
||||||
reverse: false,
|
|
||||||
diffText: simpleDiff,
|
diffText: simpleDiff,
|
||||||
expected: "",
|
expected: "",
|
||||||
},
|
},
|
||||||
@ -134,7 +146,6 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
filename: "filename",
|
filename: "filename",
|
||||||
firstLineIndex: 0,
|
firstLineIndex: 0,
|
||||||
lastLineIndex: 11,
|
lastLineIndex: 11,
|
||||||
reverse: false,
|
|
||||||
diffText: simpleDiff,
|
diffText: simpleDiff,
|
||||||
expected: `--- a/filename
|
expected: `--- a/filename
|
||||||
+++ b/filename
|
+++ b/filename
|
||||||
@ -152,7 +163,6 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
filename: "filename",
|
filename: "filename",
|
||||||
firstLineIndex: 6,
|
firstLineIndex: 6,
|
||||||
lastLineIndex: 6,
|
lastLineIndex: 6,
|
||||||
reverse: false,
|
|
||||||
diffText: simpleDiff,
|
diffText: simpleDiff,
|
||||||
expected: `--- a/filename
|
expected: `--- a/filename
|
||||||
+++ b/filename
|
+++ b/filename
|
||||||
@ -169,7 +179,6 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
filename: "filename",
|
filename: "filename",
|
||||||
firstLineIndex: 7,
|
firstLineIndex: 7,
|
||||||
lastLineIndex: 7,
|
lastLineIndex: 7,
|
||||||
reverse: false,
|
|
||||||
diffText: simpleDiff,
|
diffText: simpleDiff,
|
||||||
expected: `--- a/filename
|
expected: `--- a/filename
|
||||||
+++ b/filename
|
+++ b/filename
|
||||||
@ -187,7 +196,6 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
filename: "filename",
|
filename: "filename",
|
||||||
firstLineIndex: -100,
|
firstLineIndex: -100,
|
||||||
lastLineIndex: 100,
|
lastLineIndex: 100,
|
||||||
reverse: false,
|
|
||||||
diffText: simpleDiff,
|
diffText: simpleDiff,
|
||||||
expected: `--- a/filename
|
expected: `--- a/filename
|
||||||
+++ b/filename
|
+++ b/filename
|
||||||
@ -198,59 +206,6 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
...
|
...
|
||||||
...
|
...
|
||||||
...
|
...
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "whole range reversed",
|
|
||||||
filename: "filename",
|
|
||||||
firstLineIndex: 0,
|
|
||||||
lastLineIndex: 11,
|
|
||||||
reverse: true,
|
|
||||||
diffText: simpleDiff,
|
|
||||||
expected: `--- a/filename
|
|
||||||
+++ b/filename
|
|
||||||
@@ -1,5 +1,5 @@
|
|
||||||
apple
|
|
||||||
+orange
|
|
||||||
-grape
|
|
||||||
...
|
|
||||||
...
|
|
||||||
...
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "removal reversed",
|
|
||||||
filename: "filename",
|
|
||||||
firstLineIndex: 6,
|
|
||||||
lastLineIndex: 6,
|
|
||||||
reverse: true,
|
|
||||||
diffText: simpleDiff,
|
|
||||||
expected: `--- a/filename
|
|
||||||
+++ b/filename
|
|
||||||
@@ -1,5 +1,6 @@
|
|
||||||
apple
|
|
||||||
+orange
|
|
||||||
grape
|
|
||||||
...
|
|
||||||
...
|
|
||||||
...
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "removal reversed",
|
|
||||||
filename: "filename",
|
|
||||||
firstLineIndex: 7,
|
|
||||||
lastLineIndex: 7,
|
|
||||||
reverse: true,
|
|
||||||
diffText: simpleDiff,
|
|
||||||
expected: `--- a/filename
|
|
||||||
+++ b/filename
|
|
||||||
@@ -1,5 +1,4 @@
|
|
||||||
apple
|
|
||||||
-grape
|
|
||||||
...
|
|
||||||
...
|
|
||||||
...
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -258,7 +213,6 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
filename: "filename",
|
filename: "filename",
|
||||||
firstLineIndex: -100,
|
firstLineIndex: -100,
|
||||||
lastLineIndex: 100,
|
lastLineIndex: 100,
|
||||||
reverse: false,
|
|
||||||
diffText: addNewlineToEndOfFile,
|
diffText: addNewlineToEndOfFile,
|
||||||
expected: `--- a/filename
|
expected: `--- a/filename
|
||||||
+++ b/filename
|
+++ b/filename
|
||||||
@ -272,37 +226,21 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
testName: "add newline to end of file, addition only",
|
testName: "add newline to end of file, reversed",
|
||||||
filename: "filename",
|
filename: "filename",
|
||||||
firstLineIndex: 8,
|
firstLineIndex: -100,
|
||||||
lastLineIndex: 8,
|
lastLineIndex: 100,
|
||||||
reverse: true,
|
reverse: true,
|
||||||
diffText: addNewlineToEndOfFile,
|
diffText: addNewlineToEndOfFile,
|
||||||
expected: `--- a/filename
|
expected: `--- a/filename
|
||||||
+++ b/filename
|
+++ b/filename
|
||||||
@@ -60,4 +60,5 @@ grape
|
@@ -60,4 +60,4 @@ grape
|
||||||
...
|
|
||||||
...
|
|
||||||
...
|
|
||||||
+last line
|
|
||||||
\ No newline at end of file
|
|
||||||
last line
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "add newline to end of file, removal only",
|
|
||||||
filename: "filename",
|
|
||||||
firstLineIndex: 10,
|
|
||||||
lastLineIndex: 10,
|
|
||||||
reverse: true,
|
|
||||||
diffText: addNewlineToEndOfFile,
|
|
||||||
expected: `--- a/filename
|
|
||||||
+++ b/filename
|
|
||||||
@@ -60,4 +60,3 @@ grape
|
|
||||||
...
|
...
|
||||||
...
|
...
|
||||||
...
|
...
|
||||||
-last line
|
-last line
|
||||||
|
\ No newline at end of file
|
||||||
|
+last line
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -310,7 +248,24 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
filename: "filename",
|
filename: "filename",
|
||||||
firstLineIndex: -100,
|
firstLineIndex: -100,
|
||||||
lastLineIndex: 100,
|
lastLineIndex: 100,
|
||||||
reverse: false,
|
diffText: removeNewlinefromEndOfFile,
|
||||||
|
expected: `--- a/filename
|
||||||
|
+++ b/filename
|
||||||
|
@@ -60,4 +60,4 @@ grape
|
||||||
|
...
|
||||||
|
...
|
||||||
|
...
|
||||||
|
-last line
|
||||||
|
+last line
|
||||||
|
\ No newline at end of file
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "remove newline from end of file, reversed",
|
||||||
|
filename: "filename",
|
||||||
|
firstLineIndex: -100,
|
||||||
|
lastLineIndex: 100,
|
||||||
|
reverse: true,
|
||||||
diffText: removeNewlinefromEndOfFile,
|
diffText: removeNewlinefromEndOfFile,
|
||||||
expected: `--- a/filename
|
expected: `--- a/filename
|
||||||
+++ b/filename
|
+++ b/filename
|
||||||
@ -328,7 +283,6 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
filename: "filename",
|
filename: "filename",
|
||||||
firstLineIndex: 8,
|
firstLineIndex: 8,
|
||||||
lastLineIndex: 8,
|
lastLineIndex: 8,
|
||||||
reverse: false,
|
|
||||||
diffText: removeNewlinefromEndOfFile,
|
diffText: removeNewlinefromEndOfFile,
|
||||||
expected: `--- a/filename
|
expected: `--- a/filename
|
||||||
+++ b/filename
|
+++ b/filename
|
||||||
@ -337,6 +291,24 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
...
|
...
|
||||||
...
|
...
|
||||||
-last line
|
-last line
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "remove newline from end of file, removal only, reversed",
|
||||||
|
filename: "filename",
|
||||||
|
firstLineIndex: 8,
|
||||||
|
lastLineIndex: 8,
|
||||||
|
reverse: true,
|
||||||
|
diffText: removeNewlinefromEndOfFile,
|
||||||
|
expected: `--- a/filename
|
||||||
|
+++ b/filename
|
||||||
|
@@ -60,5 +60,4 @@ grape
|
||||||
|
...
|
||||||
|
...
|
||||||
|
...
|
||||||
|
-last line
|
||||||
|
last line
|
||||||
|
\ No newline at end of file
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -344,7 +316,6 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
filename: "filename",
|
filename: "filename",
|
||||||
firstLineIndex: 9,
|
firstLineIndex: 9,
|
||||||
lastLineIndex: 9,
|
lastLineIndex: 9,
|
||||||
reverse: false,
|
|
||||||
diffText: removeNewlinefromEndOfFile,
|
diffText: removeNewlinefromEndOfFile,
|
||||||
expected: `--- a/filename
|
expected: `--- a/filename
|
||||||
+++ b/filename
|
+++ b/filename
|
||||||
@ -355,6 +326,23 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
last line
|
last line
|
||||||
+last line
|
+last line
|
||||||
\ No newline at end of file
|
\ No newline at end of file
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "remove newline from end of file, addition only, reversed",
|
||||||
|
filename: "filename",
|
||||||
|
firstLineIndex: 9,
|
||||||
|
lastLineIndex: 9,
|
||||||
|
reverse: true,
|
||||||
|
diffText: removeNewlinefromEndOfFile,
|
||||||
|
expected: `--- a/filename
|
||||||
|
+++ b/filename
|
||||||
|
@@ -60,3 +60,4 @@ grape
|
||||||
|
...
|
||||||
|
...
|
||||||
|
...
|
||||||
|
+last line
|
||||||
|
\ No newline at end of file
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -362,7 +350,6 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
filename: "filename",
|
filename: "filename",
|
||||||
firstLineIndex: -100,
|
firstLineIndex: -100,
|
||||||
lastLineIndex: 100,
|
lastLineIndex: 100,
|
||||||
reverse: false,
|
|
||||||
diffText: twoHunks,
|
diffText: twoHunks,
|
||||||
expected: `--- a/filename
|
expected: `--- a/filename
|
||||||
+++ b/filename
|
+++ b/filename
|
||||||
@ -389,7 +376,6 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
filename: "filename",
|
filename: "filename",
|
||||||
firstLineIndex: 7,
|
firstLineIndex: 7,
|
||||||
lastLineIndex: 15,
|
lastLineIndex: 15,
|
||||||
reverse: false,
|
|
||||||
diffText: twoHunks,
|
diffText: twoHunks,
|
||||||
expected: `--- a/filename
|
expected: `--- a/filename
|
||||||
+++ b/filename
|
+++ b/filename
|
||||||
@ -408,32 +394,6 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
...
|
...
|
||||||
...
|
...
|
||||||
...
|
...
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "staging part of both hunks, reversed",
|
|
||||||
filename: "filename",
|
|
||||||
firstLineIndex: 7,
|
|
||||||
lastLineIndex: 15,
|
|
||||||
reverse: true,
|
|
||||||
diffText: twoHunks,
|
|
||||||
expected: `--- a/filename
|
|
||||||
+++ b/filename
|
|
||||||
@@ -1,5 +1,4 @@
|
|
||||||
apple
|
|
||||||
-orange
|
|
||||||
...
|
|
||||||
...
|
|
||||||
...
|
|
||||||
@@ -8,8 +7,7 @@ grape
|
|
||||||
...
|
|
||||||
...
|
|
||||||
...
|
|
||||||
-pear
|
|
||||||
lemon
|
|
||||||
...
|
|
||||||
...
|
|
||||||
...
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -441,7 +401,6 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
filename: "newfile",
|
filename: "newfile",
|
||||||
firstLineIndex: -100,
|
firstLineIndex: -100,
|
||||||
lastLineIndex: 100,
|
lastLineIndex: 100,
|
||||||
reverse: false,
|
|
||||||
diffText: newFile,
|
diffText: newFile,
|
||||||
expected: `--- a/newfile
|
expected: `--- a/newfile
|
||||||
+++ b/newfile
|
+++ b/newfile
|
||||||
@ -456,28 +415,12 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
filename: "newfile",
|
filename: "newfile",
|
||||||
firstLineIndex: 6,
|
firstLineIndex: 6,
|
||||||
lastLineIndex: 7,
|
lastLineIndex: 7,
|
||||||
reverse: false,
|
|
||||||
diffText: newFile,
|
diffText: newFile,
|
||||||
expected: `--- a/newfile
|
expected: `--- a/newfile
|
||||||
+++ b/newfile
|
+++ b/newfile
|
||||||
@@ -0,0 +1,2 @@
|
@@ -0,0 +1,2 @@
|
||||||
+apple
|
+apple
|
||||||
+orange
|
+orange
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "adding a new file, reversed",
|
|
||||||
filename: "newfile",
|
|
||||||
firstLineIndex: -100,
|
|
||||||
lastLineIndex: 100,
|
|
||||||
reverse: true,
|
|
||||||
diffText: newFile,
|
|
||||||
expected: `--- a/newfile
|
|
||||||
+++ b/newfile
|
|
||||||
@@ -1,3 +0,0 @@
|
|
||||||
-apple
|
|
||||||
-orange
|
|
||||||
-grape
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -485,7 +428,6 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
filename: "newfile",
|
filename: "newfile",
|
||||||
firstLineIndex: -100,
|
firstLineIndex: -100,
|
||||||
lastLineIndex: 100,
|
lastLineIndex: 100,
|
||||||
reverse: false,
|
|
||||||
diffText: addNewlineToPreviouslyEmptyFile,
|
diffText: addNewlineToPreviouslyEmptyFile,
|
||||||
expected: `--- a/newfile
|
expected: `--- a/newfile
|
||||||
+++ b/newfile
|
+++ b/newfile
|
||||||
@ -499,13 +441,49 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
filename: "newfile",
|
filename: "newfile",
|
||||||
firstLineIndex: -100,
|
firstLineIndex: -100,
|
||||||
lastLineIndex: 100,
|
lastLineIndex: 100,
|
||||||
reverse: true,
|
|
||||||
diffText: addNewlineToPreviouslyEmptyFile,
|
diffText: addNewlineToPreviouslyEmptyFile,
|
||||||
|
reverse: true,
|
||||||
expected: `--- a/newfile
|
expected: `--- a/newfile
|
||||||
+++ b/newfile
|
+++ b/newfile
|
||||||
@@ -1,1 +0,0 @@
|
@@ -0,0 +1,1 @@
|
||||||
-new line
|
+new line
|
||||||
\ No newline at end of file
|
\ No newline at end of file
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "adding part of a hunk",
|
||||||
|
filename: "filename",
|
||||||
|
firstLineIndex: 6,
|
||||||
|
lastLineIndex: 7,
|
||||||
|
reverse: false,
|
||||||
|
diffText: twoChangesInOneHunk,
|
||||||
|
expected: `--- a/filename
|
||||||
|
+++ b/filename
|
||||||
|
@@ -1,5 +1,5 @@
|
||||||
|
apple
|
||||||
|
-grape
|
||||||
|
+kiwi
|
||||||
|
orange
|
||||||
|
pear
|
||||||
|
lemon
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "adding part of a hunk, reverse",
|
||||||
|
filename: "filename",
|
||||||
|
firstLineIndex: 6,
|
||||||
|
lastLineIndex: 7,
|
||||||
|
reverse: true,
|
||||||
|
diffText: twoChangesInOneHunk,
|
||||||
|
expected: `--- a/filename
|
||||||
|
+++ b/filename
|
||||||
|
@@ -1,5 +1,5 @@
|
||||||
|
apple
|
||||||
|
-grape
|
||||||
|
+kiwi
|
||||||
|
orange
|
||||||
|
banana
|
||||||
|
lemon
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -513,7 +491,11 @@ func TestModifyPatchForRange(t *testing.T) {
|
|||||||
for _, s := range scenarios {
|
for _, s := range scenarios {
|
||||||
s := s
|
s := s
|
||||||
t.Run(s.testName, func(t *testing.T) {
|
t.Run(s.testName, func(t *testing.T) {
|
||||||
result := ModifiedPatchForRange(nil, s.filename, s.diffText, s.firstLineIndex, s.lastLineIndex, s.reverse, false)
|
result := ModifiedPatchForRange(nil, s.filename, s.diffText, s.firstLineIndex, s.lastLineIndex,
|
||||||
|
PatchOptions{
|
||||||
|
Reverse: s.reverse,
|
||||||
|
KeepOriginalHeader: false,
|
||||||
|
})
|
||||||
if !assert.Equal(t, s.expected, result) {
|
if !assert.Equal(t, s.expected, result) {
|
||||||
fmt.Println(result)
|
fmt.Println(result)
|
||||||
}
|
}
|
||||||
|
@ -181,7 +181,8 @@ func (self *StagingController) applySelection(reverse bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
firstLineIdx, lastLineIdx := state.SelectedRange()
|
firstLineIdx, lastLineIdx := state.SelectedRange()
|
||||||
patch := patch.ModifiedPatchForRange(self.c.Log, path, state.GetDiff(), firstLineIdx, lastLineIdx, reverse, false)
|
patch := patch.ModifiedPatchForRange(self.c.Log, path, state.GetDiff(), firstLineIdx, lastLineIdx,
|
||||||
|
patch.PatchOptions{Reverse: reverse, KeepOriginalHeader: false})
|
||||||
|
|
||||||
if patch == "" {
|
if patch == "" {
|
||||||
return nil
|
return nil
|
||||||
@ -190,6 +191,9 @@ func (self *StagingController) applySelection(reverse bool) error {
|
|||||||
// apply the patch then refresh this panel
|
// apply the patch then refresh this panel
|
||||||
// create a new temp file with the patch, then call git apply with that patch
|
// create a new temp file with the patch, then call git apply with that patch
|
||||||
applyFlags := []string{}
|
applyFlags := []string{}
|
||||||
|
if reverse {
|
||||||
|
applyFlags = append(applyFlags, "reverse")
|
||||||
|
}
|
||||||
if !reverse || self.staged {
|
if !reverse || self.staged {
|
||||||
applyFlags = append(applyFlags, "cached")
|
applyFlags = append(applyFlags, "cached")
|
||||||
}
|
}
|
||||||
@ -227,7 +231,8 @@ func (self *StagingController) editHunk() error {
|
|||||||
|
|
||||||
hunk := state.CurrentHunk()
|
hunk := state.CurrentHunk()
|
||||||
patchText := patch.ModifiedPatchForRange(
|
patchText := patch.ModifiedPatchForRange(
|
||||||
self.c.Log, path, state.GetDiff(), hunk.FirstLineIdx, hunk.LastLineIdx(), self.staged, false,
|
self.c.Log, path, state.GetDiff(), hunk.FirstLineIdx, hunk.LastLineIdx(),
|
||||||
|
patch.PatchOptions{Reverse: self.staged, KeepOriginalHeader: false},
|
||||||
)
|
)
|
||||||
patchFilepath, err := self.git.WorkingTree.SaveTemporaryPatch(patchText)
|
patchFilepath, err := self.git.WorkingTree.SaveTemporaryPatch(patchText)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -249,9 +254,15 @@ func (self *StagingController) editHunk() error {
|
|||||||
|
|
||||||
lineCount := strings.Count(editedPatchText, "\n") + 1
|
lineCount := strings.Count(editedPatchText, "\n") + 1
|
||||||
newPatchText := patch.ModifiedPatchForRange(
|
newPatchText := patch.ModifiedPatchForRange(
|
||||||
self.c.Log, path, editedPatchText, 0, lineCount, false, false,
|
self.c.Log, path, editedPatchText, 0, lineCount,
|
||||||
|
patch.PatchOptions{KeepOriginalHeader: false},
|
||||||
)
|
)
|
||||||
if err := self.git.WorkingTree.ApplyPatch(newPatchText, "cached"); err != nil {
|
|
||||||
|
applyFlags := []string{"cached"}
|
||||||
|
if self.staged {
|
||||||
|
applyFlags = append(applyFlags, "reverse")
|
||||||
|
}
|
||||||
|
if err := self.git.WorkingTree.ApplyPatch(newPatchText, applyFlags...); err != nil {
|
||||||
return self.c.Error(err)
|
return self.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -672,7 +672,7 @@ func (gui *Gui) refreshPatchBuildingPanel(opts types.OnFocusOpts) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
secondaryDiff := gui.git.Patch.PatchManager.RenderPatchForFile(path, false, false, true)
|
secondaryDiff := gui.git.Patch.PatchManager.RenderPatchForFile(path, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -121,7 +121,7 @@ func RunTUI() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("code -r pkg/integration/tests/%s", currentTest.Name()))
|
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("code -r pkg/integration/tests/%s.go", currentTest.Name()))
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,92 @@
|
|||||||
|
package patch_building
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ApplyInReverseWithConflict = NewIntegrationTest(NewIntegrationTestArgs{
|
||||||
|
Description: "Apply a custom patch in reverse, resulting in a conflict",
|
||||||
|
ExtraCmdArgs: "",
|
||||||
|
Skip: false,
|
||||||
|
SetupConfig: func(config *config.AppConfig) {},
|
||||||
|
SetupRepo: func(shell *Shell) {
|
||||||
|
shell.CreateFileAndAdd("file1", "file1 content\n")
|
||||||
|
shell.CreateFileAndAdd("file2", "file2 content\n")
|
||||||
|
shell.Commit("first commit")
|
||||||
|
shell.UpdateFileAndAdd("file1", "file1 content\nmore file1 content\n")
|
||||||
|
shell.UpdateFileAndAdd("file2", "file2 content\nmore file2 content\n")
|
||||||
|
shell.Commit("second commit")
|
||||||
|
shell.UpdateFileAndAdd("file1", "file1 content\nmore file1 content\neven more file1\n")
|
||||||
|
shell.Commit("third commit")
|
||||||
|
},
|
||||||
|
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||||
|
t.Views().Commits().
|
||||||
|
Focus().
|
||||||
|
Lines(
|
||||||
|
Contains("third commit").IsSelected(),
|
||||||
|
Contains("second commit"),
|
||||||
|
Contains("first commit"),
|
||||||
|
).
|
||||||
|
NavigateToLine(Contains("second commit")).
|
||||||
|
PressEnter()
|
||||||
|
|
||||||
|
t.Views().CommitFiles().
|
||||||
|
IsFocused().
|
||||||
|
Lines(
|
||||||
|
Contains("M").Contains("file1").IsSelected(),
|
||||||
|
Contains("M").Contains("file2"),
|
||||||
|
).
|
||||||
|
// Add both files to the patch; the first will conflict, the second won't
|
||||||
|
PressPrimaryAction().
|
||||||
|
SelectNextItem().
|
||||||
|
PressPrimaryAction()
|
||||||
|
|
||||||
|
t.Views().Information().Content(Contains("building patch"))
|
||||||
|
|
||||||
|
t.Views().PatchBuildingSecondary().Content(
|
||||||
|
Contains("+more file1 content").Contains("+more file2 content"))
|
||||||
|
|
||||||
|
t.Common().SelectPatchOption(Contains("apply patch in reverse"))
|
||||||
|
|
||||||
|
t.ExpectPopup().Alert().
|
||||||
|
Title(Equals("Error")).
|
||||||
|
Content(Contains("Applied patch to 'file1' with conflicts.").
|
||||||
|
Contains("Applied patch to 'file2' cleanly.")).
|
||||||
|
Confirm()
|
||||||
|
|
||||||
|
t.Views().Files().
|
||||||
|
Focus().
|
||||||
|
Lines(
|
||||||
|
Contains("UU").Contains("file1").IsSelected(),
|
||||||
|
).
|
||||||
|
PressPrimaryAction()
|
||||||
|
|
||||||
|
t.Views().MergeConflicts().
|
||||||
|
IsFocused().
|
||||||
|
ContainsLines(
|
||||||
|
Contains("file1 content"),
|
||||||
|
Contains("<<<<<<< ours").IsSelected(),
|
||||||
|
Contains("more file1 content").IsSelected(),
|
||||||
|
Contains("even more file1").IsSelected(),
|
||||||
|
Contains("=======").IsSelected(),
|
||||||
|
Contains(">>>>>>> theirs"),
|
||||||
|
).
|
||||||
|
SelectNextItem().
|
||||||
|
PressPrimaryAction()
|
||||||
|
|
||||||
|
t.Views().Files().
|
||||||
|
Focus().
|
||||||
|
Lines(
|
||||||
|
Contains("M").Contains("file1").IsSelected(),
|
||||||
|
Contains("M").Contains("file2"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Views().Main().
|
||||||
|
ContainsLines(
|
||||||
|
Contains(" file1 content"),
|
||||||
|
Contains("-more file1 content"),
|
||||||
|
Contains("-even more file1"),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
@ -8,7 +8,7 @@ import (
|
|||||||
var MoveToIndexWithConflict = NewIntegrationTest(NewIntegrationTestArgs{
|
var MoveToIndexWithConflict = NewIntegrationTest(NewIntegrationTestArgs{
|
||||||
Description: "Move a patch from a commit to the index, causing a conflict",
|
Description: "Move a patch from a commit to the index, causing a conflict",
|
||||||
ExtraCmdArgs: "",
|
ExtraCmdArgs: "",
|
||||||
Skip: true, // Skipping until https://github.com/jesseduffield/lazygit/pull/2471 is merged
|
Skip: false,
|
||||||
SetupConfig: func(config *config.AppConfig) {},
|
SetupConfig: func(config *config.AppConfig) {},
|
||||||
SetupRepo: func(shell *Shell) {
|
SetupRepo: func(shell *Shell) {
|
||||||
shell.CreateFileAndAdd("file1", "file1 content")
|
shell.CreateFileAndAdd("file1", "file1 content")
|
||||||
|
@ -99,6 +99,7 @@ var tests = []*components.IntegrationTest{
|
|||||||
misc.InitialOpen,
|
misc.InitialOpen,
|
||||||
patch_building.Apply,
|
patch_building.Apply,
|
||||||
patch_building.ApplyInReverse,
|
patch_building.ApplyInReverse,
|
||||||
|
patch_building.ApplyInReverseWithConflict,
|
||||||
patch_building.CopyPatchToClipboard,
|
patch_building.CopyPatchToClipboard,
|
||||||
patch_building.MoveToIndex,
|
patch_building.MoveToIndex,
|
||||||
patch_building.MoveToIndexPartial,
|
patch_building.MoveToIndexPartial,
|
||||||
|
Loading…
Reference in New Issue
Block a user