1a9c722ad341c178dc418034e51a1d015507db9b markd Fri Oct 2 20:23:47 2026 -0700 Let superTracks nest inside superTracks. refs #38460 A superTrack given a parent used to pass tdbQuery -check -strict and then be dropped at load, with no warning: the browser reported the outer folder as not found. Seven places each looked exactly one level up the parent chain, which is what trackDb.h means by "Folders are superTracks. Currently only one level deep". trackDbSuperMarkup skipped any track that was itself a superTrack when resolving parent, so the link was never made rFindTrack looked "to the sky" one level, so the outer super could not be found by name addChildRefsToParents only walked tdbList, which excludes superTracks, so an inner super never reached its parent's children list findSuperTrack matched tdb->parent->track only, so hgTrackUi could not find the outer folder of a hub assembly flatten rescued the immediate super parent only, so the outer one was never written to the table polishSupers polished the immediate parent only, so the outer super reached the write with a NULL shortLabel and segfaulted groupTrackListAddSuper made a track for one super above a member setSuperTrackHasVisibleMembers marked one level visible Each now walks the chain. The group list still shows only the outermost folder, since superTrack members are not listed there; the inner folders appear on its page with their own controls, as members always have. Walking the chain makes a parent loop fatal where it used to be harmless, so trackDbSuperMarkup now checks that the supertracks form a tree once every link is set, and breaks a loop at the supertrack that closes it, naming that track. Verified by planting a loop: before, hgTrackDb spun in trackDbSetting and had to be killed; now it warns and loads. The same hazard in composite parents is untouched and filed as #38471. Regression: trackDb built with this and with the released hgTrackDb is byte-identical on mm39 (20377 rows) and, on a tree with no nesting, on hg38 (55629) and hs1 (782). With a nested trackDb the only difference on hg38 is the one rescued container row. hgTracks on hg38 renders the same 32 rows and the same 10 supertrack controls as dev, and the wgEncodeReg member list is unchanged. Note for whoever deploys this: trackDbToTxt also has to be rebuilt. It writes the curated hub trackDb.txt from the table, and a released one emits a member whose parent stanza is missing, which breaks the hub. 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)