diff --git a/lib/update_out.py b/lib/update_out.py
new file mode 100644
index 0000000..c46515c
--- /dev/null
+++ b/lib/update_out.py
@@ -0,0 +1,1328 @@
+#!/usr/bin/env python
+
+# Copyright 2001-6 Anthony Towns
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+import sys, re, string, time, whrandom, math
+import britney
+
+if len(sys.argv) != 4:
+ print "Must specify testing, unstable, testing-updates directories."
+ sys.exit(1)
+
+testingdir = sys.argv[1]
+unstabledir = sys.argv[2]
+testingupdatesdir = sys.argv[3]
+
+# Configuration information
+
+expected_arches = 12
+allarches = [ 'i386', 'sparc', 'alpha', 'powerpc', 'arm', 'm68k', 'hppa', 'ia64', 'mips', 'mipsel', 's390', 'amd64' ]
+
+mindays = { "low" : 10, "medium" : 5, "high" : 2, "critical" : 0,
+ "emergency" : 0 }
+defaulturgency = "low"
+
+# if you're not in this list, arch: all packages are allowed to break on you
+nobreakarchallarches = ['i386']
+# if you're in this list, your packages may not stay in sync with the source
+fuckedarches = ['m68k', 'sparc']
+# if you're in this list, your uninstallability count may increase
+breakarches = ['m68k', 'sparc']
+# new architectures
+newarches = []
+
+allarches.sort()
+arches = [ x for x in allarches if x in nobreakarchallarches ]
+arches += [ x for x in allarches if x not in arches and x not in fuckedarches ]
+arches += [ x for x in allarches if x not in arches and x not in breakarches ]
+arches += [ x for x in allarches if x not in arches and x not in newarches ]
+arches += [ x for x in allarches if x not in arches ]
+
+# Subs
+
+def same_source(sv1, sv2):
+ if sv1 == sv2:
+ return 1
+
+ m = re.match(r'^(.*)\+b\d+$', sv1)
+ if m: sv1 = m.group(1)
+ m = re.match(r'^(.*)\+b\d+$', sv2)
+ if m: sv2 = m.group(1)
+
+ if sv1 == sv2:
+ return 1
+
+ if re.search("-", sv1) or re.search("-", sv2):
+ m = re.match(r'^(.*-[^.]+)\.0\.\d+$', sv1)
+ if m: sv1 = m.group(1)
+ m = re.match(r'^(.*-[^.]+\.[^.]+)\.\d+$', sv1)
+ if m: sv1 = m.group(1)
+
+ m = re.match(r'^(.*-[^.]+)\.0\.\d+$', sv2)
+ if m: sv2 = m.group(1)
+ m = re.match(r'^(.*-[^.]+\.[^.]+)\.\d+$', sv2)
+ if m: sv2 = m.group(1)
+
+ return (sv1 == sv2)
+ else:
+ m = re.match(r'^([^-]+)\.0\.\d+$', sv1)
+ if m and sv2 == m.group(1): return 1
+
+ m = re.match(r'^([^-]+)\.0\.\d+$', sv2)
+ if m and sv1 == m.group(1): return 1
+
+ return 0
+
+def read_approvals(dir, approver, approved):
+ f = open("%s/%s" % (dir, approver))
+ line = f.readline()
+ while line:
+ l = string.split(line)
+ if len(l) == 2:
+ [pkg,ver] = l
+ approved["%s_%s" % (pkg, ver)] = approver
+ line = f.readline()
+ f.close()
+
+def read_bugs(file):
+ bugsperpkg = {}
+
+ f = open(file)
+ line = f.readline()
+ while line:
+ l = string.split(line)
+ if len(l) == 2:
+ bugsperpkg[l[0]] = string.atoi(l[1])
+ line = f.readline()
+ f.close()
+ return bugsperpkg
+
+def write_bugs(file, bugs):
+ f = open(file, 'w')
+ pkgs = bugs.keys()
+ pkgs.sort()
+ for pkg in pkgs:
+ if bugs[pkg] == 0: continue
+ f.write("%s %d\n" % (pkg, bugs[pkg]))
+ f.close()
+
+def read_dates(file):
+ dates = {}
+
+ f = open(file)
+ line = f.readline()
+ while line:
+ l = string.split(line)
+ if len(l) == 3:
+ dates[l[0]] = (l[1], string.atoi(l[2]))
+ line = f.readline()
+ f.close()
+ return dates
+
+def write_dates(file, dates):
+ f = open(file, 'w')
+ pkgs = dates.keys()
+ pkgs.sort()
+ for pkg in dates.keys():
+ f.write("%s %s %d\n" % ((pkg,) + dates[pkg]))
+ f.close()
+
+def read_urgencies(file, testing, unstable):
+ urgency = {}
+
+ f = open(file)
+ line = f.readline()
+ while line:
+ l = string.split(line)
+ if len(l) == 3:
+ uo = urgency.get(l[0], defaulturgency)
+ mo = mindays.get(uo, mindays[defaulturgency])
+ mn = mindays.get(l[2], mindays[defaulturgency])
+ if mo <= mn:
+ line = f.readline()
+ continue
+
+ tsrcv = testing.get_version(l[0])
+ if tsrcv and britney.versioncmp(tsrcv, l[1]) >= 0:
+ line = f.readline()
+ continue
+ usrcv = unstable.get_version(l[0])
+ if not usrcv or britney.versioncmp(usrcv, l[1]) < 0:
+ line = f.readline()
+ continue
+
+ urgency[l[0]] = l[2]
+
+ line = f.readline()
+ f.close()
+ return urgency
+
+def read_hints(dir, hinter, hints, allowed):
+ res = {}
+ for k in allowed:
+ res[k] = []
+
+ try:
+ f = open("%s/%s" % (dir, hinter))
+ except IOError:
+ return res
+
+ while 1:
+ line = f.readline()
+ if not line: break
+
+ l = string.split(line)
+ if len(l) == 0 or line[0] == "#":
+ continue
+
+ type = l[0]
+
+ if type == "finished":
+ break
+
+ if type not in allowed:
+ continue
+
+ def mysplit(str):
+ x = str.rfind("/")
+ if x == -1: return [str]
+ return [str[:x], str[x+1:]]
+
+ if type in ["easy", "hint", "force-hint"]:
+ l = [ tuple(mysplit(y)) for y in l[1:] ]
+ l = [ k for k in l if len(k) == 2 ]
+ res[type].append((hinter, l))
+
+ if type in ["block-all"]:
+ l = [ (y, hinter) for y in l[1:] ]
+ res[type].extend(l)
+
+ if type in ["block"]:
+ l = [ (y, hinter) for y in l[1:] ]
+ res[type].extend(l)
+
+ if type in ["remove", "approve", "unblock", "force", "urgent"]:
+ l = [ tuple(mysplit(y)+[hinter]) for y in l[1:] ]
+ l = [ k for k in l if len(k) == 3 ]
+ l = [ (p, (v,h)) for (p,v,h) in l ]
+ res[type].extend(l)
+
+ f.close()
+ return res
+
+class Excuse:
+ reemail = re.compile(r"<.*?>")
+
+ def __init__(self, name):
+ self.name = name
+ self.ver = ("-", "-")
+ self.maint = None
+ self.pri = None
+ self.date = None
+ self.urgency = None
+ self.daysold = None
+ self.mindays = None
+ self.section = None
+ self.dontinvalidate = 0
+
+ self.invalid_deps = []
+ self.deps = []
+ self.break_deps = []
+ self.bugs = []
+ self.htmlline = []
+
+ def set_vers(self, tver, uver):
+ if tver: self.ver = (tver, self.ver[1])
+ if uver: self.ver = (self.ver[0], uver)
+
+ def set_maint(self, maint):
+ self.maint = self.reemail.sub("",maint)
+# self.maint = maint
+
+ def set_section(self, section):
+ self.section = section
+
+ def set_priority(self, pri):
+ self.pri = pri
+
+ def set_date(self, date):
+ self.date = date
+
+ def set_urgency(self, date):
+ self.urgency = date
+
+ def add_dep(self, name):
+ if name not in self.deps: self.deps.append(name)
+ def add_break_dep(self, name, arch):
+ if (name, arch) not in self.break_deps:
+ self.break_deps.append( (name, arch) )
+
+ def invalidate_dep(self, name):
+ if name not in self.invalid_deps: self.invalid_deps.append(name)
+
+ def setdaysold(self, daysold, mindays):
+ self.daysold = daysold
+ self.mindays = mindays
+
+ def addhtml(self, note):
+ self.htmlline.append(note)
+
+ def html(self):
+ res = "%s (%s to %s)\n
\n" % \
+ (self.name, self.name, self.name, self.ver[0], self.ver[1])
+ if self.maint:
+ res = res + "
Maintainer: %s\n" % (self.maint)
+ if self.section and string.find(self.section, "/") > -1:
+ res = res + "
Section: %s\n" % (self.section)
+ if self.daysold != None:
+ if self.daysold < self.mindays:
+ res = res + ("
Too young, only %d of %d days old\n" %
+ (self.daysold, self.mindays))
+ else:
+ res = res + ("
%d days old (needed %d days)\n" %
+ (self.daysold, self.mindays))
+ for x in self.htmlline:
+ res = res + "
" + x + "\n"
+ for x in self.deps:
+ if x in self.invalid_deps:
+ res = res + "
Depends: %s %s (not considered)\n" % (self.name, x, x)
+ else:
+ res = res + "
Depends: %s %s\n" % (self.name, x, x)
+ for (n,a) in self.break_deps:
+ if n not in self.deps:
+ res += "
Ignoring %s depends: %s\n" % (a, n, n)
+ res = res + "
\n"
+ return res
+
+
+
+def should_remove_source(src, orig, new, excs):
+ if new.is_present(src): return 0
+
+ okay = 1
+
+ exc = Excuse("-" + src)
+
+ exc.set_vers(orig.get_version(src), None)
+ m = orig.get_field(src, "Maintainer")
+ if m: exc.set_maint(string.strip(m))
+ s = orig.get_field(src, "Section")
+ if s: exc.set_section(string.strip(s))
+
+ if hints["block"].has_key("-" + src):
+ blocked = hints["block"]["-" + src]
+ exc.addhtml("Not touching package, as requested by %s (contact debian-release if update is needed)" % (blocked))
+ okay = 0
+
+ if okay:
+ exc.addhtml("Valid candidate")
+ else:
+ exc.addhtml("Not considered")
+
+ excs.append(exc)
+
+ return okay
+
+def should_upgrade_srcarch(src, arch, suite, tsrcv, orig, opkgsa, new, npkgsa, excs):
+ # binnmu this arch?
+ anywrongver = 0
+ anyworthdoing = 0
+
+ ref = "%s/%s" % (src, arch)
+ if suite: ref = ref + "_%s" % (suite)
+
+ e = Excuse(ref)
+ e.set_vers(tsrcv, tsrcv)
+ m = new.get_field(src, "Maintainer")
+ if m: e.set_maint(string.strip(m))
+ s = new.get_field(src, "Section")
+ if s: e.set_section(string.strip(s))
+
+ if hints["remove"].has_key(src):
+ if same_source(tsrcv, hints["remove"][src][0]):
+ e.addhtml("Removal request by %s" %
+ (hints["remove"][src][1]))
+ e.addhtml("Trying to remove package, not update it")
+ e.addhtml("Not considered")
+ excs.append(e)
+ return 0
+
+ for pkg in new.binaries(src, arch):
+ pkgv = npkgsa.get_version(pkg)
+ pkgsv = npkgsa.get_sourcever(pkg)
+
+ if npkgsa.is_arch_all(pkg):
+ e.addhtml("Ignoring %s %s (from %s) as it is arch: all"
+ % (pkg, pkgv, pkgsv))
+ continue
+
+ if not same_source(tsrcv, pkgsv):
+ anywrongver = 1
+ e.addhtml("From wrong source: %s %s (%s not %s)" % (
+ pkg, pkgv, pkgsv, tsrcv))
+ break
+
+ excuse_unsat_deps(pkg, arch, opkgsa, npkgsa, e, 0)
+
+ if not opkgsa.is_present(pkg):
+ e.addhtml("New binary: %s (%s)" % (pkg, pkgv))
+ anyworthdoing = 1
+ continue
+
+ tpkgv = opkgsa.get_version(pkg)
+ if britney.versioncmp(tpkgv, pkgv) > 0:
+ anywrongver = 1
+ e.addhtml("Not downgrading: %s (%s to %s)" % (
+ pkg, tpkgv, pkgv))
+ break
+ elif britney.versioncmp(tpkgv, pkgv) < 0:
+ e.addhtml("Updated binary: %s (%s to %s)" % (
+ pkg, tpkgv, pkgv))
+ anyworthdoing = 1
+
+ if not anywrongver and (anyworthdoing or not new.is_fake(src)):
+ srcv = new.get_version(src)
+ ssrc = same_source(tsrcv, srcv)
+ for pkg in orig.binaries(src, arch):
+ if opkgsa.is_arch_all(pkg):
+ e.addhtml("Ignoring removal of %s as it is arch: all"
+ % (pkg))
+ continue
+ if not npkgsa.is_present(pkg):
+ tpkgv = opkgsa.get_version(pkg)
+ e.addhtml("Removed binary: %s %s" % (
+ pkg, tpkgv))
+ if ssrc: anyworthdoing = 1
+
+ if not anywrongver and anyworthdoing:
+ e.addhtml("Valid candidate")
+ excs.append(e)
+ return 1
+ else:
+ if anyworthdoing:
+ e.addhtml("Not considered")
+ excs.append(e)
+ return 0
+
+def excuse_unsat_deps(pkg, arch, tpkgsarch, upkgsarch, exc, ignore_break=0):
+ for d in ['Pre-Depends', 'Depends']:
+ udt = tpkgsarch.unsatisfiable_deps(upkgsarch, pkg, d)
+ udu = upkgsarch.unsatisfiable_deps(upkgsarch, pkg, d)
+
+ for t,u in map(None, udt, udu):
+ if t[1]: continue
+ l = []
+ for e in u[1]:
+ s = upkgsarch.get_source(e)
+ if s not in l: l.append(s)
+ if src in l: continue
+ if l == []:
+ exc.addhtml("%s/%s unsatisfiable %s: %s" % (pkg, arch, d, t[0]))
+ for s in l:
+ if ignore_break or arch not in breakarches:
+ exc.add_dep(s)
+ else:
+ exc.add_break_dep(s, arch)
+
+def should_upgrade_src(src, suite, orig, origpkgs, new, newpkgs, approvals,
+ excs):
+ srcv = new.get_version(src)
+
+ if orig.is_present(src):
+ tsrcv = orig.get_version(src)
+ if britney.versioncmp(srcv, tsrcv) == 0:
+ # Candidate for binnmus only
+ return 0
+ else:
+ tsrcv = None
+
+ updatecand = 1
+
+ ref = src
+ if suite: ref = ref + "_tpu"
+
+ exc = Excuse(ref)
+ exc.set_vers(tsrcv, srcv)
+ m = new.get_field(src, "Maintainer")
+ if m: exc.set_maint(string.strip(m))
+ s = new.get_field(src, "Section")
+ if s: exc.set_section(string.strip(s))
+
+ if tsrcv and britney.versioncmp(srcv, tsrcv) < 0:
+ # Version in unstable is older!
+ exc.addhtml("ALERT: %s is newer in testing (%s %s)" % (src, tsrcv, srcv))
+ excs.append(exc)
+ return 0
+
+ if unstable.is_fake(src):
+ exc.addhtml("%s source package doesn't exist" % (src))
+ updatecand = 0
+
+ urgency = unstableurg.get(src, defaulturgency)
+ if not tsrcv and urgency != defaulturgency:
+ exc.addhtml("Ignoring %s urgency setting for NEW package" % (urgency))
+ urgency = defaulturgency
+
+ if hints["remove"].has_key(src):
+ if (tsrcv and same_source(tsrcv, hints["remove"][src][0])) or \
+ same_source(srcv, hints["remove"][src][0]):
+ exc.addhtml("Removal request by %s" %
+ (hints["remove"][src][1]))
+ exc.addhtml("Trying to remove package, not update it")
+ updatecand = 0
+
+ blocked = None
+ if hints["block"].has_key(src):
+ blocked = hints["block"][src]
+ elif hints["block-all"].has_key("source"):
+ blocked = hints["block-all"]["source"]
+
+ if blocked:
+ ubv = hints["unblock"].get(src,(None,None))[0]
+ if ubv != None and same_source(ubv, srcv):
+ exc.addhtml("Ignoring request to block package by %s, due to unblock request by %s" % (blocked, hints["unblock"][src][1]))
+ else:
+ if ubv != None:
+ exc.addhtml("Unblock request by %s ignored due to version mismatch: %s" % (hints["unblock"][src][1], hints["unblock"][src][0]))
+ exc.addhtml("Not touching package, as requested by %s (contact debian-release if update is needed)" % (blocked))
+ updatecand = 0
+
+ if suite == None:
+ if not unstabledates.has_key(src):
+ unstabledates[src] = (srcv, datenow)
+ elif not same_source(unstabledates[src][0], srcv):
+ unstabledates[src] = (srcv, datenow)
+
+ daysold = datenow - unstabledates[src][1]
+ mymindays = mindays[urgency]
+ exc.setdaysold(daysold, mymindays)
+ if daysold < mymindays:
+ if hints["urgent"].has_key(src) and same_source(srcv, hints["urgent"][src][0]):
+ exc.addhtml("Too young, but urgency pushed by %s" % (hints["urgent"][src][1]))
+ else:
+ updatecand = 0
+
+ pkgs = { src: ["source"] }
+ anybins = 0
+ for arch in arches:
+ oodbins = {}
+ for pkg in new.binaries(src,arch):
+ anybins = 1
+ if not pkgs.has_key(pkg): pkgs[pkg] = []
+ pkgs[pkg].append(arch)
+
+ pkgsv = newpkgs[arch].get_sourcever(pkg)
+ if not same_source(srcv, pkgsv):
+ if not oodbins.has_key(pkgsv):
+ oodbins[pkgsv] = []
+ oodbins[pkgsv].append(pkg)
+ continue
+
+ if newpkgs[arch].isnt_arch_all(pkg) or \
+ arch in nobreakarchallarches:
+ excuse_unsat_deps(pkg, arch,
+ origpkgs[arch], newpkgs[arch], exc)
+
+ if oodbins:
+ oodtxt = ""
+ for v in oodbins.keys():
+ if oodtxt: oodtxt = oodtxt + "; "
+ oodtxt = oodtxt + "%s (from %s)" % \
+ (", ".join(oodbins[v]), arch, src, v, v)
+ text = "out of date on %s: %s" % (arch, src, srcv, arch, oodtxt)
+
+ if arch in fuckedarches:
+ text = text + " (but %s isn't keeping up," % \
+ (arch) + " so nevermind)"
+ else:
+ updatecand = 0
+
+ if datenow != unstabledates[src][1]:
+ exc.addhtml(text)
+
+ if not anybins:
+ exc.addhtml("%s has no binaries on any arch" % src)
+ updatecand = 0
+
+ if suite == None:
+ for pkg in pkgs.keys():
+ if not testingbugs.has_key(pkg): testingbugs[pkg] = 0
+ if not unstablebugs.has_key(pkg): unstablebugs[pkg] = 0
+
+ if unstablebugs[pkg] > testingbugs[pkg]:
+ exc.addhtml("%s (%s) is buggy! (%d > %d)" % \
+ (pkg, ", ".join(pkgs[pkg]), pkg,
+ unstablebugs[pkg], testingbugs[pkg]))
+ updatecand = 0
+ elif unstablebugs[pkg] > 0:
+ exc.addhtml("%s (%s) is (less) buggy! (%d <= %d)" % \
+ (pkg, ", ".join(pkgs[pkg]), pkg,
+ unstablebugs[pkg], testingbugs[pkg]))
+
+ if not updatecand and hints["force"].has_key(src) and same_source(srcv, hints["force"][src][0]) :
+ exc.dontinvalidate = 1
+ exc.addhtml("Should ignore, but forced by %s" % (hints["force"][src][1]))
+ updatecand = 1
+
+ if approvals:
+ if approvals.has_key("%s_%s" % (src, srcv)):
+ exc.addhtml("Approved by %s" %
+ approvals["%s_%s" % (src, srcv)])
+ else:
+ exc.addhtml("NEEDS APPROVAL BY RM")
+ updatecand = 0
+
+ if updatecand:
+ exc.addhtml("Valid candidate")
+ else:
+ exc.addhtml("Not considered")
+
+ excuses.append(exc)
+
+ return updatecand
+
+###
+
+# Brute force stuff
+
+class UpgradeRun:
+ def __init__(self, sn, u, tu, ps):
+ self.srcsn = sn
+ self.unstable = u
+ self.testingupdates = tu
+ self.packages = ps
+
+ self.sortpkgs()
+
+ self.output = open("update.OUTPUT_py", "w");
+
+ self.arches = [ x for x in arches if x in srcsn.arches ]
+ self.srcsnpkgs = {}
+ for arch in arches:
+ self.srcsnpkgs[arch] = self.srcsn.Packages(arch)
+
+ #def __del__():
+ # self.output.close()
+
+ def sortpkgs(self):
+ p = self.packages
+ p.sort()
+ self.packages = p
+
+ def writeout(self, text):
+ self.output.write(text)
+ sys.stdout.write(text)
+ self.output.flush()
+ sys.stdout.flush()
+
+ def doop_source(self, op):
+ # removals = "-