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.
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.PascalCase directory name for the repro that describes the
bug concisely, e.g. P4OpenedDisagreement, LostRename,
SmartSyncDemo. Create it under repro/<Name>/ and put the script at
repro/<Name>/repro.sh.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.
Shebang and strict mode.
#!/opt/homebrew/bin/bash
#------------------------------------------------------------------------------
## Repro for: <one-line description of the bug/behavior>
set -u
(Use #!/bin/bash instead if targeting a machine without homebrew bash;
check what sibling scripts in the same batch use.)
A ##-prefixed usage/header comment block right after the shebang,
describing:
## Usage:\n## ./repro.sh [-f] 2>&1## Scenario: <Title>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; }.
Environment isolation.
export P4CONFIG=.p4config.local
export P4ENVIRO=/dev/null/.p4enviro
Standard variables.
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
Micro functions (copy verbatim; do not reinvent):
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.
Command-line arg parsing. Support -f (force-remove $ReproDir)
and -h (usage) at minimum:
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
Self-logging via tee.
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.
Scratch dir setup, with the -f guard.
[[ -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"
Numbered, banner-separated scenario sections.
declare -i Scenario=1
msg "$H1\nScenario $Scenario: $ScenarioTitle\n"
# ...
Scenario=$((Scenario+1))
msg "\n$H1\nScenario $Scenario: <next title>.\n"
Always start with:
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"
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.
A plain-language "Result" section at the end, always gated on
$ErrorCount:
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"
chmod +x repro/<Name>/repro.sh.-f, confirming:
0 when the bug/behavior reproduces as expected.-f correctly refuses (old $ReproDir exists).-f cleans up and reruns successfully.repro.log (next to repro.sh, not in /tmp/repro)
reads coherently top-to-bottom as a narrated transcript.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.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.
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).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.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).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.sleep 1 between
p4 init -C0 -n calls in different directories, or they can end up with
colliding ServerIDs.---
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/<Name>/` and put the script at
`repro/<Name>/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: <one-line description of the bug/behavior>
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: <Title>`
* 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.
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #1 | 33288 | C. Thomas Tyler | Add ReadMe.md and ai_dev_support p4-repro-script skill |