From b1b6eec0a5a05defc13e5eb33956eb7738a98a47 Mon Sep 17 00:00:00 2001 From: Mark Banner Date: Fri, 24 Apr 2026 15:17:52 +0100 Subject: [PATCH] When running the upload script, look for uncommitted changes and check before upload if necessary. --- scripts/upload.py | 56 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/scripts/upload.py b/scripts/upload.py index 05aa338..95dd2f7 100644 --- a/scripts/upload.py +++ b/scripts/upload.py @@ -4,8 +4,41 @@ from getpass import getpass import json import requests +import subprocess import sys +def has_uncommitted_changes(): + """Check if there are uncommitted changes in a jj or git repository.""" + # Try jj first — works for both native jj repos and git-backed jj repos. + try: + result = subprocess.run( + ["jj", "diff", "--stat"], + capture_output=True, + text=True, + ) + if result.returncode == 0: + # Successfully queried a jj repo; jj only tracks non-ignored files + # so no extra filtering is needed. + return bool(result.stdout.strip()) + # jj is installed but this is not a jj repository; fall back to git. + except FileNotFoundError: + pass # jj is not installed + + # Fall back to git, ignoring untracked files. + try: + result = subprocess.run( + ["git", "status", "--porcelain", "--untracked-files=no"], + capture_output=True, + text=True, + ) + if result.returncode == 0: + return bool(result.stdout.strip()) + except FileNotFoundError: + pass # git is not installed + + return False + + API_ENDPOINTS = { "dev": "https://remote-settings-dev.allizom.org/v1/", "stage": "https://remote-settings.allizom.org/v1/", @@ -37,6 +70,19 @@ parser.print_help() sys.exit(1) +if has_uncommitted_changes(): + if args.server == "prod": + print("ERROR: There are uncommitted local changes. Please ensure all changes have been reviewed before pushing to production", file=sys.stderr) + sys.exit(1) + + # We allow pushing with uncommitted changes on dev and stage because this + # can be useful for quick tests (e.g. to help QA check something). + reply = input( + "\n\nWARNING: There are uncommitted local changes. Are you sure you wish to proceed? (y/N):" + ).lower().strip() + if not reply or reply[0] != "y": + sys.exit(1) + # workspace = 'main' if args.server == 'dev' else 'main-workspace' API_ENDPOINT = API_ENDPOINTS[args.server] + "buckets/%s/collections/%s/records" % ( @@ -57,12 +103,6 @@ existingRecords = response.json() -# Handle python 2 backwards compatibility. -if sys.version_info[0] < 3: - inputFn = raw_input -else: - inputFn = input - def getIdForRecord(record): """ Gets or creates an id based on the given record. @@ -100,7 +140,7 @@ def findRecord(id, recordSet): def yes_or_no(question): while "the answer is invalid": - reply = str(inputFn(question + " (y/n): ")).lower().strip() + reply = str(input(question + " (y/n): ")).lower().strip() if reply[0] == "y": return True if reply[0] == "n": @@ -157,7 +197,7 @@ def strip_record(record): recordsToRemove.append(record) if len(recordsToRemove) > 0: - print("\Records to Remove:\n") + print("\nRecords to Remove:\n") for record in recordsToRemove: print(getIdForRecord(record))