e70c7f68f61a3477631bd364c31f99c157528fc8
braney
  Fri Sep 4 12:48:02 2026 -0700
quickLift: lift chains, and let chain and bigChain tracks into the hub

A chain is an alignment between the assembly the track came from and some other
species, so lifting one composes two alignments and leaves the user with chains
between the assembly on screen and that species.

quickLiftChain does it by the same route the psl work uses:  chainToPsl, then
quickLiftPsl, then chainFromPsl, which is the inverse chainToPsl never had.  It
does not modify the chain handed to it.  That matters more than it sounds:  every
chain loader leaves the header describing the whole chain while loading only the
blocks in range, so the header has to be corrected for the conversion, but the
callers still want the original.  The details page reports it, and the track
sorts on it, which is what puts a lifted chain in the same row as the native one.

quickLiftSourceRanges hands back the ranges in the other assembly that the window
maps to, for callers whose items cannot be had from a query quickLiftSql knows
how to make.  It works those ranges out by intersecting the window with each
chain block rather than taking the whole block, which matters here because one
block can be enormous:  hg19 and hg38 run identical for 12.8Mb on chr7, and
asking for all of it turned a 39 chain window into a 1892 chain one.

quickLiftIsOwnChainTrack keeps quickLift's own chain track out of all this.  That
stanza carries quickLiftUrl and quickLiftDb like any lifted track and is loaded
by bigChainLoadItems like any bigChain, but its data is already in reference
coordinates.  The giveaway is that its bigDataUrl is the quickLift chain file.

validateOneTdb now accepts chain and bigChain.  netAlign is still refused:  a net
is a hierarchy of gaps rather than a plain alignment and wants its own thought.

refs #38249

diff --git src/hg/lib/trackHub.c src/hg/lib/trackHub.c
index 264bbb41fa4..d49d2f1cd2e 100644
--- src/hg/lib/trackHub.c
+++ src/hg/lib/trackHub.c
@@ -1940,43 +1940,47 @@
  * silently (no user-facing complaint about non-liftable types). */
 {
 // trackDb types are matched without regard to case since that's how the rest of the
 // browser reads them (some trackDb stanzas say "bigbed" rather than "bigBed").
 if (sameString("cytoBandIdeo", trackHubSkipHubName(tdb->track)) ||
     !( startsWithNoCase("bigBed", tdb->type) || \
        startsWithNoCase("bigWig", tdb->type) || \
        startsWithNoCase("bigDbSnp", tdb->type) || \
        startsWithNoCase("bigGenePred", tdb->type) || \
        startsWithNoCase("gvf", tdb->type) || \
        startsWithNoCase("genePred", tdb->type) || \
        startsWithNoCase("narrowPeak", tdb->type) || \
        startsWithNoCase("broadPeak", tdb->type) || \
        startsWithNoCase("bigLolly", tdb->type) || \
        startsWithNoCase("bigPsl", tdb->type) || \
+       startsWithNoCase("bigChain", tdb->type) || \
+       sameWord("chain", tdb->type) ||
+       startsWithNoCase("chain ", tdb->type) ||
        sameWord("psl", tdb->type) ||
        startsWithNoCase("psl ", tdb->type) ||
        sameWord("bed", tdb->type) ||
        startsWithNoCase("bed ", tdb->type)))
     {
     if (badList != NULL)
         slAddHead(badList, tdb);
     return FALSE;
     }
 
 // make sure we have a bigDataUrl
 if (startsWithNoCase("bigBed", tdb->type) || \
        startsWithNoCase("bigPsl", tdb->type) || \
+       startsWithNoCase("bigChain", tdb->type) || \
        startsWithNoCase("bigWig", tdb->type))
     {
     char *fileName = cloneString(trackDbSetting(tdb, "bigDataUrl"));
 
     if (fileName == NULL)
         {
         struct sqlConnection *conn = hAllocConnTrack(db, tdb);
         fileName = bbiNameFromSettingOrTable(tdb, conn, tdb->table);
         hashAdd(tdb->settingsHash, "bigDataUrl", fileName);
         hFreeConn(&conn);
         }
     }
 
 return TRUE;
 }