5ee674363623678393bb186b1f5fdfd981ee743f
braney
Mon Aug 17 10:35:26 2026 -0700
hgTracks: default multiRegionButtonTop TRUE in config.c too, refs #37977
Two places read multiRegionButtonTop through MULTI_REGION_CFG_BUTTON_TOP: the
top-bar Multi-region button in hgTracks.c and the "Show all" checkbox in the
multi-region dialog in config.c. Kate put the checkbox under this flag on
purpose in 8ae222adb54. Then e1f7896a08f turned the flag on by default in
2022 and changed only the hgTracks.c read, so on a machine that does not set
the flag the button moved to the top bar while the checkbox stayed hidden.
It has been hidden for four years, although the cart variable it sets is read
unconditionally at hgTracks.c:11115, so the feature works for anyone who can
reach the variable another way.
The hg.conf catalog row carried this as an open defect, so its note is
rewritten and its recorded default now matches the tree.
diff --git src/hg/utils/hgConfCatalog/hgConfCatalog.py src/hg/utils/hgConfCatalog/hgConfCatalog.py
index a9ac72befe1..cb504eb3159 100755
--- src/hg/utils/hgConfCatalog/hgConfCatalog.py
+++ src/hg/utils/hgConfCatalog/hgConfCatalog.py
@@ -1,2754 +1,2754 @@
#!/usr/bin/env python3
"""hgConfCatalog.py - the registry of hg.conf variables.
Third of the browser configuration inventories, after cartTrackVarCatalog.py
(#37838, track-scoped cart variables) and urlCommandCatalog.py (#37923, CGI URL
parameters). This one covers hg.conf: the per-machine configuration file every
CGI reads at startup through the cfgOption* accessors in hg/lib/hgConfig.c.
What makes hg.conf different from the other two, and the reason this file
needed a mechanism the others do not have:
A cart variable belongs to a user and a URL parameter belongs to a request.
An hg.conf variable belongs to a machine, and a machine we do not control.
Mirrors, the GBiB and the GBiC all carry their own hg.conf, so a variable is
reachable long after the tree stops caring about it, and deleting one is a
compatibility decision rather than a cleanup.
On top of that, the tree deliberately manufactures short-lived hg.conf
variables as part of the release process. A user-visible feature is expected
to ship dark behind cfgOptionBooleanDefault(name, FALSE) so it can sit on
master through QA without blocking the release train, then have its default
flipped to TRUE once it is released, then have the flag deleted. That last
step is the one nobody does. Nothing in the tree records that a flag was
meant to be temporary, so shipped gates accumulate: showTutorial has been
defaulting TRUE since v466, greyBarIcons since v492, and both are still
branch points in hgTracks today.
So the registry separates two populations that look identical in the source:
gate introduced to gate a release. Temporary by intent. Has a lifecycle
(added, flipped, sunset) and is expected to be deleted.
knob a genuine deployment switch a mirror is entitled to set forever.
isGbib, browser.dumpStack and hgta.disableAllTables are knobs. These
are exempt from sunsetting and saying so explicitly is what keeps the
report from crying wolf.
The distinction cannot be made mechanically, which is why it is curated here
rather than harvested. Everything else about a gate's lifecycle is mechanical:
harvestHgConf.py --age dates each variable's first read, and each boolean
flag's first TRUE default, out of the git history, mapped onto the CGI_VERSION
in effect at the time. So `added` and `flipped` come from the tree and only
`sunset` is a judgement call. A gate with no explicit sunset gets
flipped + KEEP_AFTER_FLIP, which is the release after mirrors have had a cycle
to object.
Sunset policy, all in versions (releases are about three weeks apart):
KEEP_AFTER_FLIP = 4 once the default is TRUE the feature is public. Keep
the flag four more releases so a mirror or hgwbeta can
switch it back off without a code change, then delete
it along with every branch that reads it.
QA_GRACE = 6 a gate still defaulting FALSE this long after it landed
is not being gated, it is being forgotten. Either turn
it on or delete the feature.
Verification status. Rows carry verified=True only where the classification
was confirmed by reading the code at the cited file:line. --check counts what
is left, because a variable described as a permanent knob when it is really a
forgotten gate defeats the purpose of the exercise.
Usage:
hgConfCatalog.py --json out.json
hgConfCatalog.py --html out.html
hgConfCatalog.py --check # counts and internal consistency
hgConfCatalog.py --reconcile # diff the catalog against the tree
hgConfCatalog.py --reconcile --verbose # ... with the standing drift
hgConfCatalog.py --sunset # what should be deleted, and when
hgConfCatalog.py --sunset-new # ... only what changed since the backlog
hgConfCatalog.py --update-baseline # accept the current backlog
hgConfCatalog.py --refresh --sunset-new # ... rebuilding the age cache
hgConfCatalog.py --cache /tmp/ages.json --refresh --sunset-new
# ... without writing into the tree
--reconcile is the mode meant for a nightly cron: it prints nothing and exits 0
when the tree holds no setting the catalog has not classified, and exits 1 with
the list when somebody has added one. --sunset-new is the same shape for the
release side, and is what belongs in the weekly build's wrap-up: it reports the
gates that went overdue or stalled since the backlog file was last accepted, and
the ones that were cleaned up, and says nothing about the standing backlog.
--sunset itself reports every overdue gate on every run whether or not anything
changed. That is the right thing for a person working the list down and the
wrong thing for anything automated, which is why it is a separate mode.
"""
import argparse
import html
import json
import os
import re
import sys
# Sunset policy, in releases. See the module docstring.
KEEP_AFTER_FLIP = 4
QA_GRACE = 6
# The gates already known to be overdue or stalled, so --sunset-new can report
# what changed rather than the whole standing list. Regenerate with
# --update-baseline and commit it; the diff is then the release-to-release
# history of the backlog, in the git log rather than in somebody's memory.
BACKLOG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"hgConfGateBacklog.txt")
# Floor on how many settings a working scan finds; well under the real count.
# See the check in reconcile().
MIN_TREE_NAMES = 150
REDMINE = "https://redmine.gi.ucsc.edu/issues/%d"
# ---------------------------------------------------------------------------
# helpers
# ---------------------------------------------------------------------------
def h(name, kind, src, default=None, note=None, public=False, verified=False,
role=None, sunset=None, env=None, deprecated=False, family=None,
required=False, ticket=None, debatable=None):
"""One catalog entry.
name the hg.conf setting name
kind what sort of setting: path, table, profile, credential, url,
email, limit, flag, branding, debug, internal, dead
src file:line where the tree reads it
default compiled-in default if the read supplies one
role for boolean flags only: "gate" (a release gate, temporary by
intent, subject to sunsetting) or "knob" (a permanent
deployment switch, exempt). Every cfgOptionBooleanDefault
flag in the tree must be one or the other; --reconcile
enforces that
sunset release by which a gate should be gone from the tree. Omit to
take the default of flipped + KEEP_AFTER_FLIP; the age cache
supplies the flip version
public documented for mirror operators in product/ex.hg.conf, or
belongs there
verified True if the classification was confirmed by reading src
env environment variable that overrides it, via cfgOptionEnv
family groups members of one prefix family, for the docs
required read with cfgVal, so the CGI dies if it is absent
deprecated the feature it configures is gone or going
ticket Redmine ticket that introduced or tracks it
debatable why this row's gate-or-knob call could reasonably go the other
way. Set it rather than picking a side quietly: the whole
point of separating the two is that someone has decided, and a
decision nobody argued with is not the same as a decision.
--check lists these as the review agenda
"""
d = {"name": name, "kind": kind, "src": src, "public": public,
"verified": verified}
for key, val in (("default", default), ("note", note), ("role", role),
("sunset", sunset), ("env", env), ("family", family),
("ticket", ticket), ("debatable", debatable)):
if val is not None:
d[key] = val
if required:
d["required"] = True
if deprecated:
d["deprecated"] = True
return d
# ---------------------------------------------------------------------------
# how hg.conf is read at all
# ---------------------------------------------------------------------------
ACCESSORS = {
"cfgOption": "Returns the value or NULL. Absent means off.",
"cfgOptionDefault": "Returns the value or a compiled-in default.",
"cfgOptionBooleanDefault":
"Boolean with a compiled-in default. Accepts yes/no, on/off, "
"true/false. This is the accessor used to gate a release.",
"cfgVal": "Returns the value or errAborts. The CGI will not run without it.",
"cfgOptionEnv":
"Environment variable first, then hg.conf. Note the argument order: "
"the environment name comes first and is not an hg.conf setting.",
"cfgOptionEnvDefault": "As cfgOptionEnv, with a compiled-in default.",
"cfgOption2":
"Reads prefix.suffix. The prefix is usually a runtime value, which is "
"how one call site serves db.host, central.host and every other "
"profile at once.",
"cfgOptionDefault2": "As cfgOption2, with a compiled-in default.",
}
BOUNDARY = (
"hg.conf is read once per CGI invocation and is never written by the "
"browser. It is not user state: nothing in a session, a saved session or "
"a URL can change it, which is exactly why it is the right place to gate "
"a feature during a release. A mirror's copy is outside our control, so "
"removing a variable has to be treated as an interface change, not a "
"cleanup. Precedence for a value is: the environment (only for the "
"cfgOptionEnv settings, and only where the CGI allows it), then hg.conf, "
"then the compiled-in default."
)
PROFILE_SUFFIXES = {
"what":
"A database profile is a set of hg.conf settings sharing one prefix, "
"read through cfgOption2(profileName, suffix) where profileName is a "
"runtime value. So these suffixes are legal under any profile prefix, "
"and none of the resulting names appears as a literal anywhere in the "
"tree. This is why product/ex.hg.conf documents "
"archivecentral.password while a search of the source finds nothing.",
"src": "hg/lib/jksql.c:231",
"suffixes": ["host", "port", "socket", "user", "password", "db",
"verifyServerCert", "ca", "caPath", "cert", "key", "cipher",
"crl", "crlPath"],
"knownProfiles": ["db", "central", "cart", "customTracks", "archivecentral",
"backupcentral", "myStuff", "myGenome", "rrcentral", "pq",
"rtdb", "cdw"],
}
# ---------------------------------------------------------------------------
# release gates: boolean flags that exist to hold a feature back
# ---------------------------------------------------------------------------
# These are the reason this catalog has a sunset mode. Each was added so a
# user-visible change could sit on master without shipping. Ordered by age so
# the backlog is visible at a glance.
RELEASE_GATES = {
"what": "Boolean flags introduced to ship a feature dark during a release. "
"Temporary by intent: each should be deleted once the feature it "
"guards is public and mirrors have had a cycle to object.",
"vars": [
h("showMouseovers", "flag", "hg/hgTracks/config.c:703", default="FALSE",
role="gate", verified=True,
note="Mouseover text on track items instead of the browser's own "
"title tooltips. Added v446 and still defaulting FALSE, which "
"makes it the oldest gate in the tree that never shipped. Four "
"call sites across config.c and imageV2.c. Either the feature "
"is wanted, in which case flip it, or it is not, in which case "
"the flag and the code behind it should go."),
h("storeUserFiles", "flag", "hg/hgHubConnect/hgHubConnect.c:1730",
default="FALSE", role="gate", verified=True,
note="Hub space, the user file store behind hgHubConnect's upload "
"wizard. Added v447 and briefly defaulted TRUE around v454 "
"before going back to FALSE, so the history shows a flip that "
"was reverted. Four call sites."),
h("hgSession.shortLink", "flag", "hg/hgSession/hgSession.c:179",
default="FALSE", role="gate", verified=True,
note="Short session links. Added v374 and never flipped, which is "
"the longest-running dark feature here."),
h("showHubApiKey", "flag", "hg/hgHubConnect/hgHubConnect.c:571",
default="FALSE", role="gate", verified=True,
note="Expose the hub API key UI. Shares its call site with "
"storeUserFiles, so the two should be retired together."),
h("autoBlatBigPsl", "flag", "hg/hgBlat/hgBlat.c:3375",
default="FALSE", role="gate", verified=True, ticket="32751",
note="Always create a custom track from BLAT results, so a result "
"page can be reopened and shared. The read at hgBlat.c:2972 "
"overrides the file-scope autoBigPsl, which is initialised "
"FALSE at hgBlat.c:60 under the comment \"DEFAULT VALUE change "
"to TRUE in future\", and eleven branches downstream test it. "
"Filed as a knob until that was read, on the strength of the "
"default being an identifier the harvester could not resolve; "
"a flag whose own source says to flip it later is a gate."),
h("blatShowLocus", "flag", "hg/hgBlat/hgBlat.c:1164", default="FALSE",
role="gate", verified=True,
note="Show the genomic locus alongside BLAT results."),
h("modernAlignPage", "flag", "hg/hgc/hgc.c:9348", default="FALSE",
role="gate", verified=True, ticket="37893",
note="Use the modern single-page base-by-base alignment view (the one "
"hgBlat's new results page uses) for ordinary alignment clicks too "
"- mRNA/EST, PSL and similar hgc details - instead of the classic "
"two-frame