f518e03d8f56af784ec14536a7bdc0daed9a38fd
chmalee
  Wed Sep 16 11:11:32 2026 -0700
Send each file's genome with a hubtools upload so hubSpace rows get a db, and stop the server rewriting a user-uploaded hub.txt, no redmine

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

diff --git src/hg/lib/userdata.c src/hg/lib/userdata.c
index 0bc28371523..4617b662ff6 100644
--- src/hg/lib/userdata.c
+++ src/hg/lib/userdata.c
@@ -620,47 +620,57 @@
     sleep1000(HUB_LOCK_POLL_MS);
     waitedMs += HUB_LOCK_POLL_MS;
     }
 dyStringFree(&lockPath);
 return fd;
 }
 
 void unlockHubDir(int fd)
 /* Release an exclusive hub lock acquired by lockHubDir. Closing the fd
  * releases the flock automatically on Linux. */
 {
 if (fd >= 0)
     close(fd);
 }
 
-void upgradeExistingHubToAssembly(struct hubSpace *rowForFile, char *userDataDir)
-/* When a 2bit lands in a hub, add the assembly stanza to hub.txt (if the
- * backend owns it) and flip every row for this hub to hubType='assemblyHub'.
- * No-op unless rowForFile is a 2bit. */
+void upgradeExistingHubToAssembly(struct hubSpace *rowForFile, char *userDataDir,
+    boolean backendOwnsHubTxt)
+/* When a 2bit arrives into a hub, mark every hubSpace row for this hub as
+ * hubType='assemblyHub'. When backendOwnsHubTxt, first add the assembly stanza to
+ * the synthesized hub.txt, which is itself a no-op if that file does not exist.
+ * Pass FALSE for a hub.txt the user uploaded, whose contents are theirs to write.
+ * The whole function is a no-op unless rowForFile is a 2bit. */
 {
 if (!sameOk(rowForFile->fileType, "2bit"))
     return;
 
 char *hubDir = hubPathFromParentDir(rowForFile->parentDir, userDataDir);
 struct dyString *hubFileDy = dyStringCreate("%s%shub.txt",
     hubDir, endsWith(hubDir, "/") ? "" : "/");
 char *hubFile = dyStringCannibalize(&hubFileDy);
+// rewrite hub.txt before any row is touched. upgradeHubTxtForAssembly aborts on a
+// failed write and the caller's errCatch does not roll back mysql, so flipping the
+// rows first would leave a hub marked assemblyHub whose hub.txt has no twoBitPath
+if (backendOwnsHubTxt)
     upgradeHubTxtForAssembly(hubFile, rowForFile->db, rowForFile->location);
 
 setAssemblyHubTypeForDir(rowForFile->userName, hubDir);
+if (backendOwnsHubTxt)
+    {
     // hub.txt just changed on disk, so the row's size and md5 are out of date
     refreshHubTextRow(rowForFile->userName, hubFile);
+    }
 
 freeMem(hubFile);
 }
 
 char *writeHubText(char *path, char *userName, char *db, char *twoBitFileName)
 /* Create a hub.txt file, optionally creating the directory holding it.
  * If twoBitFileName is non-NULL, write an assembly hub stanza referencing it
  * (with stub organism / scientificName / description / defaultPos derived from
  * the 2bit). For convenience, return the file name of the created hub, which
  * can be freed. */
 {
 // an empty path would put the hub.txt at the root of the filesystem
 if (isEmpty(path))
     errAbort("no directory given for the hub, cannot create hub.txt");
 int oldUmask = 00;