Merge lp:ubuntu-manpage-repository into lp:~ya-bo-ng/ubuntu-manpage-repository/add-gtm

Proposed by Anthony Dillon
Status: Needs review
Proposed branch: lp:ubuntu-manpage-repository
Merge into: lp:~ya-bo-ng/ubuntu-manpage-repository/add-gtm
Diff against target: 409 lines (+115/-72)
7 files modified
bin/fetch-man-pages.sh (+12/-12)
bin/make-manpage-repo.sh (+83/-45)
bin/manpages-modify-config (+4/-0)
bin/w3mman-to-html.pl (+6/-6)
cgi-bin/search.py (+4/-4)
examples/config (+2/-1)
www/functions.js (+4/-4)
To merge this branch: bzr merge lp:ubuntu-manpage-repository
Reviewer Review Type Date Requested Status
Christian Ehrhardt  (community) Approve
Anthony Dillon Pending
Review via email: mp+455501@code.launchpad.net

Commit message

Add GTM tags to the head and body to enable Google Analytics to track usage

To post a comment you must log in.
Revision history for this message
Christian Ehrhardt  (paelzer) wrote :

Ack to the changes themselve
This is the new version of https://bazaar.launchpad.net/~dpb/ubuntu-manpage-repository/ga-update/revision/220 which everybody believed to have landed in the past (surprise, it did not)

As coordinated via mail, this MR is the wrong way around and not towards the production repo.
I'll integrate it directly after checking some constraints with the release people usually updating it.

Revision history for this message
Christian Ehrhardt  (paelzer) wrote :
review: Approve
lp:ubuntu-manpage-repository updated
234. By Christian Ehrhardt 

Merge production branch back to trunk

235. By Christian Ehrhardt 

config: revert back to just release names

Turns out that we need all pockets anyway and will teach the script
to iterate over them. So the config can be simple release names just
like it always was.

236. By Christian Ehrhardt 

Need to consider all pockets

The former change to allo to define release-updates in the config
worked, but only so far to pick up the content of -updates.

Obviously packages that never got an update so far are not listed
at all in the Packages.gz behind -updates and therefore were forgotten.

Fix this by reverting the config back to just release names and on the
other hand extending make-manpage-repo.sh to always look for release,
-updates and -security.

237. By Christian Ehrhardt 

Use dpkg compare-version

Instead of just hoping they are in order use dpkg compare-version
to identify which content needs to be (re-)rendered.

238. By Christian Ehrhardt 

rename src -> binpkg as we handle binary packages

Many archive tools handle source packages, but this parses and
iterates binaries. Make it less misleading by fixing the
variable names and log out put to say binpkg instead of calling
them src.

239. By Christian Ehrhardt 

Track versions in the right context

The while loop used to be a subshell behind curl + processing.
To properly track versions that were already handled it has to
move into the same context as the rest of the program.

While touching the decision making on which package to process
ensure that each path creates a log entry and make them all
follow the same pattern which eases log reading.

240. By Christian Ehrhardt 

Remove debug output

Further unify output text and remove debug-only content.

241. By Christian Ehrhardt 

Process releases concurrently

Execution is slow and mostly toggling between being CPU bound (conversion)
and network bound (download). Making releases run concurrently allows
to utilize more CPUs at once and the network more continuously.

242. By Christian Ehrhardt 

remove trailing whitespace

243. By Christian Ehrhardt 

Add missing whitespace in log entry

244. By Christian Ehrhardt 

Add timing into to the log entries

Since we are debugging the slow execution on prodstack that might help
to compare and track execution.

Unmerged revisions

244. By Christian Ehrhardt 

Add timing into to the log entries

Since we are debugging the slow execution on prodstack that might help
to compare and track execution.

243. By Christian Ehrhardt 

Add missing whitespace in log entry

242. By Christian Ehrhardt 

remove trailing whitespace

241. By Christian Ehrhardt 

Process releases concurrently

Execution is slow and mostly toggling between being CPU bound (conversion)
and network bound (download). Making releases run concurrently allows
to utilize more CPUs at once and the network more continuously.

240. By Christian Ehrhardt 

Remove debug output

Further unify output text and remove debug-only content.

239. By Christian Ehrhardt 

Track versions in the right context

The while loop used to be a subshell behind curl + processing.
To properly track versions that were already handled it has to
move into the same context as the rest of the program.

