Anchor patch hunks as git apply does, refuse patches touching non-100644 files by their real mode, survive bare-CR context lines (issue 1604 review)
mesh/merge-gate head 324e331184da merged into trunk 0f2cee1bea4e — pass: builds gitea → novox; no bus step; every machine composes with the change as it di…
mesh/repo-check head 324e331184da merged into trunk 0f2cee1bea4e — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery covered: a later merge that contains it was delivered: novox/mesh-catalog@e023f69989ab (merged as b5dbfbb7 into main, walk plan-17925769516…
mesh/merge-gate head 324e331184da merged into trunk 0f2cee1bea4e — pass: builds gitea → novox; no bus step; every machine composes with the change as it di…
mesh/repo-check head 324e331184da merged into trunk 0f2cee1bea4e — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery covered: a later merge that contains it was delivered: novox/mesh-catalog@e023f69989ab (merged as b5dbfbb7 into main, walk plan-17925769516…
This commit is contained in:
@@ -17,7 +17,7 @@ import (
|
||||
// a patch that creates an executable file, changes a mode or touches a symlink is not applied here: the tool says so.
|
||||
|
||||
// fileSource reads a file at the commit the patch is applied to; ok is false when there is no such file.
|
||||
type fileSource func(path string) (content []byte, ok bool, err error)
|
||||
type fileSource func(path string) (content []byte, mode string, ok bool, err error)
|
||||
|
||||
// patchFile is one file section of a patch.
|
||||
type patchFile struct {
|
||||
@@ -33,6 +33,8 @@ type hunk struct {
|
||||
oldStart int
|
||||
old, new []string
|
||||
oldNoNL, newNoNL bool
|
||||
leading int // unchanged lines before the first change
|
||||
trailing int // unchanged lines after the last change
|
||||
}
|
||||
|
||||
var hunkHeader = regexp.MustCompile(`^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@`)
|
||||
@@ -151,28 +153,40 @@ func parsePatch(text string) ([]*patchFile, error) {
|
||||
h.oldNoNL, h.newNoNL = true, true
|
||||
}
|
||||
}
|
||||
changed := false
|
||||
for oldN > 0 || newN > 0 {
|
||||
i++
|
||||
if i >= len(lines) {
|
||||
return nil, fmt.Errorf("the hunk of %s at patch line %d ends before its %d old and %d new lines are all there", cur.name(), startLine, oldN, newN)
|
||||
}
|
||||
b := strings.TrimSuffix(lines[i], "\r")
|
||||
if lines[i] == "" {
|
||||
b = " " // a blank context line some tools strip to nothing
|
||||
if b == "" {
|
||||
b = " " // a blank context line some tools strip to nothing, or a bare CR
|
||||
}
|
||||
switch b[0] {
|
||||
case '\\':
|
||||
noNL()
|
||||
case ' ':
|
||||
h.old, h.new = append(h.old, lines[i][min(1, len(lines[i])):]), append(h.new, lines[i][min(1, len(lines[i])):])
|
||||
text := lines[i]
|
||||
if strings.HasPrefix(text, " ") {
|
||||
text = text[1:] // a bare "" or "\r" is the whole blank line, its leading space stripped
|
||||
}
|
||||
h.old, h.new = append(h.old, text), append(h.new, text)
|
||||
oldN--
|
||||
newN--
|
||||
if changed {
|
||||
h.trailing++
|
||||
} else {
|
||||
h.leading++
|
||||
}
|
||||
case '-':
|
||||
h.old = append(h.old, lines[i][1:])
|
||||
oldN--
|
||||
changed, h.trailing = true, 0
|
||||
case '+':
|
||||
h.new = append(h.new, lines[i][1:])
|
||||
newN--
|
||||
changed, h.trailing = true, 0
|
||||
default:
|
||||
return nil, fmt.Errorf("the hunk of %s at patch line %d has a line %d that is neither context, - nor +: %q", cur.name(), startLine, i+1, lines[i])
|
||||
}
|
||||
@@ -254,8 +268,26 @@ func (t textFile) apply(h hunk, delta int) (textFile, int, error) {
|
||||
}
|
||||
return true
|
||||
}
|
||||
// As git apply does: a hunk that starts at line 1 (or 0) begins the file, and a hunk with context but none
|
||||
// after its last change ends it; a hunk whose old side lacks the final newline ends it too.
|
||||
atStart := h.oldStart <= 1
|
||||
atEnd := (h.trailing == 0 && h.leading > 0) || h.oldNoNL
|
||||
at := -1
|
||||
for d := 0; d <= len(t.lines)+1 && at < 0; d++ {
|
||||
switch {
|
||||
case atStart && atEnd:
|
||||
if len(t.lines) == len(h.old) && matches(0) {
|
||||
at = 0
|
||||
}
|
||||
case atStart:
|
||||
if matches(0) {
|
||||
at = 0
|
||||
}
|
||||
case atEnd:
|
||||
if e := len(t.lines) - len(h.old); matches(e) {
|
||||
at = e
|
||||
}
|
||||
}
|
||||
for d := 0; !atStart && !atEnd && d <= len(t.lines)+1 && at < 0; d++ {
|
||||
switch {
|
||||
case matches(want - d):
|
||||
at = want - d
|
||||
@@ -298,10 +330,13 @@ func patchAsFiles(text string, read fileSource) ([]FileChange, error) {
|
||||
if _, seen := work[p]; seen {
|
||||
return nil
|
||||
}
|
||||
b, ok, err := read(p)
|
||||
b, mode, ok, err := read(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok && mode != "" && mode != "100644" {
|
||||
return fmt.Errorf("%s is a file of mode %s, and the forge's files call carries no file mode, so committing it that way would make it 100644", p, mode)
|
||||
}
|
||||
orig[p] = ok
|
||||
if ok {
|
||||
if b == nil {
|
||||
|
||||
Reference in New Issue
Block a user