8600f5319ad0670e63cdee1f17fe2872224bd89d braney Sat Jul 18 14:00:49 2026 -0700 redmineCli: support --assemblies and --custom-field on create Trackers with required custom fields (e.g. Track requires Assemblies) 422'd on a bare create. Add a named --assemblies flag (custom field 2) and a generic repeatable --custom-field ID=VALUE, reusing the same parsing/validation as update. refs #37281 Co-Authored-By: Claude Opus 4.8 (1M context) diff --git src/utils/redmineCli src/utils/redmineCli index 65b4f781575..8130aa030f7 100755 --- src/utils/redmineCli +++ src/utils/redmineCli @@ -1,24 +1,26 @@ #!/usr/bin/env python3 """Comprehensive Redmine CLI for the UCSC Genome Browser team. Reads the Redmine API key from ~/.hg.conf (redmine.apiKey=...). Usage: redmineCli show 33571 redmineCli list --project maillists --status open --limit 10 redmineCli create --subject "Bug report" --description "Details here" + redmineCli create --project genomebrowser --tracker 11 --subject "New track" \ + --description "Details here" --assemblies hg38 # Track tracker requires Assemblies redmineCli build-patch --subject "v500 build patch: ..." --description "..." \ --developer braney --commit-id abc123 --files src/hg/js/hgTracks.js \ --cgis hgTracks --test-case "..." --reviewer hiram \ --target-version 500 --relates 27113 redmineCli comment 33571 --message "Adding a note" redmineCli update 33571 --status 1 --assigned-to lou --note "Reopening" redmineCli update 33571 --release-log-url "../cgi-bin/hgTrackUi?db=hg38&g=myTrack" redmineCli update 33571 --custom-field 46="some value" redmineCli attach 33571 screenshot.png --note "See attached" redmineCli note 20460 85 # show note-85 from ticket 20460 redmineCli relate 10316 15336 30368 # relate tickets to each other redmineCli watch 37339 lou braney # add watchers to a ticket redmineCli users # list user names and IDs """ @@ -623,30 +625,43 @@ "subject": subject, "description": description, "tracker_id": resolve_tracker(args.tracker), "priority_id": args.priority, "status_id": resolve_status(args.status), } } custom_fields = [] if args.category: custom_fields.append({"id": CF_CATEGORY, "value": args.category}) if args.email: custom_fields.append({"id": CF_EMAIL, "value": args.email}) if args.mlm: custom_fields.append({"id": CF_MLM, "value": args.mlm}) + if getattr(args, "assemblies", None) is not None: + custom_fields.append({"id": CF_ASSEMBLIES, "value": args.assemblies}) + for cf_spec in (getattr(args, "custom_field", None) or []): + if "=" not in cf_spec: + sys.exit(f"Error: --custom-field must be ID=VALUE, got: {cf_spec}") + cf_id, cf_val = cf_spec.split("=", 1) + if not cf_id.isdigit(): + sys.exit(f"Error: custom field ID must be numeric, got: {cf_id}") + cf_id = int(cf_id) + if cf_id in FIELD_VALIDATORS: + validator, name = FIELD_VALIDATORS[cf_id] + cf_val = validator(cf_val, f"--custom-field {cf_id} ({name})") + custom_fields.append({"id": cf_id, "value": cf_val}) if custom_fields: issue_data["issue"]["custom_fields"] = custom_fields if args.assigned_to: issue_data["issue"]["assigned_to_id"] = resolve_user(args.assigned_to) result = api_post(args.base_url, "/issues.json", args.api_key, issue_data) ticket_id = result["issue"]["id"] print(f"Created #{ticket_id}: {make_url(args.base_url, ticket_id)}") # --------------------------------------------------------------------------- # Subcommand: build-patch # --------------------------------------------------------------------------- @@ -1089,30 +1104,37 @@ p_create.add_argument("--description-file", dest="description_file", help="Read description from file (- for stdin)") p_create.add_argument("--project", default=DEFAULT_PROJECT, help="Project (default: %(default)s)") p_create.add_argument("--tracker", default=TRACKER_MLQ, help="Tracker name or ID (e.g. 'To Do' or 10; default: %(default)s)") p_create.add_argument("--priority", type=int, default=PRIORITY_UNPRIORITIZED, help="Priority ID (default: %(default)s)") p_create.add_argument("--status", default=STATUS_NEW, help="Status name or ID (e.g. 'New' or 1; default: %(default)s)") p_create.add_argument("--assigned-to", dest="assigned_to", help="Assignee name or user ID") p_create.add_argument("--category", help="MLQ Category (custom field)") p_create.add_argument("--email", help="Sender email (custom field)") p_create.add_argument("--mlm", help="MLM name (custom field)") + p_create.add_argument("--assemblies", + help="Assemblies (custom field, e.g. 'hg38')") + p_create.add_argument("--custom-field", dest="custom_field", + action="append", metavar="ID=VALUE", + help="Set an arbitrary custom field by numeric ID " + "(repeatable). Use for required fields on trackers " + "like Track that would otherwise 422.") # build-patch p_bp = sub.add_parser("build-patch", help="Create a Build Patch ticket (GB, Urgent, all required fields set)") p_bp.add_argument("--subject", required=True, help="Ticket subject") p_bp.add_argument("--description", help="What is broken and why it needs a patch") p_bp.add_argument("--description-file", dest="description_file", help="Read description from file (- for stdin)") p_bp.add_argument("--developer", required=True, help="Developer who caused the bug (name or user ID)") p_bp.add_argument("--commit-id", dest="commit_id", required=True, help="Fix commit hash; list all hashes if several commits fix it") p_bp.add_argument("--files", required=True, help="Full paths of the source files changed") p_bp.add_argument("--cgis", required=True,