While touching the decision making on which package to process
ensure that each path creates a log entry and make them all
follow the same pattern which eases log reading.

238. By Christian Ehrhardt 

rename src -> binpkg as we handle binary packages

Many archive tools handle source packages, but this parses and
iterates binaries. Make it less misleading by fixing the
variable names and log out put to say binpkg instead of calling
them src.

237. By Christian Ehrhardt 

Use dpkg compare-version

Instead of just hoping they are in order use dpkg compare-version
to identify which content needs to be (re-)rendered.

236. By Christian Ehrhardt 

Need to consider all pockets

The former change to allo to define release-updates in the config
worked, but only so far to pick up the content of -updates.

Obviously packages that never got an update so far are not listed
at all in the Packages.gz behind -updates and therefore were forgotten.

Fix this by reverting the config back to just release names and on the
other hand extending make-manpage-repo.sh to always look for release,
-updates and -security.

235. By Christian Ehrhardt 

config: revert back to just release names

Turns out that we need all pockets anyway and will teach the script
to iterate over them. So the config can be simple release names just
like it always was.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/fetch-man-pages.sh'
2--- bin/fetch-man-pages.sh 2018-06-05 23:34:38 +0000
3+++ bin/fetch-man-pages.sh 2023-12-07 08:37:19 +0000
4@@ -43,7 +43,7 @@
5 NAME_AND_VER=$(echo "$PKG" | sed "s/\.deb$//")
6 DEB="$TEMPDIR/$PKG"
7
8-echo "INFO: fetching: $PKGURL"
9+echo "INFO ($(date '+%H:%M:%S.%N')) - ${DIST}: fetching: $PKGURL"
10 curl --silent "$PKGURL" > "$DEB"
11
12 DESTDIR="$PUBLIC_HTML_DIR/manpages/$DIST"
13@@ -52,13 +52,13 @@
14 export W3MMAN_MAN='man --no-hyphenation'
15 export MAN_KEEP_FORMATTING=1
16
17-echo "INFO: Looking for manpages in [$DEB]"
18+echo "INFO ($(date '+%H:%M:%S.%N')) - ${DIST}: Looking for manpages in [$DEB]"
19 # The .*man bit is to handle postgres' inane manpage installation
20 man=$(dpkg-deb -c "$DEB" | grep -E " \./usr/share.*/man/.*\.[0-9][a-zA-Z0-9\.\-]*\.gz$" | sed -e "s/^.*\.\//\.\//" -e "s/ \-> /\->/")
21
22 # Exit immediately if this package does not contain manpages
23 if [ -z "$man" ]; then
24- echo "INFO: No manpages: [$DIST] [$PKG]"
25+ echo "INFO ($(date '+%H:%M:%S.%N')) - ${DIST}: No manpages: [$DIST] [$PKG]"
26 # Touch the cache file so we don't look again until package updated
27 sha1sum "$DEB" | awk '{ print $1 }' > "$DESTDIR/.cache/$NAME"
28 exit 0
29@@ -68,22 +68,22 @@
30
31 dpkg-deb -x "$DEB" "$TEMPDIR"
32 for i in $man; do
33- #printf "%s\n" "INFO: Considering entry [$i]"
34+ #printf "%s\n" "DEBUG: Considering entry [$i]"
35 i=$(printf "%s" "$i" | sed "s/^.*\.\///")
36 if printf "%s" "$i" | grep -qs "\->"; then
37 SYMLINK=1
38 symlink_src_html=$(printf "%s" "$i" | sed -e "s/^.*\->//" -e "s/\.gz$/\.html/")
39- i=$(printf "%s" "$i" | sed "s/\->.*$//")
40- #printf "%s\n" "INFO: [$i] is a symbolic link"
41+ i=$(printf "%s" "$i" | sed "s/\->.*$//")
42+ #printf "%s\n" "DEBUG: [$i] is a symbolic link"
43 else
44 SYMLINK=0
45 fi
46 manpage="$TEMPDIR/$i"
47 i=$(printf "%s" "$i" | sed -e "s/usr\/share.*\/man\///i" -e "s/\.gz$//")
48- #printf "%s\n" "INFO: Considering manpage [$i]"
49+ #printf "%s\n" "DEBUG: Considering manpage [$i]"
50 # shellcheck disable=SC2166
51 if [ ! -s "$manpage" -o -z "$i" ] && [ "$SYMLINK" = "0" ]; then
52- #printf "%s\n" "INFO: Skipping empty manpage [$manpage]"
53+ #printf "%s\n" "DEBUG: Skipping empty manpage [$manpage]"
54 continue
55 fi
56 out="$DESTDIR"/"$i".html
57@@ -91,12 +91,12 @@
58 mkdir -p "$(dirname "$out")" "$outgz" > /dev/null || true
59 if [ "$SYMLINK" = "1" ]; then
60 ln -f -s "$symlink_src_html" "$out"
61- printf "%s\n" "INFO: Created symlink [$out]"
62+ printf "%s\n" "INFO ($(date '+%H:%M:%S.%N')) - ${DIST}: Created symlink [$out]"
63 else
64 if LN=$(zcat "$manpage" | head -n1 | grep "^\.so "); then
65 LN=$(printf "%s" "$LN" | sed -e 's/^\.so /\.\.\//' -e 's/\/\.\.\//\//g' -e 's/$/\.html/')
66 ln -f -s "$LN" "$out"
67- printf "INFO: Created symlink [%s]" "$out"
68+ printf "INFO ($(date '+%H:%M:%S.%N')) - ${DIST}: Created symlink [%s]" "$out"
69 else
70 BODY=$(COLUMNS=100 /usr/lib/w3m/cgi-bin/w3mman2html.cgi "local=$manpage" | grep -A 1000000 "^<b>" | sed -e '/<\/body>/,+100 d' -e 's:^<b>\(.*\)</b>$:</pre><h4><b>\1</b></h4><pre>:g' -e 's:<a href="file\:///[^?]*?\([^(]*\)(\([^)]*\))">:<a href="../man\2/\1.\2.html">:g')
71 TITLE=$(printf "%s" "$BODY" | head -n2 | tail -n1 | sed "s/<[^>]\+>//g")
72@@ -110,7 +110,7 @@
73 $BODY
74 </pre><!--#include virtual='/below.html' -->" > "$out"
75
76- printf "%s\n" "INFO: Created manpage [$out]"
77+ printf "%s\n" "INFO ($(date '+%H:%M:%S.%N')) - ${DIST}: Created manpage [$out]"
78 fi
79 fi
80 mv -f "$manpage" "$outgz"
81@@ -121,7 +121,7 @@
82 done
83
84 # After extracting all manpages, cache the sha1sum, so we don't
85-# repeat the downloads
86+# repeat the downloads
87 sha1sum "$DEB" | awk '{ print $1 }' > "$DESTDIR/.cache/$NAME"
88
89 # In the case of freakish package permissions, fix them on rm failure.
90
91=== modified file 'bin/make-manpage-repo.sh'
92--- bin/make-manpage-repo.sh 2018-06-05 23:29:26 +0000
93+++ bin/make-manpage-repo.sh 2023-12-07 08:37:19 +0000
94@@ -2,23 +2,23 @@
95
96 ###############################################################################
97 # Copyright (C) 2008 Canonical Ltd.
98-#
99+#
100 # This code was originally written by Dustin Kirkland <kirkland@ubuntu.com>,
101 # based on a framework by Kees Cook <kees@ubuntu.com>.
102-#
103+#
104 # This program is free software: you can redistribute it and/or modify
105 # it under the terms of the GNU General Public License as published by
106 # the Free Software Foundation, either version 3 of the License, or
107 # (at your option) any later version.
108-#
109+#
110 # This program is distributed in the hope that it will be useful,
111 # but WITHOUT ANY WARRANTY; without even the implied warranty of
112 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
113 # GNU General Public License for more details.
114-#
115+#
116 # You should have received a copy of the GNU General Public License
117 # along with this program. If not, see <http://www.gnu.org/licenses/>.
118-#
119+#
120 # On Debian-based systems, the complete text of the GNU General Public
121 # License can be found in /usr/share/common-licenses/GPL-3
122 ###############################################################################
123@@ -29,7 +29,9 @@
124 ARCH=""
125
126 # shellcheck disable=SC1090
127-. "$DIR/../config"
128+config="$DIR/../config"
129+echo "INFO: source $config"
130+. "$config"
131
132 # Establish some locking, to keep multiple updates from running
133 mkdir -p "$PUBLIC_HTML_DIR/manpages"
134@@ -46,9 +48,9 @@
135 FORCE="$1"
136
137 get_packages_url() {
138- dist=$1
139- repo=$2
140- arch=$3
141+ local dist=$1
142+ local repo=$2
143+ local arch=$3
144 if [ -e "$DEBDIR/dists/$dist/$repo/binary-$arch/Packages.gz" ]; then
145 echo "file://$DEBDIR/dists/$dist/$repo/binary-$arch/Packages.gz"
146 else
147@@ -58,7 +60,7 @@
148
149
150 get_deb_url() {
151- deb=$1
152+ local deb=$1
153 if [ -e "$DEBDIR/$deb" ]; then
154 echo "file://$DEBDIR/$deb"
155 else
156@@ -71,50 +73,51 @@
157 if [ "$FORCE" = "-f" ] || [ "$FORCE" = "--force" ]; then
158 return 0
159 fi
160- deb="$1"
161- sum="$2"
162- name=$(basename "$deb" | awk -F_ '{print $1}')
163+ local deb="$1"
164+ local sum="$2"
165+ local distnopocket="$3"
166+ local name=$(basename "$deb" | awk -F_ '{print $1}')
167 existing_sum=$(cat "$PUBLIC_HTML_DIR/manpages/$dist/.cache/$name" 2>/dev/null)
168
169 # Take the first two digits of the existing_sum modulo 28 to
170 # compare to the current day of month.
171- #
172+ #
173 # Reasoning: this will invalidate the cache for everything ~
174 # once per month (days: 1-28)
175 day_mod=$((0x$(echo "$existing_sum" | cut -b 1-2)%27 + 1))
176 if [ "$day_mod" -eq "$(date +%d)" ]; then
177- echo "INFO: date_mod match, regnerating: $deb ($day_mod)"
178+ echo "INFO ($(date '+%H:%M:%S.%N')) - ${distnopocket}: date_mod match, regnerating: $deb ($day_mod)"
179 return 0
180 fi
181
182 # Of course, if the sum found in the packages file for this
183 # package does not equal the sum I have on disk, regenerate.
184 if [ "$existing_sum" = "$sum" ]; then
185- echo "INFO: cksum skip: $deb"
186+ echo "INFO ($(date '+%H:%M:%S.%N')) - ${distnopocket}: cksum skip: $deb"
187 return 1
188 else
189- echo "INFO: cksum mismatch: $deb"
190+ echo "INFO ($(date '+%H:%M:%S.%N')) - ${distnopocket}: cksum mismatch: $deb"
191 return 0
192 fi
193 }
194
195 handle_deb() {
196- dist="$1"
197- deb="$2"
198- sum="$3"
199- deburl=$(get_deb_url "$deb")
200+ local distnopocket="$1"
201+ local deb="$2"
202+ local sum="$3"
203+ local deburl=$(get_deb_url "$deb")
204 # FIXME: the || true needs to bubble up to a list of things wrong obviously.
205 # shellcheck disable=SC2015
206- is_pkg_cache_invalid "$deb" "$sum" && "$DIR/fetch-man-pages.sh" "$dist" "$deburl" || true
207+ is_pkg_cache_invalid "$deb" "$sum" "$distnopocket" && "$DIR/fetch-man-pages.sh" "$distnopocket" "$deburl" || true
208 }
209
210 link_en_locale() {
211- dist="$1"
212- mkdir -p "$PUBLIC_HTML_DIR/manpages/$dist/en"
213+ local distnopocket="$1"
214+ mkdir -p "$PUBLIC_HTML_DIR/manpages/$distnopocket/en"
215 for i in $(seq 1 9); do
216 for j in "manpages" "manpages.gz"; do
217- mkdir -p "$PUBLIC_HTML_DIR/$j/$dist/en"
218- dir="$PUBLIC_HTML_DIR/$j/$dist/en/man$i"
219+ mkdir -p "$PUBLIC_HTML_DIR/$j/$distnopocket/en"
220+ dir="$PUBLIC_HTML_DIR/$j/$distnopocket/en/man$i"
221 if [ -L "$dir" ]; then
222 # link exists: we're good
223 continue
224@@ -133,26 +136,61 @@
225 return 0
226 }
227
228-
229+handle_series() {
230+ local dist="${1}"
231+ # On one hand in some cases we do not want to know the pocket, as it
232+ # would show up in paths, URLs and bug links
233+ distnopocket="${dist}"
234+ # Yet on the other hand we need to process all pockets. Without -release
235+ # we'd miss content that never got an update, without -updates we'd not
236+ # pick up changes.
237+ # It orders by likely most up-to-date pocket first and only re-renders if
238+ # a newer version of the same source package is found later (even single
239+ # Packages files can list the same source multiple times).
240+ declare -A pkg_handled
241+ pkg_handled=()
242+ for pocket in "-updates" "-security" ""; do
243+ mkdir -p "$PUBLIC_HTML_DIR/manpages/$distnopocket/.cache" "$PUBLIC_HTML_DIR/manpages.gz/$distnopocket" || true
244+ link_en_locale "$distnopocket"
245+ for repo in $REPOS; do
246+ for arch in $ARCH; do
247+ file=$(get_packages_url "${dist}${pocket}" "$repo" "$arch")
248+ echo "INFO ($(date '+%H:%M:%S.%N')) - ${dist}: Packages.gz: $file"
249+ plist=$(mktemp "/tmp/XXXXXXX.manpages.${dist}${pocket}.$repo.$arch.plist")
250+ curl -s "$file" \
251+ | gunzip -c \
252+ | grep -E "(^Package: |^Version: |^Filename: |^SHA1: )" \
253+ | awk '{print $2}' \
254+ | sed 'N;N;N;s/\n/ /g' \
255+ | sort -u > "${plist}"
256+ while read -r binpkg version deb sum; do
257+ if dpkg --compare-versions "${version}" gt "${pkg_handled["$binpkg"]}"; then
258+ if [[ -n "${pkg_handled[$binpkg]}" ]]; then
259+ echo "INFO ($(date '+%H:%M:%S.%N')) - ${dist}: binpkg: $binpkg ${version} > ${pkg_handled["$binpkg"]} (processing it again)"
260+ else
261+ echo "INFO ($(date '+%H:%M:%S.%N')) - ${dist}: First encounter of binpkg: $binpkg ${version} (processing)"
262+ fi
263+ pkg_handled["$binpkg"]="${version}"
264+ handle_deb "$distnopocket" "$deb" "$sum"
265+ else
266+ echo "INFO ($(date '+%H:%M:%S.%N')) - ${dist}: binpkg: $binpkg ${version} < ${pkg_handled["$binpkg"]} (not processing)"
267+ fi
268+ done < "${plist}"
269+ rm -f "${plist}"
270+ done
271+ done
272+ done
273+
274+}
275+
276+# Simple parallelization on the level of releases; they do not overlap
277+# in regard to directories/files, but doing so help to keep the network
278+# connection utilized as one can fetch while the other is converting.
279+# Furthermore it avoids that issues, or a lot of new content, in one release
280+# (e.g. -dev opened) will make the regular update on the others take ages.
281 for dist in $DISTROS; do
282- export dist
283- mkdir -p "$PUBLIC_HTML_DIR/manpages/$dist/.cache" "$PUBLIC_HTML_DIR/manpages.gz/$dist" || true
284- link_en_locale "$dist"
285- for repo in $REPOS; do
286- for arch in $ARCH; do
287- file=$(get_packages_url "$dist" "$repo" "$arch")
288- echo "INFO: Packages.gz: $file"
289- curl -s "$file" \
290- | gunzip -c \
291- | grep -E "(^Filename: |^SHA1: )" \
292- | awk '{print $2}' \
293- | sed 'N;s/\n/ /' \
294- | sort -u \
295- | while read -r deb sum; do
296- handle_deb "$dist" "$deb" "$sum"
297- done
298- done
299- done
300+ handle_series "${dist}" &
301 done
302+wait
303
304 "$DIR/make-sitemaps.sh"
305
306=== modified file 'bin/manpages-modify-config'
307--- bin/manpages-modify-config 2020-10-21 12:52:23 +0000
308+++ bin/manpages-modify-config 2023-12-07 08:37:19 +0000
309@@ -13,6 +13,10 @@
310 # launchpad = Launchpad.login_anonymously('manpages', launchpadlib.uris.STAGING_SERVICE_ROOT)
311 launchpad = Launchpad.login_anonymously('manpages', launchpadlib.uris.LPNET_SERVICE_ROOT)
312
313+ubuntu = launchpad.distributions["ubuntu"]
314+archive = ubuntu.main_archive
315+current_series = ubuntu.current_series
316+
317 active_series = []
318 for s in launchpad.projects['ubuntu'].series:
319 if not s.active:
320
321=== modified file 'bin/w3mman-to-html.pl'
322--- bin/w3mman-to-html.pl 2009-02-09 21:37:49 +0000
323+++ bin/w3mman-to-html.pl 2023-12-07 08:37:19 +0000
324@@ -2,25 +2,25 @@
325
326 ##############################################################################
327 # This is the Ubuntu manpage repository generator and interface.
328-#
329+#
330 # Copyright (C) 2008 Canonical Ltd.
331-#
332+#
333 # This code was originally written by Dustin Kirkland <kirkland@ubuntu.com>,
334 # based on a framework by Kees Cook <kees@ubuntu.com>.
335-#
336+#
337 # This program is free software: you can redistribute it and/or modify
338 # it under the terms of the GNU General Public License as published by
339 # the Free Software Foundation, either version 3 of the License, or
340 # (at your option) any later version.
341-#
342+#
343 # This program is distributed in the hope that it will be useful,
344 # but WITHOUT ANY WARRANTY; without even the implied warranty of
345 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
346 # GNU General Public License for more details.
347-#
348+#
349 # You should have received a copy of the GNU General Public License
350 # along with this program. If not, see <http://www.gnu.org/licenses/>.
351-#
352+#
353 # On Debian-based systems, the complete text of the GNU General Public
354 # License can be found in /usr/share/common-licenses/GPL-3
355 ##############################################################################
356
357=== modified file 'cgi-bin/search.py'
358--- cgi-bin/search.py 2021-04-23 15:01:08 +0000
359+++ cgi-bin/search.py 2023-12-07 08:37:19 +0000
360@@ -114,11 +114,11 @@
361 lr = "en"
362
363 versions = OrderedDict()
364-versions["bionic"] = "18.04 LTS"
365 versions["focal"] = "20.04 LTS"
366-versions["groovy"] = "20.10"
367-versions["hirsute"] = "21.04"
368-versions["impish"] = "21.10"
369+versions["jammy"] = "22.04 LTS"
370+versions["lunar"] = "23.04"
371+versions["mantic"] = "23.10"
372+versions["noble"] = "24.04 LTS"
373
374 distros = versions.keys()
375 title_html += ("</div></div><div class='p-strip u-no-padding--top'>"
376
377=== modified file 'examples/config'
378--- examples/config 2021-04-23 15:01:08 +0000
379+++ examples/config 2023-12-07 08:37:19 +0000
380@@ -20,7 +20,8 @@
381 SITE="http://manpages.ubuntu.com"
382
383 # These form a combinatorial download matrix
384-DISTROS="impish hirsute groovy focal bionic"
385+# The release names will be automatically modified by bin/manpages-modify-config
386+DISTROS="noble mantic lunar jammy focal"
387 REPOS="main restricted universe multiverse"
388
389 # For now, stick to a single arch
390
391=== modified file 'www/functions.js'
392--- www/functions.js 2021-04-23 15:01:08 +0000
393+++ www/functions.js 2023-12-07 08:37:19 +0000
394@@ -58,11 +58,11 @@
395 var navigationContainer = document.getElementById('navigation-container');
396 var navigationOutput = "";
397 versions = new Array();
398- versions.push({"name":"bionic", "number":"18.04 LTS"});
399 versions.push({"name":"focal", "number":"20.04 LTS"});
400- versions.push({"name":"groovy", "number":"20.10"});
401- versions.push({"name":"hirsute", "number":"21.04"});
402- versions.push({"name":"impish", "number":"21.10"});
403+ versions.push({"name":"jammy", "number":"22.04 LTS"});
404+ versions.push({"name":"lunar", "number":"23.04"});
405+ versions.push({"name":"mantic", "number":"23.10"});
406+ versions.push({"name":"noble", "number":"24.04 LTS"});
407 for (var i = 0; i < versions.length; i++) {
408 if (location.href.match("\.html$")) {
409 href = location.href;

Subscribers

People subscribed via source and target branches