I also considered writing a script to get all the Changelists since UE 5.5 was branched from UE5/Main. The stream was taken at CL 36144969. Then search for all CL description containing ‘arm’. But you have access to P4, so you can probably do it too. I asked ChatGPT to write up a Python script to pull the changelists with the prompt below. I didn’t test, but I took a quick look and the code looks like a good starting point. It might needs some tweaking, but it should give you a good starting point (and you can rerun/tweak the prompt by another AI if you want).
Can you write a python script to gather all changelists description from a Perforce Stream //UE5/Main since CL 36144969 on server "perforce:1666"? Then, search all changelist descriptions for the word 'arm'. If there is a match, putt the changelist number and the description in a python dictionary with the changelist number as a key and the descrition as the value. Then nicely print the content of the dictionnary in a 2 columns table.This is the result
#!/usr/bin/env python3
"""
Collect Perforce changelist descriptions from //UE5/Main since a given CL,
find descriptions containing a keyword (case-insensitive), store matches in a dict
{cl_number: description}, and print a 2-column table.
Requires:
pip install P4Python
Auth:
export P4PORT=perforce:1666
export P4USER=your_user
export P4CLIENT=your_workspace
# and be logged in:
p4 -p perforce:1666 login
"""
from __future__ import annotations
import os
import re
import sys
from typing import Dict, List
from P4 import P4, P4Exception
def gather_matching_cl_descriptions(
p4port: str,
stream: str,
since_cl: int,
keyword: str = "arm",
max_changes: int = 0,
) -> Dict[int, str]:
"""
Fetch changelists for a stream since 'since_cl', search their descriptions for keyword,
return {change: description}.
"""
p4 = P4()
p4.port = p4port
# Prefer env vars for these; set if present.
p4.user = os.environ.get("P4USER", "")
p4.client = os.environ.get("P4CLIENT", "")
# Case-insensitive whole-word-ish match (arm matches Arm, ARM, etc.)
pattern = re.compile(rf"\b{re.escape(keyword)}\b", re.IGNORECASE)
matches: Dict[int, str] = {}
try:
p4.connect()
# Get changelists from the stream since the CL.
# `p4 changes -s submitted -m N -c client` isn't right for streams.
# Use: p4 changes -s submitted //UE5/Main/...[Content removed]now
path_range = f"{stream}/...[Content removed]now"
args: List[str] = ["-s", "submitted"]
if max_changes and max_changes > 0:
args += ["-m", str(max_changes)]
args.append(path_range)
changes = p4.run("changes", *args)
# 'p4 changes' returns newest-first; iterate and inspect descriptions via 'describe -s'
for ch in changes:
cl_str = ch.get("change")
if not cl_str:
continue
cl = int(cl_str)
desc = p4.run("describe", "-s", str(cl))[0].get("desc", "")
desc_one_line = " ".join(desc.split()) # normalize whitespace for printing
if pattern.search(desc_one_line):
matches[cl] = desc_one_line
return matches
except P4Exception as e:
# Provide helpful error output without dumping sensitive details
sys.stderr.write("Perforce query failed.\n")
for msg in getattr(p4, "errors", []) or []:
sys.stderr.write(f"- {msg}\n")
sys.stderr.write(f"{e}\n")
raise
finally:
if p4.connected():
p4.disconnect()
def print_dict_as_table(cl_to_desc: Dict[int, str]) -> None:
"""
Print a 2-column table: Changelist | Description
"""
if not cl_to_desc:
print("No matching changelists found.")
return
# Sort ascending (or change to reverse=True if you prefer newest-first)
items = sorted(cl_to_desc.items(), key=lambda kv: kv[0])
header1, header2 = "Changelist", "Description"
col1_width = max(len(header1), max(len(str(cl)) for cl, _ in items))
col2_width = max(len(header2), 40)
def trunc(s: str, width: int) -> str:
return s if len(s) <= width else (s[: width - 1] + "…")
# Pick a reasonable max width for the description column for terminal output
term_width = 120
max_desc_width = max(40, term_width - (col1_width + 3 + 3)) # separators/padding
print(f"{header1:<{col1_width}} | {header2}")
print(f"{'-' * col1_width}-+-{'-' * max_desc_width}")
for cl, desc in items:
print(f"{cl:<{col1_width}} | {trunc(desc, max_desc_width)}")
def main() -> int:
p4port = "perforce:1666"
stream = "//UE5/Main"
since_cl = 36144969
keyword = "arm"
cl_to_desc = gather_matching_cl_descriptions(
p4port=p4port,
stream=stream,
since_cl=since_cl,
keyword=keyword,
max_changes=0, # 0 = no limit; set e.g. 5000 if you want to cap runtime
)
print_dict_as_table(cl_to_desc)
return 0
if __name__ == "__main__":
raise SystemExit(main())
Regards,
Patrick
[Attachment Removed]