93a3129444064c4d19b4cb49e24b6de4bd0e06a6 markd Fri Oct 2 21:41:15 2026 -0700 Restore the superTrack nesting fix. refs #38460 Taking this off master was over-cautious. It is wanted on its own: #38460 is a real bug, a superTrack given a parent passes tdbQuery -check -strict and is then silently dropped at load, and the fix should get into a release rather than wait on a trackDb change that uses it. Nothing on master nests yet, and the fix is behaviour-identical where nothing does: trackDb built with it and with the released hgTrackDb is byte-identical on hg38, hs1 and mm39, and hgTracks renders the same rows and supertrack controls as dev. The TSS trackDb change that uses this stays reverted, on superTrackNesting, until this ships. Whoever releases it: trackDbToTxt has to be rebuilt too, or the curated hub it writes names a parent stanza it did not emit. diff --git src/hg/lib/trackDbCustom.c src/hg/lib/trackDbCustom.c index 4fddeb2ca84..a0e6a842070 100644 --- src/hg/lib/trackDbCustom.c +++ src/hg/lib/trackDbCustom.c @@ -613,30 +613,34 @@ } char *maybeSkipHubPrefix(char *track) { if (!startsWith("hub_", track)) return track; char *nextUnderBar = strchr(track + sizeof "hub_", '_'); if (nextUnderBar) return nextUnderBar + 1; return track; } +#define maxSuperTrackDepth 10 +// No real trackDb nests supertracks anywhere near this deep; the limit is only +// here so a malformed parent chain cannot spin forever. + void trackDbSuperMarkup(struct trackDb *tdbList) /* Set trackDb from superTrack setting */ { struct trackDb *tdb; struct hash *superHash = hashNew(0); char *setting = NULL; char *words[3]; int wordCt = 0; /* find supertracks, setup their settings */ for (tdb = tdbList; tdb != NULL; tdb = tdb->next) { setting = trackDbLocalSetting(tdb, "superTrack"); if (!setting) continue; @@ -645,55 +649,82 @@ { if (!hashLookup(superHash, tdb->track)) { hashAdd(superHash, maybeSkipHubPrefix(tdb->track), tdb); tdbMarkAsSuperTrack(tdb); if ((wordCt > 1) && sameString("show", words[1])) tdb->isShow = TRUE; } } freeMem(words[0]); } /* adjust settings on supertrack members after verifying they have * a supertrack configured in this trackDb */ for (tdb = tdbList; tdb != NULL; tdb = tdb->next) { - if (tdbIsSuperTrack(tdb) || tdb->parent != NULL) + if (tdb->parent != NULL) continue; setting = trackDbLocalSetting(tdb, "parent"); - if (!setting) + // A superTrack may name a parent superTrack, which nests it one level deeper. + // Its own "superTrack on" is not a parent reference, so only a non-super may + // fall back to the old style, where the setting holds the parent's name. + if (!setting && !tdbIsSuperTrack(tdb)) setting = trackDbLocalSetting(tdb, "superTrack"); // Old style if (!setting) continue; wordCt = chopLine(cloneString(setting), words); assert(differentString("on", words[0])); // already weeded out "superTrack on" char *parentName = maybeSkipHubPrefix(words[0]); tdb->parent = hashFindVal(superHash, parentName); if (tdb->parent == tdb) { warn("Track %s lists itself as its own parent, ignoring.", tdb->track); tdb->parent = NULL; } if (tdb->parent) { tdbMarkAsSuperTrackChild(tdb); tdb->parentName = cloneString(parentName); if (wordCt > 1) tdb->visibility = max(0, hTvFromStringNoAbort(words[1])); } freeMem(words[0]); } + +/* Now that every link is set, make sure the supertracks form a tree. Nesting + * means the parent chain is walked rather than looked at one level, so a loop + * would hang every walker instead of being harmless. Only a supertrack can + * close a loop here, since nothing else is given a parent by this routine, so + * breaking it at a supertrack names the track whose setting is wrong and leaves + * the members attached to their folder. */ +for (tdb = tdbList; tdb != NULL; tdb = tdb->next) + { + if (!tdbIsSuperTrack(tdb) || tdb->parent == NULL) + continue; + struct trackDb *ancestor; + int depth = 0; + for (ancestor = tdb->parent; ancestor != NULL; ancestor = ancestor->parent) + { + if (ancestor == tdb || ++depth > maxSuperTrackDepth) + { + warn("Supertrack %s is its own ancestor through its parent setting, " + "ignoring that setting.", tdb->track); + tdb->parent = NULL; + break; + } + } + } hashFree(&superHash); } char *trackDbOrigAssembly(struct trackDb *tdb) /* return setting from trackDb, if any */ { return (trackDbSetting(tdb, "origAssembly")); } void trackDbPrintOrigAssembly(struct trackDb *tdb, char *database) /* Print lift information from trackDb, if any */ { char *origAssembly = trackDbOrigAssembly(tdb); if (origAssembly) {