705b694649940189cdb66af57560c97a0cd606f7
lrnassar
  Fri May 15 14:42:52 2026 -0700
Three small rtsUpdate fixes uncovered during first live test on Non_Coding_SNVs_hg38: line-buffer stdout so git diff output interleaves correctly with surrounding prints, replace deprecated datetime.utcnow() with timezone-aware datetime.now(datetime.timezone.utc), and print a trailing newline after confirm() so the post-prompt status message doesn't land on the same line as the prompt. refs #32768

diff --git src/hg/utils/rts/rtsUpdate src/hg/utils/rts/rtsUpdate
index 4c634089494..3213c3f480b 100755
--- src/hg/utils/rts/rtsUpdate
+++ src/hg/utils/rts/rtsUpdate
@@ -29,51 +29,53 @@
 }
 
 VALIDATE_URL = {
     "dev":  "https://hgwdev.gi.ucsc.edu",
     "beta": "https://hgwbeta.soe.ucsc.edu",
     "rr":   "https://genome.ucsc.edu",
 }
 
 # Session and userName must match this. Excludes anything that could be SQL- or
 # shell-special; permits the URL-encoding form ('%' and digits) used for sessions
 # whose human-readable names contain spaces (e.g. 'CNVs%20Clinical').
 SAFE_NAME = re.compile(r"^[A-Za-z0-9_%.\-]+$")
 
 
 def log(msg):
-    ts = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
+    ts = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
     line = f"{ts} {getpass.getuser()} {msg}\n"
     LOGFILE.parent.mkdir(parents=True, exist_ok=True)
     with open(LOGFILE, "a") as f:
         f.write(line)
 
 
 def die(msg, code=1):
     print(f"ERROR: {msg}", file=sys.stderr)
     sys.exit(code)
 
 
 def confirm(prompt):
     while True:
         try:
             ans = input(prompt + " (yes/no): ").strip().lower()
         except EOFError:
             return False
         if ans == "yes":
+            print()
             return True
         if ans == "no":
+            print()
             return False
         print("  please type 'yes' or 'no'")
 
 
 def validate_name(name, label):
     if not name or not SAFE_NAME.match(name):
         die(f"unsafe {label}: {name!r} (must match {SAFE_NAME.pattern})")
 
 
 # ---------- hgsql shim ----------
 
 def hgsql_read(host_key, sql):
     """Run a SELECT.  Returns list[list[str]].  Caller is responsible for
     interpolating only validated values into sql."""
     host, db = HOSTS[host_key]
@@ -353,31 +355,31 @@
     db = find_db_for_session(target) or find_db_in_tree(target)
     if db is None:
         die(f"cannot find {target!r} in recTrackSets.<db>.tab nor in rts/<db>/")
 
     pairs = read_kent_file(db, target)
     if not pairs:
         die(f"kent tree file is empty: {kent_path(db, target)}")
     new_contents = join_pairs(pairs)
 
     dest_h, dest_db = HOSTS[dest]
     print(f"\nReading current row from {dest} ({dest_h}:{dest_db})...")
     dest_contents, dest_settings, dest_exists = fetch_session_row(dest, TARGET_USER, target)
     if not dest_exists and not args.allow_create:
         die(f"View/{target} does not exist on {dest}; pass --allow-create to INSERT")
 
-    ts = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
+    ts = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%dT%H%M%SZ")
     bdir = pathlib.Path(tempfile.gettempdir()) / f"rtsUpdate-{ts}" / dest
     bdir.mkdir(parents=True, exist_ok=True)
     bfile = bdir / f"{target}.tsv"
     if dest_exists:
         bfile.write_text(f"{TARGET_USER}\t{target}\t{dest_contents}\t{dest_settings}\n")
         print(f"  pre-write backup: {bfile}")
     else:
         print(f"  no existing row on {dest} -- nothing to back up")
 
     dest_pairs = sorted(set(split_pairs(dest_contents))) if dest_exists else []
     print(f"\n--- diff: kent tree ({db}/{target}) vs {dest} ---")
     show_pair_diff(dest, dest_pairs, "tree", pairs, args.verbose)
 
     if dest == "rr":
         if not args.i_confirm_rr:
@@ -494,27 +496,33 @@
     pp.add_argument("--allow-create", action="store_true",
                     help="INSERT row if missing on dest")
     pp.add_argument("--i-confirm-rr", action="store_true",
                     help="required in addition to interactive yes for --to rr")
     pp.add_argument("--verbose", "-v", action="store_true")
 
     pd = sub.add_parser("diff",
                         help="read-only: compare kent tree vs dev/beta/rr")
     pd.add_argument("--target-session", required=True)
     pd.add_argument("--verbose", "-v", action="store_true")
 
     return p
 
 
 def main():
+    # Line-buffer stdout so prints interleave correctly with subprocess output
+    # (git diff, hgsql) that writes directly via the OS.
+    try:
+        sys.stdout.reconfigure(line_buffering=True)
+    except AttributeError:
+        pass
     args = build_parser().parse_args()
     if args.cmd == "fetch":
         return cmd_fetch(args)
     if args.cmd == "push":
         return cmd_push(args)
     if args.cmd == "diff":
         return cmd_diff(args)
     return 0
 
 
 if __name__ == "__main__":
     sys.exit(main() or 0)