84b2a66056c61f0661676de79e7d710073ba8d5d
max
  Tue Feb 3 01:37:38 2026 -0800
adding hub email display to hub error output, refs #36916 and refs #33571

diff --git src/hg/hgTracks/hgTracks.c src/hg/hgTracks/hgTracks.c
index 426b0a9c360..20a4459b34d 100644
--- src/hg/hgTracks/hgTracks.c
+++ src/hg/hgTracks/hgTracks.c
@@ -1200,35 +1200,39 @@
 char *(*finder)(char *needle, char *haystack) = (anyIupac(fOligo) ? iupacIn : stringInWrapper);
 int oligoSize = strlen(fOligo);
 char *rOligo = cloneString(fOligo);
 char *rMatch = NULL, *fMatch = NULL;
 struct bed *bedList = NULL, *bed;
 char strand;
 int count = 0, maxCount = 1000000;
 char *strandFilter = cartUsualString(cart, oligoMatchStrandVar, oligoMatchStrandDefault);
 boolean searchForward = sameString(strandFilter, "both") || sameString(strandFilter, "forward");
 boolean searchReverse = sameString(strandFilter, "both") || sameString(strandFilter, "reverse");
 
 if (oligoSize >= 2)
     {
     if (searchForward)
         fMatch = finder(fOligo, dna);
-    iupacReverseComplement(rOligo, oligoSize);
+
     if (sameString(rOligo, fOligo))
         rOligo = NULL;
     else if (searchReverse)
+        {
+        iupacReverseComplement(rOligo, oligoSize);
 	rMatch = finder(rOligo, dna);
+        }
+
     for (;;)
         {
 	char *oneMatch = NULL;
 	if (rMatch == NULL)
 	    {
 	    if (fMatch == NULL)
 		break;
 	    else
 		{
 		oneMatch = fMatch;
 		fMatch = finder(fOligo, fMatch+1);
 		strand = '+';
 		}
 	    }
 	else if (fMatch == NULL)
@@ -8747,30 +8751,61 @@
 #endif
 
 
 unsigned getParaLoadTimeout()
 // get the parallel load timeout in seconds (defaults to 90)
 {
 char *paraLoadTimeoutStr = cartOptionalString(cart, "parallelFetch.timeout");
 if (paraLoadTimeoutStr == NULL)
     paraLoadTimeoutStr = cfgOptionDefault("parallelFetch.timeout", "90");  // wait up to default 90 seconds.
 
 unsigned paraLoadTimeout = sqlUnsigned(paraLoadTimeoutStr);
 
 return paraLoadTimeout;
 }
 
+static char *hubPublicEmailFromHubName(char *hubName)
+{
+/* return public hub email given url or NULL if such a column doesn't exist (mirrors don't have this column) */
+/* result must be freed */
+char *hubIdStr = strchr(hubName, '_'); // could not find a function for this in hubConnect.c
+if (!hubIdStr)
+    return NULL;
+unsigned hubId = sqlUnsigned(hubIdStr+1);
+
+struct hubConnectStatus *hubStatus = hubFromIdNoAbort(hubId);                                                           
+if (hubStatus == NULL)
+    return NULL;
+
+char *url = hubStatus->hubUrl;
+if (!url)
+    return NULL;
+
+struct sqlConnection *conn = hConnectCentral();
+
+char *email = NULL;
+if (sqlColumnExists(conn, "hubPublic", "email"))
+    {
+    char query[1000];
+    sqlSafef(query, sizeof query, "SELECT email FROM hubPublic WHERE hubUrl='%s'", url);
+    email = sqlQuickNonemptyString(conn, query);
+    }
+
+hDisconnectCentral(&conn);
+return email;
+}
+
 void doTrackForm(char *psOutput, struct tempName *ideoTn)
 /* Make the tracks display form with the zoom/scroll buttons and the active
  * image.  If the ideoTn parameter is not NULL, it is filled in if the
  * ideogram is created.  */
 {
 #ifdef GRAPH_BUTTON_ON_QUICKLIFT
 int graphCount = 0;
 #endif
 int disconCount = 0;
 struct group *group;
 struct track *track;
 char *freezeName = NULL;
 boolean hideAll = cgiVarExists("hgt.hideAll");
 boolean hideTracks = cgiOptionalString( "hideTracks") != NULL;
 boolean defaultTracks = cgiVarExists("hgt.reset");
@@ -9901,30 +9936,33 @@
                 // we want tracks in the visible list to also be visible
                 // in the normal group list, so use a separate hash for the
                 // visible tracks grouping
                 groupTrackListAddSuper(cart, group, hashNew(8), hashNew(8));
                 }
             else
                 groupTrackListAddSuper(cart, group, superHash, trackHash);
 
 	    /* Display track controls */
             if (group->errMessage)
                 {
                 hPrintf("<tr><td colspan=8><b>Track hub error</b> ");
                 printInfoIcon("Use the hub debugging tool under <i>My Data > Track Hubs > Hub Development</i>. You need to switch off <i>File caching</i> there to see your changes without delay. Error <i>Response is missing required header</i> usually means the hub is not reachable.<br><br>Contact us or the hub provider if you cannot resolve the issue.");
                 hPrintf(": ");
                 hPrintf("<i>%s</i>", group->errMessage);
+                char *email = hubPublicEmailFromHubName(hubName);
+                if (isNotEmpty(email))
+                    hPrintf("<br>You can contact the hub author at %s", email);
                 hPrintf("</td></tr>\n");
                 }
 
 	    for (tr = group->trackList; tr != NULL; tr = tr->next)
 		{
 		struct track *track = tr->track;
 		if (tdbIsSuperTrackChild(track->tdb))
 		    /* don't display supertrack members */
 		    continue;
                 // only top level tracks contribute to the total count
                 trackCount++;
 		myControlGridStartCell(cg, isOpen, group->name,
                                        shouldBreakAll(track->shortLabel));
 
                 printTrackLink(track);