Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions gitdiff/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,19 @@ func Apply(dst io.Writer, src io.ReaderAt, f *File, opts ...ApplyOption) error {
return applier.Close()

case len(f.TextFragments) > 0:
// creating a file requires an empty source; a fragment starting at
// position 0 is not enough to detect this, as zero-context patches use
// the same position to insert before the first line of an existing file
if f.IsNew {
ok, err := isLen(src, 0)
if err != nil {
return applyError(err)
}
if !ok {
return applyError(&Conflict{"cannot create new file from non-empty src"})
}
}

frags := make([]*TextFragment, len(f.TextFragments))
copy(frags, f.TextFragments)

Expand All @@ -137,6 +150,14 @@ func Apply(dst io.Writer, src io.ReaderAt, f *File, opts ...ApplyOption) error {
return applyError(err, fragNum(i))
}
}
// deleting a file must consume the whole source; a fragment ending at
// new position 0 is not enough to detect this, as zero-context patches
// use the same position to delete the first line of a file
if f.IsDelete {
if err := applier.checkFullDelete(); err != nil {
return err
}
}
return applier.Close()

default:
Expand Down
28 changes: 21 additions & 7 deletions gitdiff/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ func TestApplyTextFragment(t *testing.T) {
"addEnd": {Files: getApplyFiles("text_fragment_add_end")},
"addEndNoEOL": {Files: getApplyFiles("text_fragment_add_end_noeol")},

// zero-context fragments: the old range is empty and OldPosition is the
// line the content follows, not the line it starts at
"insertStart": {Files: getApplyFiles("text_fragment_insert_start")},
"insertMiddle": {Files: getApplyFiles("text_fragment_insert_middle")},
"insertEnd": {Files: getApplyFiles("text_fragment_insert_end")},
"insertNoEOL": {Files: getApplyFiles("text_fragment_insert_noeol")},
"deleteStart": {Files: getApplyFiles("text_fragment_delete_start")},

"changeStart": {Files: getApplyFiles("text_fragment_change_start")},
"changeMiddle": {Files: getApplyFiles("text_fragment_change_middle")},
"changeEnd": {Files: getApplyFiles("text_fragment_change_end")},
Expand Down Expand Up @@ -68,13 +76,6 @@ func TestApplyTextFragment(t *testing.T) {
},
Err: &Conflict{},
},
"errorNewFile": {
Files: applyFiles{
Src: "text_fragment_error.src",
Patch: "text_fragment_error_new_file.patch",
},
Err: &Conflict{},
},
}

for name, test := range tests {
Expand Down Expand Up @@ -200,6 +201,19 @@ func TestApplyFile(t *testing.T) {
},
Err: &Conflict{},
},
"textInsertZeroContext": {
Files: getApplyFiles("file_text_insert_zero_context"),
},
"textDeleteStartZeroContext": {
Files: getApplyFiles("file_text_delete_start"),
},
"textErrorNewNonEmpty": {
Files: applyFiles{
Src: "file_text.src",
Patch: "file_text_error_new_non_empty.patch",
},
Err: &Conflict{},
},
"binaryModify": {
Files: getApplyFiles("file_bin_modify"),
},
Expand Down
42 changes: 20 additions & 22 deletions gitdiff/apply_text.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,13 @@ func (a *TextApplier) ApplyFragment(f *TextFragment) error {
return applyError(err)
}

// lines are 0-indexed, positions are 1-indexed (but new files have position = 0)
// lines are 0-indexed, positions are 1-indexed; a fragment with no old
// lines inserts after OldPosition instead of starting at it, so position 0
// inserts before the first line
fragStart := f.OldPosition - 1
if f.OldLines == 0 {
fragStart = f.OldPosition
}
if fragStart < 0 {
fragStart = 0
}
Expand All @@ -79,16 +84,6 @@ func (a *TextApplier) ApplyFragment(f *TextFragment) error {
return applyError(&Conflict{"fragment overlaps with an applied fragment"})
}

if f.OldPosition == 0 {
ok, err := isLen(a.src, 0)
if err != nil {
return applyError(err)
}
if !ok {
return applyError(&Conflict{"cannot create new file from non-empty src"})
}
}

preimage, err := readPreimage(a.lineSrc, start, fragEnd-start)
if err != nil {
return applyError(err)
Expand Down Expand Up @@ -116,18 +111,21 @@ func (a *TextApplier) ApplyFragment(f *TextFragment) error {
}
a.nextLine = fragStart + used

// new position of +0,0 mean a full delete, so check for leftovers
if f.NewPosition == 0 && f.NewLines == 0 {
var b [1][]byte
n, err := a.lineSrc.ReadLinesAt(b[:], a.nextLine)
if err != nil && err != io.EOF {
return applyError(err, lineNum(a.nextLine))
}
if n > 0 {
return applyError(&Conflict{"src still has content after full delete"}, lineNum(a.nextLine))
}
}
return nil
}

// checkFullDelete returns a *Conflict if the source contains content that was
// not consumed by the applied fragments. Deleting a file is a property of the
// file header, so only [Apply] can decide when this check applies.
func (a *TextApplier) checkFullDelete() error {
var b [1][]byte
n, err := a.lineSrc.ReadLinesAt(b[:], a.nextLine)
if err != nil && err != io.EOF {
return applyError(err, lineNum(a.nextLine))
}
if n > 0 {
return applyError(&Conflict{"src still has content after full delete"}, lineNum(a.nextLine))
}
return nil
}

