--- name: p4-repro-script description: Generate a new Perforce (Helix Core) bug/behavior repro.sh script, following the style and conventions used throughout the repro/ directory tree (disposable P4 DVCS micro-repos, msg/errmsg/bail/cmd helpers, -f to reset the scratch dir, self-logging, numbered scenarios, and a plain-language Result section). Use this whenever asked to create, write, or scaffold a new repro script or repro for a Perforce/Helix Core bug, oddity, or demo. --- # P4 Repro Script Generator ## Purpose Produce a new, self-contained `repro.sh` (and, once run, its `repro.log`) that demonstrates a specific Perforce/Helix Core behavior -- a bug, a disagreement between commands, a surprising interaction between features, or occasionally just a demo of intended behavior. The output must read like the existing scripts in this repository's `repro/` directory: narrated, numbered, self-logging, and ending with a plain-language interpretation of what happened. This skill assumes `p4` and `p4d` are already installed and on `PATH`, and that the machine can create local P4-native DVCS "personal servers" via `p4 init`. ## Before writing anything 1. **Understand the bug/scenario precisely.** Get (or infer) the exact sequence of Perforce operations that triggers the behavior, and exactly what the "wrong" output looks like vs. the "right"/expected output. If this is ambiguous, ask before generating a script that might not actually reproduce anything. 2. **Look at a few sibling repros** in `repro/*/repro.sh` (especially any that touch similar areas: streams, opened files, resolve/merge, obliterate, shelve, etc.) to pick up any scenario-specific idioms before writing the new one. 3. **Pick a `PascalCase` directory name** for the repro that describes the bug concisely, e.g. `P4OpenedDisagreement`, `LostRename`, `SmartSyncDemo`. Create it under `repro//` and put the script at `repro//repro.sh`. ## Required structure and conventions Follow these precisely -- they are a fairly strict style guide, not just inspiration. See `templates/repro.sh.template` in this skill folder for a ready-to-copy skeleton implementing all of the below. 1. **Shebang and strict mode.** ```bash #!/opt/homebrew/bin/bash #------------------------------------------------------------------------------ ## Repro for: set -u ``` (Use `#!/bin/bash` instead if targeting a machine without homebrew bash; check what sibling scripts in the same batch use.) 2. **A `##`-prefixed usage/header comment block** right after the shebang, describing: * `## Usage:\n## ./repro.sh [-f] 2>&1` * `## Scenario: ` * A prose explanation of the bug: the setup, the specific commands that disagree or misbehave, and what "wrong" vs. "right" output looks like. Quote actual expected command output snippets when known. * What kind of throwaway environment is used (almost always `p4 init -C0 -n`) and confirmation that no real depot/server is touched. This block doubles as `usage()` output: `function usage () { grep '^##' "$0" | sed 's/^##//'; exit 1; }`. 3. **Environment isolation.** ```bash export P4CONFIG=.p4config.local export P4ENVIRO=/dev/null/.p4enviro ``` 4. **Standard variables.** ```bash declare ThisScript=${0##*/} declare Version=1.0.0 declare -i ErrorCount=0 declare AppHome="$PWD" declare H1="==============================================================================" declare Log="${ThisScript%.sh}.log" declare CmdLog= declare ReproDir=/tmp/repro ``` 5. **Micro functions** (copy verbatim; do not reinvent): ```bash function msg () { echo -e "${1:-Hi}"; } function errmsg () { msg "\nError: ${1:-Unknown Error}"; ErrorCount+=1; } function bail () { errmsg "${1:-Unknown Error}"; exit "$ErrorCount"; } function cmd () { msg "${2:-Executing command: $1}"; $1; return $?; } function usage () { grep '^##' "$0" | sed 's/^##//'; exit 1; } ``` Use `cmd "p4 something"` for simple one-shot commands you want narrated and run. For anything needing stdin redirection, pipes, or captured output for later `grep` checks, call `p4 ...` directly instead of via `cmd`, but still precede it with a `msg` line explaining what's about to happen and why. 6. **Command-line arg parsing.** Support `-f` (force-remove `$ReproDir`) and `-h` (usage) at minimum: ```bash declare -i Force=0 set +u while [[ $# -gt 0 ]]; do case $1 in (-f) Force=1;; (-h) usage;; (-*) bail "Usage error: Unknown option ($1).";; (*) bail "Usage error: Unknown parameter ($1).";; esac shift done set -u ``` 7. **Self-logging via `tee`.** ```bash if [[ "$Log" != off ]]; then touch "$Log" || bail "Couldn't touch log file [$Log]." exec > >(tee "$Log") exec 2>&1 fi ``` This must run **before** `cd`-ing into `$ReproDir`, so the log file (`repro.log`) lands next to `repro.sh`, not in the disposable scratch dir that may get wiped by a future `-f` run. 8. **Scratch dir setup, with the `-f` guard.** ```bash [[ -d "$ReproDir" && "$Force" -eq 1 ]] && /bin/rm -rf "$ReproDir" [[ -d "$ReproDir" ]] && bail "Old repro dir [$ReproDir] exists. Use -f to remove it first." mkdir "$ReproDir" || bail "Could not do: mkdir $ReproDir" cd "$ReproDir" || bail "Could not do: cd $ReproDir" CmdLog="$ReproDir/cmd.log" ``` 9. **Numbered, banner-separated scenario sections.** ```bash declare -i Scenario=1 msg "$H1\nScenario $Scenario: $ScenarioTitle\n" # ... Scenario=$((Scenario+1)) msg "\n$H1\nScenario $Scenario: <next title>.\n" ``` Always start with: ```bash msg "\nPreliminary info: Show versions of p4/p4d on the PATH:" cmd "p4 -V" cmd "p4d -V" msg "\nPreliminary setup: Spin up a local repo." cmd "p4 init -C0 -n" ``` 10. **Narrate every command with a `msg` before it**, explaining *why* you're running it, not just restating the command. When comparing "wrong" vs. "right" output (the whole point of most of these repros), run each variant, capture it to `$CmdLog`, `cat` it so it's visible in the log, and use `grep -q` against `$CmdLog` to assert the expected (buggy or correct) substring is present -- calling `errmsg` if the assertion fails, so a clean run (`ErrorCount=0`) really does mean the repro reproduced what it claims to. 11. **A plain-language "Result" section at the end**, always gated on `$ErrorCount`: ```bash msg "\n$H1\nThe Result:\n" if [[ "$ErrorCount" -eq 0 ]]; then rm -f "$CmdLog" msg "Yay, ..." msg "\n<explain what was observed, why it's wrong/interesting, and what the practical impact is>" else msg "One or more checks reported errors. Review the log above; the repro conditions may not have reproduced as expected on this version of p4d." fi exit "$ErrorCount" ``` ## After writing the script 1. Make it executable: `chmod +x repro/<Name>/repro.sh`. 2. **Dry-run it twice**: once normally, once with `-f`, confirming: * Exit code is `0` when the bug/behavior reproduces as expected. * Running again without `-f` correctly refuses (old `$ReproDir` exists). * `-f` cleans up and reruns successfully. * The generated `repro.log` (next to `repro.sh`, not in `/tmp/repro`) reads coherently top-to-bottom as a narrated transcript. 3. **Prototype interactively before finalizing** if the exact reproduction conditions are uncertain -- run the raw `p4`/`p4d` commands by hand in a scratch `/tmp` directory first, confirm the disagreement/bug actually occurs on the available p4d version, then encode exactly that sequence into the script. Don't guess at commands that "should" reproduce a bug without having verified them. 4. Once the repro is confirmed useful, add and submit both files to the depot together, e.g.: ```bash p4 add repro.sh repro.log p4 change -o | sed 's/<enter description here>/Add <Name> repro.sh and repro.log/' > /tmp/cl.txt p4 change -i < /tmp/cl.txt # note the new pending changelist number p4 submit -c <N> ``` Only include `repro.sh` and `repro.log` for the new repro in that changelist -- don't sweep in unrelated open files sitting in the `default` changelist. ## Template files in this skill * `templates/repro.sh.template` -- a copy-paste starting point implementing every convention above, with `<PLACEHOLDER>` markers for the scenario-specific parts (title, setup commands, scenario body, and assertions). ## Common patterns worth reusing * **Editing a stream spec via `p4 stream -o` + text munging + `p4 -s stream -i`.** Capture the current spec to a scratch file, transform it (an `awk` script, a `sed` one-liner, or a plain `cat ... >>` append when the field you're adding is known to be last), `grep -v '^#'` it for display, then reload with `p4 -s stream -i < file`. Always print the modified spec before applying it. * **Switching a workspace's stream:** `p4 client -s -S //stream/<name>` followed by `p4 sync` (note: switching the stream does *not* by itself update the `have` list -- a subsequent `p4 sync` is required, and is often itself part of the story, e.g. "smart sync" behavior). * **Comparing multiple invocations of the same command** (e.g. `p4 opened` vs. `p4 opened ...` vs. `p4 opened -a` vs. `p4 opened <file>`) is a good way to expose an internal disagreement/bug -- run each into `$CmdLog`, `cat` it, and assert on it individually. * **DVCS personal servers created back-to-back** need a `sleep 1` between `p4 init -C0 -n` calls in different directories, or they can end up with colliding `ServerID`s.