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
74 changes: 74 additions & 0 deletions diff/reverse.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,83 @@ func reverseExtendedHeaders(headers []string, origName, newName string) ([]strin
}
swapHeaderValues(reversed, parsed, gitModeHeaderPair)
swapHeaderValues(reversed, parsed, gitRenameHeaderPair)
reverseBinaryPatchSections(reversed)
return reversed, nil
}

// reverseBinaryPatchSections swaps the two encoded sections of a GIT binary
// patch. git apply uses the first section as the forward result and the
// second as the reverse result.
func reverseBinaryPatchSections(headers []string) {
start := -1
for i, header := range headers {
if strings.TrimRight(header, "\r") == gitExtendedHeaderBinaryPatch {
start = i
break
}
}
if start < 0 || start+1 >= len(headers) {
return
}
first, rest, ok := takeBinarySection(headers[start+1:])
if !ok {
return
}
second, tail, ok := takeBinarySection(rest)
if !ok {
return
}
swapped := make([]string, 0, len(headers))
swapped = append(swapped, headers[:start+1]...)
swapped = append(swapped, second...)
swapped = append(swapped, first...)
swapped = append(swapped, tail...)
copy(headers, swapped)
}

func takeBinarySection(lines []string) (section, rest []string, ok bool) {
if len(lines) == 0 || !isBinarySectionHeader(lines[0]) {
return nil, lines, false
}
end := 1
for end < len(lines) {
if isBinarySectionHeader(lines[end]) {
break
}
end++
if isBlankHeader(lines[end-1]) {
break
}
}
return lines[:end], lines[end:], true
}

func isBinarySectionHeader(line string) bool {
line = strings.TrimRight(line, "\r")
var n string
switch {
case strings.HasPrefix(line, "literal "):
n = strings.TrimPrefix(line, "literal ")
case strings.HasPrefix(line, "delta "):
n = strings.TrimPrefix(line, "delta ")
default:
return false
}
if n == "" {
return false
}
for i := 0; i < len(n); i++ {
if n[i] < '0' || n[i] > '9' {
return false
}
}
return true
}

func isBlankHeader(line string) bool {
return strings.TrimRight(line, "\r") == ""
}

// reverseDiffGitHeader swaps the two path arguments while preserving their
// original quoting. The parsed filenames are used only to reject malformed or
// ambiguous input; names recovered from other headers can disambiguate Git's
Expand Down
33 changes: 25 additions & 8 deletions diff/reverse_git_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ func TestReverseFileDiffGitSemantics(t *testing.T) {
}
}

// Git binary patches contain both directions. Reversing the index hashes makes
// git apply select the inverse payload without reordering the encoded sections.
// git apply uses the first encoded section as the forward result and the
// second as the reverse result. Reversing a patch swaps those sections.
func TestReverseFileDiffGitBinarySemantics(t *testing.T) {
tests := []struct {
name string
Expand All @@ -134,10 +134,10 @@ func TestReverseFileDiffGitBinarySemantics(t *testing.T) {
reverse: "diff --git b/literal.bin a/literal.bin\n" +
"index e0d84f457d2b5c802ecd286e7e576365d94af3b9..acdc746399905e5ea0d9d4163d2b3c4d04b05123 100644\n" +
"GIT binary patch\n" +
"literal 8\n" +
"PcmZQzWMXDvWn%{b09*ha\n\n" +
"literal 64\n" +
"LcmZ>CqznK65PlFr\n\n",
"LcmZ>CqznK65PlFr\n\n" +
"literal 8\n" +
"PcmZQzWMXDvWn%{b09*ha\n\n",
},
{
name: "delta",
Expand All @@ -151,10 +151,27 @@ func TestReverseFileDiffGitBinarySemantics(t *testing.T) {
reverse: "diff --git b/delta.bin a/delta.bin\n" +
"index b762194ffce13918858bfd54f1c4ded144c7e3b4..df437f42c808d41dec5d543d60ce94c8cb8a044a 100644\n" +
"GIT binary patch\n" +
"delta 20\n" +
"bcmZorXi(U2ft@2cBQY;MHAQjb4Gj(ePVNVE\n\n" +
"delta 10\n" +
"PcmZorXi!+h!2v`75gY=g\n\n",
"PcmZorXi!+h!2v`75gY=g\n\n" +
"delta 20\n" +
"bcmZorXi(U2ft@2cBQY;MHAQjb4Gj(ePVNVE\n\n",
},
{
name: "reported lengths",
forward: "diff --git a/bin.dat b/bin.dat\n" +
"index bdc955b7b2e610ad5a72302b139a2e6cb325519a..eaf36c1daccfdf325514461cd1a2ffbc139b5464 100644\n" +
"GIT binary patch\n" +
"literal 4\n" +
"LcmZQzWMT#Y01f~L\n\n" +
"literal 2\n" +
"JcmZQz1ONa700IC2\n\n",
reverse: "diff --git b/bin.dat a/bin.dat\n" +
"index eaf36c1daccfdf325514461cd1a2ffbc139b5464..bdc955b7b2e610ad5a72302b139a2e6cb325519a 100644\n" +
"GIT binary patch\n" +
"literal 2\n" +
"JcmZQz1ONa700IC2\n\n" +
"literal 4\n" +
"LcmZQzWMT#Y01f~L\n\n",
},
}

Expand Down