Something went wrong. Try again.
Maps Linux distribution source packages to their upstream repositories, and through them to each other
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697// Package distro holds what the per-distribution resolvers share: upstream candidates scored// from evidence, their ranking, and host rules that map project sites and download areas to// repositories. The resolvers themselves live in subpackages (debian, arch, opensuse, alpine).package distro
import ( "sort"
"tangled.org/odd.computer/isomorph/internal/model" "tangled.org/odd.computer/isomorph/internal/repourl")
// Candidate is one possible upstream for a package, with the evidence behind it.type Candidate struct { // Key is the repourl key; URL is the canonical clone URL. Key string URL string // Kind distinguishes real repositories from registries/tarballs that only hint at one. Kind repourl.Kind // Evidence lists the observations that produced this candidate. Evidence []model.Evidence // Score is the combined weight in [0,1]. Score float64}
// AddCandidate merges an observation into a candidate list: candidates with the same key// accumulate evidence and score (1 - prod(1-w)). Non-repo kinds (tarball/registry) are added// under their Hint key when one exists; failing that, a host rule (MapHost) that maps the// project site or download area to its repository adds it, scaled by the rule's confidence.func AddCandidate(cands []Candidate, raw string, ev model.Evidence) []Candidate { return addCandidate(cands, raw, ev, true)}
func addCandidate(cands []Candidate, raw string, ev model.Evidence, mapHosts bool) []Candidate { c, err := repourl.Parse(raw) if mapHosts && (err != nil || (c.Kind != repourl.KindRepo && c.Hint == "")) { if mapped, conf, ok := MapHost(raw); ok { ev.Weight *= conf if IsArchiveURL(raw) { ev.Weight *= 0.6 // a tarball only hints at its repository, as below } return addCandidate(cands, mapped, ev, false) } } if err != nil { return cands } key, url, kind := c.Key, c.URL, c.Kind if kind != repourl.KindRepo && !(kind == repourl.KindRegistry && c.Hint == "") { // A registry entry (pypi.org/project/x, packages.python.org/x) stays a registry // candidate, for canonicalisation to follow to the repository its metadata names. if c.Hint == "" { return cands } hc, err := repourl.Parse("https://" + c.Hint) if err != nil { return cands } // A hint names a repository or, for registry downloads (PyPI sdists), a registry entry; // a registry entry stays a registry candidate so it never passes for a repository. key, url, kind = hc.Key, hc.URL, hc.Kind ev.Weight *= 0.6 // a hint is weaker than a direct repo reference } ev.Key = key for i := range cands { if cands[i].Key == key { cands[i].Evidence = append(cands[i].Evidence, ev) cands[i].Score = 1 - (1-cands[i].Score)*(1-ev.Weight) return cands } } return append(cands, Candidate{Key: key, URL: url, Kind: kind, Evidence: []model.Evidence{ev}, Score: ev.Weight})}
// RankCandidates sorts best first and applies penalties for known packaging-only hosts// (salsa.debian.org, gitlab.archlinux.org, src.opensuse.org, build.opensuse.org) which are// almost never the upstream.func RankCandidates(cands []Candidate) []Candidate { for i := range cands { cands[i].Score *= PackagingPenalty(cands[i].Key) } sort.SliceStable(cands, func(i, j int) bool { return cands[i].Score > cands[j].Score }) return cands}
// PackagingPenalty is the factor applied to a candidate on a packaging-only host.func PackagingPenalty(key string) float64 { for _, p := range []string{"salsa.debian.org/", "gitlab.archlinux.org/", "src.opensuse.org/", "build.opensuse.org/", "git.launchpad.net/ubuntu/", "sources.debian.org/"} { if hasPrefix(key, p) { return 0.2 } } return 1}
func hasPrefix(s, p string) bool { return len(s) >= len(p) && s[:len(p)] == p }