mirror of
https://github.com/juanfont/headscale.git
synced 2026-09-26 10:14:52 +09:00
731b0b963c
Diffing go.mod catches a downgrade wherever selection produced it; reading what go get printed only catches what go get did itself. The lockstep partners are exempt, since repin lowers them on purpose.
131 lines
2.8 KiB
Go
131 lines
2.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// dropped records an atom that was rewound, and why, so the pull request body
|
|
// can say what did not move instead of leaving it silently stale.
|
|
type dropped struct {
|
|
Name string
|
|
Reason string
|
|
Log string
|
|
}
|
|
|
|
// applyAtoms keeps every atom it can. The optimistic path applies the whole set
|
|
// at once; only when that fails does it split, so a healthy day costs one gate
|
|
// run and a bad day costs log2(n) rather than n.
|
|
//
|
|
// The split assumes a failure is attributable to one side. A genuine
|
|
// interaction between two atoms shows up as both being dropped, which is the
|
|
// safe direction to be wrong in.
|
|
func applyAtoms(ctx context.Context, r *repo, atoms []atom) ([]string, []dropped, error) {
|
|
if len(atoms) == 0 {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
base, err := saveModState(r)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
summaries, applyErr := applySet(ctx, r, atoms)
|
|
if applyErr == nil {
|
|
return summaries, nil, nil
|
|
}
|
|
|
|
err = base.restore(r)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
if len(atoms) == 1 {
|
|
return nil, []dropped{{
|
|
Name: atoms[0].Name,
|
|
Reason: reasonOf(applyErr),
|
|
Log: logOf(applyErr),
|
|
}}, nil
|
|
}
|
|
|
|
mid := len(atoms) / 2
|
|
|
|
keptFirst, dropFirst, err := applyAtoms(ctx, r, atoms[:mid])
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
keptSecond, dropSecond, err := applyAtoms(ctx, r, atoms[mid:])
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return append(keptFirst, keptSecond...), append(dropFirst, dropSecond...), nil
|
|
}
|
|
|
|
// applySet applies every atom, settles go.mod and asks the atom gate whether
|
|
// the result is viable.
|
|
func applySet(ctx context.Context, r *repo, atoms []atom) ([]string, error) {
|
|
before, err := parseGoMod(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
summaries := make([]string, 0, len(atoms))
|
|
|
|
for _, a := range atoms {
|
|
summary, err := a.Apply(ctx, r)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s: %w", a.Name, err)
|
|
}
|
|
|
|
// An atom that found nothing to do reports no summary; listing it
|
|
// would fill the report with modules that did not move.
|
|
if summary != "" {
|
|
summaries = append(summaries, summary)
|
|
}
|
|
}
|
|
|
|
err = settle(ctx, r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
after, err := parseGoMod(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = checkDowngrades(before, after)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = atomGate(ctx, r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return summaries, nil
|
|
}
|
|
|
|
// reasonOf renders a one-line cause for the report.
|
|
func reasonOf(err error) string {
|
|
if ce, ok := errors.AsType[*cmdError](err); ok {
|
|
return fmt.Sprintf("`%s` failed", strings.Join(ce.Argv, " "))
|
|
}
|
|
|
|
return strings.SplitN(err.Error(), "\n", 2)[0]
|
|
}
|
|
|
|
// logOf returns the captured output of a failing command, if there was one.
|
|
func logOf(err error) string {
|
|
if ce, ok := errors.AsType[*cmdError](err); ok {
|
|
return ce.Tail
|
|
}
|
|
|
|
return ""
|
|
}
|