Expand Down
5 changes: 5 additions & 0 deletions gitdiff/testdata/apply/file_text_delete_start.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
line 2
line 3
line 4
line 5
line 6
5 changes: 5 additions & 0 deletions gitdiff/testdata/apply/file_text_delete_start.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
diff --git a/gitdiff/testdata/apply/file_text_delete_start.src b/gitdiff/testdata/apply/file_text_delete_start.src
--- a/gitdiff/testdata/apply/file_text_delete_start.src
+++ b/gitdiff/testdata/apply/file_text_delete_start.src
@@ -1 +0,0 @@
-line 1
6 changes: 6 additions & 0 deletions gitdiff/testdata/apply/file_text_delete_start.src
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
line 1
line 2
line 3
line 4
line 5
line 6
8 changes: 8 additions & 0 deletions gitdiff/testdata/apply/file_text_error_new_non_empty.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
diff --git a/gitdiff/testdata/apply/file_text.src b/gitdiff/testdata/apply/file_text.src
new file mode 100644
--- /dev/null
+++ b/gitdiff/testdata/apply/file_text.src
@@ -0,0 +1,3 @@
+this is line 1
+this is line 2
+this is line 3
8 changes: 8 additions & 0 deletions gitdiff/testdata/apply/file_text_insert_zero_context.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
new line a
line 1
line 2
line 3
line 4
line 5
line 6
new line b
7 changes: 7 additions & 0 deletions gitdiff/testdata/apply/file_text_insert_zero_context.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
diff --git a/gitdiff/testdata/apply/file_text_insert_zero_context.src b/gitdiff/testdata/apply/file_text_insert_zero_context.src
--- a/gitdiff/testdata/apply/file_text_insert_zero_context.src
+++ b/gitdiff/testdata/apply/file_text_insert_zero_context.src
@@ -0,0 +1 @@
+new line a
@@ -6,0 +8 @@ line 6
+new line b
6 changes: 6 additions & 0 deletions gitdiff/testdata/apply/file_text_insert_zero_context.src
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
line 1
line 2
line 3
line 4
line 5
line 6
Empty file.
5 changes: 5 additions & 0 deletions gitdiff/testdata/apply/text_fragment_delete_start.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
diff --git a/gitdiff/testdata/apply/fragment_delete_start.src b/gitdiff/testdata/apply/fragment_delete_start.src
--- a/gitdiff/testdata/apply/fragment_delete_start.src
+++ b/gitdiff/testdata/apply/fragment_delete_start.src
@@ -1 +0,0 @@
-line 1
3 changes: 3 additions & 0 deletions gitdiff/testdata/apply/text_fragment_delete_start.src
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
line 1
line 2
line 3
7 changes: 0 additions & 7 deletions gitdiff/testdata/apply/text_fragment_error_new_file.patch

This file was deleted.

5 changes: 5 additions & 0 deletions gitdiff/testdata/apply/text_fragment_insert_end.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
line 1
line 2
line 3
new line a
new line b
6 changes: 6 additions & 0 deletions gitdiff/testdata/apply/text_fragment_insert_end.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
diff --git a/gitdiff/testdata/apply/fragment_insert_end.src b/gitdiff/testdata/apply/fragment_insert_end.src
--- a/gitdiff/testdata/apply/fragment_insert_end.src
+++ b/gitdiff/testdata/apply/fragment_insert_end.src
@@ -3,0 +4,2 @@ line 3
+new line a
+new line b
3 changes: 3 additions & 0 deletions gitdiff/testdata/apply/text_fragment_insert_end.src
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
line 1
line 2
line 3
3 changes: 3 additions & 0 deletions gitdiff/testdata/apply/text_fragment_insert_middle.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
line 1
line 2
new line a
5 changes: 5 additions & 0 deletions gitdiff/testdata/apply/text_fragment_insert_middle.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
diff --git a/gitdiff/testdata/apply/fragment_insert_middle.src b/gitdiff/testdata/apply/fragment_insert_middle.src
--- a/gitdiff/testdata/apply/fragment_insert_middle.src
+++ b/gitdiff/testdata/apply/fragment_insert_middle.src
@@ -2,0 +3 @@ line 2
+new line a
3 changes: 3 additions & 0 deletions gitdiff/testdata/apply/text_fragment_insert_middle.src
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
line 1
line 2
line 3
2 changes: 2 additions & 0 deletions gitdiff/testdata/apply/text_fragment_insert_noeol.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
line 1
new line a
5 changes: 5 additions & 0 deletions gitdiff/testdata/apply/text_fragment_insert_noeol.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
diff --git a/gitdiff/testdata/apply/fragment_insert_noeol.src b/gitdiff/testdata/apply/fragment_insert_noeol.src
--- a/gitdiff/testdata/apply/fragment_insert_noeol.src
+++ b/gitdiff/testdata/apply/fragment_insert_noeol.src
@@ -1,0 +2 @@ line 1
+new line a
3 changes: 3 additions & 0 deletions gitdiff/testdata/apply/text_fragment_insert_noeol.src
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
line 1
line 2
line 3
2 changes: 2 additions & 0 deletions gitdiff/testdata/apply/text_fragment_insert_start.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
new line a
new line b
6 changes: 6 additions & 0 deletions gitdiff/testdata/apply/text_fragment_insert_start.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
diff --git a/gitdiff/testdata/apply/fragment_insert_start.src b/gitdiff/testdata/apply/fragment_insert_start.src
--- a/gitdiff/testdata/apply/fragment_insert_start.src
+++ b/gitdiff/testdata/apply/fragment_insert_start.src
@@ -0,0 +1,2 @@
+new line a
+new line b
3 changes: 3 additions & 0 deletions gitdiff/testdata/apply/text_fragment_insert_start.src
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
line 1
line 2
line 3