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/hgTracks/hgTracks.c src/hg/hgTracks/hgTracks.c index 84029b89831..65785f37809 100644 --- src/hg/hgTracks/hgTracks.c +++ src/hg/hgTracks/hgTracks.c @@ -7149,32 +7149,35 @@ struct trackDb *tdbList = hubCollectTracks(database, pGrpList); addTdbListToTrackList(tdbList, NULL, pTrackList); } boolean restrictionEnzymesOk() /* Check to see if it's OK to do restriction enzymes. */ { return (sqlDatabaseExists("hgFixed") && hTableExists("hgFixed", "cutters") && hTableExists("hgFixed", "rebaseRefs") && hTableExists("hgFixed", "rebaseCompanies")); } static void setSuperTrackHasVisibleMembers(struct trackDb *tdb) /* Determine if any member tracks are visible -- currently - * recording this in the parent's visibility setting */ + * recording this in the parent's visibility setting. A supertrack may itself + * be a member of another supertrack, and a visible track makes every supertrack + * above it visible too. */ { +for (; tdb != NULL; tdb = tdbIsSuperTrackChild(tdb) ? tdb->parent : NULL) tdb->visibility = tvDense; } boolean superTrackHasVisibleMembers(struct trackDb *tdb) /* Determine if any member tracks are visible -- currently * recording this in the parent's visibility setting */ { if (!tdbIsSuper(tdb)) return FALSE; return (tdb->visibility != tvHide); } int hubCmpAlpha(const void *va, const void *vb) /* Compare to sort hubs based on name */ { @@ -7433,57 +7436,77 @@ if (!group || !group->trackList) return; for (tr = group->trackList; tr != NULL; tr = tr->next) { struct track *track = tr->track; AllocVar(ref); ref->track = track; slAddHead(&newList, ref); if (tdbIsSuperTrackChild(track->tdb)) { assert(track->tdb->parentName != NULL); if (hTvFromString(cartUsualString(cart, track->track, hStringFromTv(track->tdb->visibility))) != tvHide) setSuperTrackHasVisibleMembers(track->tdb->parent); assert(track->parent == NULL); - track->parent = hashFindVal(superHash, track->tdb->parentName); - if (track->parent) - continue; + + /* Walk up the chain of supertracks, making a track for each the first + * time one of its members is seen. A supertrack may itself be a member + * of another supertrack, and only the innermost is reached from the + * group's track list. A supertrack is keyed by the name its members + * call it, which for a hub track is the name without the hub prefix. */ + struct track *child = track; + struct trackDb *superTdb = track->tdb->parent; + char *superName = track->tdb->parentName; + while (superTdb != NULL) + { + struct track *superTrack = hashFindVal(superHash, superName); + if (superTrack != NULL) + { /* already made, and so were its own ancestors */ + child->parent = superTrack; + break; + } /* create track and reference for the supertrack */ - struct track *superTrack = track->parent = trackFromTrackDb(track->tdb->parent); - track->parent = superTrack; + superTrack = trackFromTrackDb(superTdb); + child->parent = superTrack; if (trackHashRef != NULL) hashAddUnique(trackHashRef,superTrack->track,superTrack); superTrack->hasUi = TRUE; superTrack->group = track->group; superTrack->groupName = cloneString(track->group->name); superTrack->defaultGroupName = cloneString(track->group->name); /* handle track reordering */ char cartVar[256]; - safef(cartVar, sizeof(cartVar), "%s.priority",track->tdb->parentName); - float priority = (float)cartUsualDouble(cart, cartVar, - track->tdb->parent->priority); + safef(cartVar, sizeof(cartVar), "%s.priority",superName); + float priority = (float)cartUsualDouble(cart, cartVar, superTdb->priority); /* remove cart variables that are the same as the trackDb settings */ - if (priority == track->tdb->parent->priority) + if (priority == superTdb->priority) cartRemove(cart, cartVar); superTrack->priority = priority; AllocVar(ref); ref->track = superTrack; slAddHead(&newList, ref); - hashAdd(superHash, track->tdb->parentName, superTrack); + hashAdd(superHash, superName, superTrack); + + if (!tdbIsSuperTrackChild(superTdb)) + break; + child = superTrack; + superName = superTdb->parentName; + superTdb = superTdb->parent; + } } } slSort(&newList, trackRefCmpPriority); /* we could free the old track list here, but it's a trivial amount of mem */ group->trackList = newList; } void topButton(char *var, char *label) /* create a 3 or 4-char wide button for top line of display. * 3 chars wide for odd-length labels, 4 for even length. * Pad with spaces so label is centered */ { char paddedLabel[5] = " "; int len = strlen(label); if (len > 4)