Build a Small Perl Patch from Code to Tests and Docs
You will finish with a reviewable Perl change: the C implementation is edited, regression tests cover the new behaviour, the documentation describes it, and the complete patch is ready to inspect. This is the workflow illustrated by the installed perlhacktut manual, which ships with Perl 5.38.2 on this machine.
The route
Jump straight to the step you need, or tick off Done means at the end.
- Checkpoint 1: start on an isolated branch
- Step 2: trace the behaviour to the implementation
- Step 3: make the smallest source change
- Step 4: add focused regression tests
- Checkpoint 2: build and run the relevant tests
- Step 5: document the changed contract
- Step 6: review the complete patch
- Step 7: prepare the hand-off
Allow an hour or two for a small change if the Perl source tree is already built. The first build can take longer. You need a Perl source checkout, Git, a compiler and the tools required by that checkout. This guide does not install dependencies or alter a system Perl installation.
Checkpoint 1: start on an isolated branch
Work in a clone of the Perl repository, not in a system directory. Replace the example path with your own checkout.
cd /path/to/perl5
git status --short
git switch -c pack-first-active-u
The status command should be empty before you begin. If it is not, stop and either commit or set aside those changes. Do not mix an unrelated working tree with a patch you intend to send.
Checkpoint
Confirm the branch and baseline before editing.
git branch --show-current
git status --short
perl -v | sed -n '1,3p'
The branch name should be pack-first-active-u. The manual's examples describe Perl internals and tests, so the exact source layout belongs to the checkout you are using. If your checkout has moved these files, locate the current equivalents before changing anything.
Step 2: trace the behaviour to the implementation
The example change concerns pack. Its runtime implementation is in the pp source files. In the manual's current example, the relevant function is pp_pack in pp_pack.c; older descriptions refer to pp.c and explicitly warn that the code was later split. Search the tree instead of assuming the old filename.
rg -n "pp_pack|SvUTF8_on|datumtype" pp*.c
The intended rule is precise: when U is the first active format in the pattern, the resulting string is treated as UTF-8. Leading spaces are inactive, so a pattern such as U* still qualifies. A pattern beginning with another active format, such as C0U*, does not.
Before touching the code, read the surrounding loop and identify how it consumes format characters. The small implementation change tracks the start of the active pattern, advances that marker when spaces are skipped, and enables the UTF-8 flag only when the current U is the first active format. Keep the edit local. Do not copy a snippet into a different function because the variable names happen to match.
Step 3: make the smallest source change
Make a backup only if your editor or local practice needs one. Git already records the original, so a separate backup file can become accidental patch content. After editing, inspect the exact diff.
git diff -- pp_pack.c
git diff --check
git diff --check should produce no output. The important review question is not whether the code compiles yet, but whether the condition distinguishes the three cases: U*, whitespace followed by U*, and an active format before U*.
Do not run the build or tests as root. They should operate inside the checkout and its build directories. Elevated privileges are not part of this workflow; using them can leave root-owned build artefacts that make later cleanup harder.
Step 4: add focused regression tests
Operator tests live below t/op. Find the current pack test and read its test-count setup before adding cases.
rg -n "pack|plan\(|1\.\." t/op/pack.t
Add tests beside the existing pack tests. The manual uses Test::More-style assertions for three behaviours: U* creates the expected Unicode string, leading spaces do not change that result, and C0U* does not make the result Unicode. Use the test helper and conventions already present in your checkout rather than importing a different testing style.
Update the declared test count when the file uses a fixed plan. A mismatch makes the harness report an incomplete or excessive test run even when the assertions themselves pass. If the file uses a dynamically calculated plan, follow its existing convention instead.
git diff -- t/op/pack.t
git diff --check
Checkpoint 2: build and run the relevant tests
Build Perl using the source tree's documented procedure, then run the focused test. The exact build command depends on how the checkout was configured, so do not paste a configure line with unverified options into an unrelated tree. The test command named by the manual is:
./perl t/op/pack.t
A passing run should report the planned tests as successful. If the interpreter is not at ./perl, use the freshly built interpreter produced by your checkout. Running the system perl instead can test the wrong implementation and give false confidence.
Then run the broader test suite required by the repository's contribution instructions. If a test fails, keep the failure output, check whether the failure is in your changed area, and fix the source or test before continuing. Do not delete failing tests or lower their expected values to make the run green.
Step 5: document the changed contract
A behaviour change is incomplete until users can discover it. The manual places this example in pod/perlfunc.pod, in the documentation for pack. Add a concise paragraph in the matching section. State that a pattern beginning with U produces a UTF-8-encoded result, that U0 can force the mode, and that an initial C0 or another active format prevents a later U from being treated as first.
Keep documentation and tests aligned with the code. If the implementation's actual rule differs from the example in this installed manual, describe the rule you verified in the current source and add a test for that rule. Review the rendered POD conventions around the insertion point before choosing markup.
git diff -- pod/perlfunc.pod
git diff --check
Step 6: review the complete patch
Run the focused test again after the documentation edit, then review every changed file and the final status.
./perl t/op/pack.t
git diff --stat
git diff --check
git status --short
git diff
The diff should contain only the intended implementation, test and documentation changes. Check for debug prints, generated files, editor backups such as pp_pack.c~, accidental whitespace churn and an incorrect test count. If a backup file was created, remove only that known file from the checkout, then confirm the status again. Do not use a broad recursive deletion command.
Step 7: prepare the hand-off
Once the focused and broader tests pass, make the patch available for review using the contribution process documented by the Perl project. The installed tutorial points to perlhack for submission details. Keep the branch until review is complete so you can amend or explain the same coherent change.
If you need to abandon the experiment, first preserve anything valuable with a patch file or a commit. Then switch away from the branch and remove it only after checking its name and status:
git diff > /tmp/perl-pack.patch
git status --short
git switch main
git branch -D pack-first-active-u
The branch deletion is destructive for uncommitted work. The patch in /tmp/perl-pack.patch is a temporary recovery copy, not a substitute for a reviewed commit. If your default branch is not named main, use the branch shown by git branch --list.
Done means
- The change is on an isolated branch with a clean, understood starting point.
- The implementation handles
U*, leading whitespace, and a prior active format distinctly. - The focused pack test and the repository's broader test run pass.
- The fixed test plan, if present, matches the number of tests.
- The
packdocumentation describes the behaviour users will observe. git diff --checkis clean and the final diff contains no accidental files.