Fall back to the tool's own patch applier when the forge answers 500, with tests (issue 1604)
This commit is contained in:
@@ -326,14 +326,42 @@ func commit(ctx context.Context, f *Forge, a CommitAsk) (*Committed, error) {
|
||||
var made struct {
|
||||
Commit madeCommit `json:"commit"`
|
||||
}
|
||||
// A patch goes to the forge's diffpatch first. When the forge answers a server error (it does so, with an
|
||||
// empty message, when an unchanged context line holds a non-ASCII character: novox/hq issue 1604) the tool
|
||||
// applies the patch itself and commits the resulting files the way a files commit does.
|
||||
files := a.Files
|
||||
forgeApplied := false
|
||||
if a.Patch != "" {
|
||||
on["content"] = a.Patch
|
||||
if err := f.SendJSON(ctx, http.MethodPost, repo+"/diffpatch", on, &made); err != nil {
|
||||
err := f.SendJSON(ctx, http.MethodPost, repo+"/diffpatch", on, &made)
|
||||
var status *forgehttp.StatusError
|
||||
switch {
|
||||
case err == nil:
|
||||
forgeApplied = true
|
||||
case errors.As(err, &status) && status.Status >= 500:
|
||||
delete(on, "content")
|
||||
read := func(p string) ([]byte, bool, error) { return f.contentAt(ctx, repo, p, ref) }
|
||||
var perr error
|
||||
if files, perr = patchAsFiles(a.Patch, read); perr != nil {
|
||||
return nil, fmt.Errorf("the forge did not take the patch: %w; the tool could not apply it itself: %v", err, perr)
|
||||
}
|
||||
total := 0
|
||||
for _, fc := range files {
|
||||
if fc.size > maxFileBytes {
|
||||
return nil, refuse("%s would be %s, more than the %s one file of a commit may be. Nothing was sent", fc.Path, mib(fc.size), mib(maxFileBytes))
|
||||
}
|
||||
total += fc.size
|
||||
}
|
||||
if total > maxCommitBytes {
|
||||
return nil, refuse("the patch's files are %s together, more than the %s one commit carries. Nothing was sent", mib(total), mib(maxCommitBytes))
|
||||
}
|
||||
out.Said = append(out.Said, fmt.Sprintf("the forge answered %d to the patch, so the tool applied it to the files at %s and committed them", status.Status, short(ref)))
|
||||
default:
|
||||
return nil, fmt.Errorf("the forge did not take the patch: %w", err)
|
||||
}
|
||||
} else {
|
||||
files := a.Files
|
||||
for _, fc := range a.Files {
|
||||
}
|
||||
if !forgeApplied {
|
||||
for _, fc := range files {
|
||||
if fc.CopyFrom != nil {
|
||||
// Read, and judged against the limits, before anything is sent.
|
||||
var err error
|
||||
@@ -391,7 +419,7 @@ func commit(ctx context.Context, f *Forge, a CommitAsk) (*Committed, error) {
|
||||
moved = "base " + out.Base
|
||||
}
|
||||
how := "each file it changes was checked against " + short(read) + " by its blob, so none of them changed in between"
|
||||
if a.Patch != "" {
|
||||
if forgeApplied {
|
||||
how = "the forge applied the patch there, and it refuses a patch whose context changed"
|
||||
}
|
||||
out.Said = append(out.Said, fmt.Sprintf("the %s moved from %s to %s between the read and the commit, so the "+
|
||||
@@ -478,3 +506,42 @@ func short(sha string) string {
|
||||
}
|
||||
return sha
|
||||
}
|
||||
|
||||
// contentAt is the content of a file at a ref; ok is false when there is no file there.
|
||||
func (f *Forge) contentAt(ctx context.Context, repo, path, ref string) ([]byte, bool, error) {
|
||||
seg, err := segments("file path", path)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
body, err := f.Do(ctx, http.MethodGet, repo+"/contents/"+seg+"?ref="+queryEscape(ref), nil, maxBody)
|
||||
if err != nil {
|
||||
if errors.Is(err, errNotFound) {
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
var entry struct {
|
||||
Type string `json:"type"`
|
||||
Size int `json:"size"`
|
||||
Content string `json:"content"`
|
||||
Encoding string `json:"encoding"`
|
||||
}
|
||||
if err := jsonInto(body, &entry); err != nil {
|
||||
return nil, false, fmt.Errorf("the forge's answer about %s is not readable: %w", path, err)
|
||||
}
|
||||
if entry.Type != "file" {
|
||||
return nil, false, refuse("%s is a %s, not a file. Nothing was sent", path, entry.Type)
|
||||
}
|
||||
if entry.Encoding == "base64" && (entry.Content != "" || entry.Size == 0) {
|
||||
raw, err := base64.StdEncoding.DecodeString(strings.ReplaceAll(entry.Content, "\n", ""))
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("the content of %s is not base64: %w", path, err)
|
||||
}
|
||||
return raw, true, nil
|
||||
}
|
||||
raw, err := f.Do(ctx, http.MethodGet, repo+"/raw/"+seg+"?ref="+queryEscape(ref), nil, maxFileBytes+1)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return raw, true, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user