e357453b3740779250e8cf80c2f5104eb77c824b
braney
  Thu Sep 10 11:23:20 2026 -0700
hgConfCatalog: report the hg.conf lines a flipped default made pointless, refs #37925

Flipping a gate's default to TRUE does not turn a feature on anywhere: it was
already on wherever somebody had appended the flag by hand while the feature
sat in QA.  What the flip does is make those lines pointless, and while one is
there the flag reads on locally whatever the tree says, which is how a wrong
default survives on the machine most likely to catch it.

--redundant reads the hg.conf files readable from hgwdev, joins them against
the catalog and names the lines to delete.  It keeps three populations apart:
a line that turns a shipped gate on is litter, a line that turns one off is a
live decision and has to be left alone, and a value that is not one of the six
words hgConfig.c accepts aborts every CGI that reads it.  Silent and exit 0
when there is nothing to delete, the same shape as --reconcile, so
nightlyRegister.sh runs it as a third pass and the reminder rides the mail that
already goes out.

Only gates are considered.  A knob is a switch a machine is entitled to set
forever, so reporting one would be the crying wolf the gate/knob split exists
to prevent.

diff --git src/hg/utils/hgConfCatalog/nightlyRegister.sh src/hg/utils/hgConfCatalog/nightlyRegister.sh
index e2d58f20dce..8cfcd5514a5 100755
--- src/hg/utils/hgConfCatalog/nightlyRegister.sh
+++ src/hg/utils/hgConfCatalog/nightlyRegister.sh
@@ -1,28 +1,34 @@
 #!/bin/bash
 # Keep hgConfCatalog.py's mechanical fields true without asking a developer for
 # anything, and report only what needs a person's judgement.  Refs #37925.
 #
 # The premise is that a developer adding an hg.conf read should not have to know
 # this catalog exists.  Asking them to did not work: hubSpaceLockTimeout arrived
 # as one supporting line inside an unrelated bugfix and nothing noticed until
 # somebody ran --reconcile by hand a day later.  So this job writes down the
 # facts itself, nightly, then commits and pushes them.  It leaves exactly one
 # thing for a human: deciding what each new setting is for, and whether a
 # boolean is a release gate or a mirror knob.  Those two are refused on
 # purpose; see auto_register()'s comments in hgConfCatalog.py.
 #
+# It carries one errand of its own, on the same terms.  When a gate's default
+# is flipped TRUE the hand-written line that turned it on in some machine's
+# hg.conf becomes pointless, and nobody is watching for that, so --redundant
+# names those lines here.  Deleting one is a judgement about a file this job
+# does not own, so it only ever reports them.
+#
 # It runs in the weekly build's own tree, and that tree is shared with a build
 # process that will not tolerate surprises, so three rules are absolute:
 #
 #   1. master only.  cherryPickCommits.csh and tagBeta.csh both cd into
 #      $BUILDHOME/kent/src/utils/qa/weeklybld and check out v${NN}_branch or
 #      beta before coming back to master.  Committing during that window would
 #      put an unreviewed row on a release branch and push it.  Release branches
 #      belong to the build process alone, so if HEAD is not master this job does
 #      nothing at all and tries again tomorrow.
 #   2. never leave the tree dirty.  autoBuild.sh's ensure_clean_git() aborts the
 #      whole build on any uncommitted change in that tree.  Every exit path here
 #      either commits or restores the file, via the trap below.
 #   3. never run beside a build.  /tmp/autoBuild.lock means one is in progress.
 #
 # Everything it writes is regenerable from the tree, so on any doubt it throws
@@ -131,52 +137,76 @@
 #
 # There used to be a second pass here, --fix-citations, which chased the line
 # numbers the catalog stored beside each read.  It is gone, and so is the field
 # it repaired.  Line numbers are derived data, and storing them by hand meant a
 # single insertion near the top of hgTracks.c moved fifteen citations at once:
 # of the sixteen commits this job pushed between 2026-08-07 and 2026-08-28,
 # thirteen changed nothing else.  A row now cites the file, and the line is
 # recomputed for display.  So this job commits only when a setting is genuinely
 # new, which was three of those sixteen nights.
 "$CATALOG" --auto-register  > "$work/register" 2>&1
 
 # What is left after the machine has done all it honestly can.  Non-zero
 # whenever a row is waiting to be classified, so the exit code is information.
 "$CATALOG" --reconcile > "$work/reconcile" 2>&1 || true
 
