Since Debian stable sometimes does not include up-to-date packages, I need to pull some packages from testing or unstable. As a result, my Debian system contains a mix of packages from different releases. To see how “mixed” my system is, I created a script and got the following result:

1
2
3
4
5
6
7
8
9
10
11
12
13
==========================================================
Package status of oracle-laptop - Debian GNU/Linux 13 (trixie)
----------------------------------------------------------
99.61 % stable 3089 packages
0.06 % unstable 2 "
0.32 % not in any repo 10 "
----------------------------------------------------------
Packages not in any repo
XXXX
zoom
----------------------------------------------------------
100.00 % total 3101 packages
----------------------------------------------------------

Here is the script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#!/usr/bin/env python3
import subprocess
import sys
import tempfile
import os

VERSION = "1.0.0"
SCRIPTNAME = os.path.basename(sys.argv[0])

PRINT_ORDER = ["stable", "testing", "unstable"]

def usage():
print(f"""NAME
{SCRIPTNAME} - check how mixed (or unmixed) your system is

SYNOPSIS
{SCRIPTNAME} [OPTION]

DESCRIPTION
The script uses apt-show-versions to gather information about
package repositories (names, quantity and percent of the packages in system)
used in the current Debian install.

-m, --show-missing Print package names that are not found in any
repository. These normally consist of obsolete packages
or packages installed outside APT.

-h, --help Show usage and exit.
-V, --version Show version info and exit.
""")
sys.exit(0)


def version():
print(f"{SCRIPTNAME} {VERSION}")
sys.exit(0)


import subprocess

def pick_preferred_repo(pkg_name: str, repo: str) -> str:
# stable/unstable already fine
for pref in ["stable", "unstable"]:
if repo.startswith(pref):
return pref

# if testing or ambiguous, refine using apt list
if repo.startswith("testing"):
try:
output = subprocess.check_output(
["apt", "list", "-a", pkg_name],
text=True, stderr=subprocess.DEVNULL
)
except subprocess.CalledProcessError:
return "missing"

PRIORITY = ["stable", "unstable", "testing"]

for line in output.splitlines():
line = line.strip()
if "[installed" not in line:
continue

# Example: libnss3/testing,unstable,now 2:3.114-1 amd64 [installed,automatic]
if "unstable" in line:
return "unstable"

return "testing"

# fallback
return "missing"

def main():
# arg handling
show_missing = False
if len(sys.argv) > 1:
arg = sys.argv[1]
if arg in ("-h", "--help"):
usage()
elif arg in ("-V", "--version"):
version()
elif arg in ("-m", "--show-missing"):
show_missing = True

# check dependencies
for dep in ["apt-show-versions", "bc"]:
if not shutil.which(dep):
print(f"Install {dep} first.", file=sys.stderr)
sys.exit(1)

# run apt-show-versions
raw = subprocess.check_output(
["apt-show-versions"], text=True, errors="ignore"
).splitlines()

packages = {}
for line in raw:
if " not installed" in line:
continue
parts = line.split()
pkg = parts[0].split(":")[0]
repo_field = parts[0].split("/")[1] if "/" in parts[0] else ""
repo = pick_preferred_repo(pkg, repo_field)
packages[pkg] = repo

total = len(packages)
counts = {r: 0 for r in PRINT_ORDER + ["missing"]}
for repo in packages.values():
counts[repo] += 1

# return
print("==========================================================")
lsb = subprocess.getoutput("lsb_release -sd")
host = os.uname().nodename
print(f"Package status of {host} - {lsb}")
print("----------------------------------------------------------")

first = True
for repo in PRINT_ORDER:
if counts[repo] == 0:
continue
percent = counts[repo] / total * 100
if first:
print(f"{percent:6.2f} % {repo:<18} {counts[repo]:9d} \t packages")
first = False
else:
print(f"{percent:6.2f} % {repo:<18} {counts[repo]:9d} \t \" ")

if counts["missing"] > 0:
percent = counts["missing"] / total * 100
print(f"{percent:6.2f} % {'not in any repo':<18} {counts['missing']:9d} \t \" ")

print("----------------------------------------------------------")
if show_missing and counts["missing"] > 0:
print("Packages not in any repo")
for pkg, repo in packages.items():
if repo == "missing":
print(f"\t{pkg}")
print("----------------------------------------------------------")

print(f"100.00 % \t total \t {total:17d} \t packages")
print("----------------------------------------------------------")


if __name__ == "__main__":
import shutil
main()