+# The third pass, and the only one that looks outside the tree.  Flipping a
+# gate's default to TRUE leaves a line behind in every hg.conf somebody had
+# turned it on in by hand, and that line then reads on locally whatever the
+# tree says, which is how a wrong default survives on the one machine that
+# would notice.  Silent unless there is a line to delete, so it costs nothing
+# on the nights there is nothing to say.  Non-zero is the news, not an error,
+# hence || true under set -e.
+"$CATALOG" --redundant > "$work/redundant" 2>&1 || true
+redundant=$(sed -n 's/^REDUNDANT .*: \([0-9][0-9]*\)$/\1/p' \
+    "$work/redundant" | head -1)
+: "${redundant:=0}"
+
+# Both mail paths below want this section on the same terms, and the wording
+# has to say whose job it is: the shared conf on this host is not the reporter's
+# to edit, and hgwbeta and the RR cannot be read from here at all.
+redundant_notes() {
+    [[ -s $work/redundant ]] || return 0
+    echo
+    echo "hg.conf lines a flipped default has made pointless:"
+    sed 's/^/  /' "$work/redundant"
+}
+
 # State worth carrying in one line: a bare pulse would say the cron is alive
 # without saying whether it is doing anything, and the point of the heartbeat is
 # to keep this job in mind rather than merely prove it ran.  Count the holding
 # pen itself, not reconcile's "problems:" total: that total also carries wrong
 # hand-written defaults and citations whose call site left the file, neither of
 # which is a row awaiting classification, and a heartbeat that quietly reports
 # the wrong number is worse than one that reports none.
 waiting=$(sed -n \
     's/^written down but not classified (\([0-9]*\)).*/\1/p' \
     "$work/reconcile" | head -1)
 : "${waiting:=0}"
 at=$(git rev-parse --short HEAD)
 
 if git diff --quiet -- "$RELPATH"; then
-    beat "nothing to register, $waiting awaiting classification, tree at $at"
+    beat "nothing to register, $waiting awaiting classification," \
+         "$redundant conf line(s) to delete, tree at $at"
     # Still speak up if reconcile found something the machine cannot fix on its
     # own, since that is the whole point of running.  This goes out whatever the
     # heartbeat setting: it is news, not a pulse.
     if [[ -s $work/reconcile ]]; then
         echo "hg.conf catalog: nothing to register, but --reconcile has notes:"
         cat "$work/reconcile"
     fi
+    redundant_notes
     exit 0
 fi
 
 # Name the settings in the subject line so the commit reads like a person wrote
 # it, and keep it under a sensible width when there are many.
 # Take the names from --auto-register's own report rather than from the diff.
 # Reading them off "+" lines used to credit the job with registering settings it
 # had only re-cited, because --fix-citations rewrote whole h("name", ...) lines
 # and every one of them matched.  The report says what was actually written.
 names=$(sed -n 's/^    \([A-Za-z0-9_.{}]*\) .*/\1/p' "$work/register" | paste -sd, - )
 count=$(echo "$names" | tr ',' '\n' | grep -c . || true)
 if [[ -z $names ]]; then
     subject="hgConfCatalog: update the catalog, refs #37925"
 elif [[ ${#names} -le 60 ]]; then
     subject="hgConfCatalog: register $names, refs #37925"
@@ -203,23 +233,25 @@
 
 if [[ $PUSH == yes ]]; then
     if ! git push --quiet origin master 2>"$work/pusherr"; then
         # Somebody landed something between the fetch and now.  The work is
         # regenerable, so drop it rather than leave an unpushed commit sitting
         # in the build's tree where the next build would carry it along.
         git reset --hard --quiet origin/master
         committed=yes   # tree is clean again; nothing for the trap to undo
         beat "push rejected, dropped the commit, will redo it tomorrow"
         echo "hg.conf catalog: push rejected, dropped the commit and will redo it tomorrow."
         cat "$work/pusherr"
         exit 0
     fi
 fi
 
-beat "committed ${count:-0} row(s) at $(git rev-parse --short HEAD), $waiting awaiting classification"
+beat "committed ${count:-0} row(s) at $(git rev-parse --short HEAD)," \
+     "$waiting awaiting classification, $redundant conf line(s) to delete"
 echo "hg.conf catalog: $subject"
 echo
 sed 's/^/  /' "$work/register"
 echo
 echo "Left for a person to decide:"
 sed 's/^/  /' "$work/reconcile"
+redundant_notes
 exit 0